无论是大型虚拟世界还是简单的网站,性能优化都是必要的。
特别是在运用三维模型的情况下,我们需要更加深入的优化。因为三维模型通常包含大量的数据和复杂的几何形状,如果不进行性能优化,浏览器可能会因为负载过重而崩溃。
在本文中,我们将探讨如何在three.js / React-three-fiber中对加载三维模型进行性能优化。我们将介绍一些实践方法和技巧,让你的虚拟世界或网站能够以更好的性能运行。
Stats.js是一个轻量级的性能监测库,可以用于监测WebGL或者其他HTML5应用的帧速率、内存使用情况等性能指标。它可以在浏览器中展示FPS(每秒帧数)、渲染时间、内存占用等性能指标,便于开发者实时监测应用程序的性能状况并进行优化。
代码示:
var stats = new Stats();
stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// monitored code goes here
stats.end();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
r3f-perf是一个React Three Fiber(R3F)的性能分析工具,它可以帮助开发者识别和解决在使用R3F构建WebGL应用程序时可能遇到的性能问题。它提供了实时的性能指标,例如帧率、GPU和CPU使用率、内存使用量等
代码示:
import { Canvas } from "@react-three/fiber";
import { Perf } from "r3f-perf";
function App() {
return (
<Canvas>
<Perf position="bottom-left" />
</Canvas>
);
}
Spector.js可以捕获所有WebGL调用,并将其记录到一个文件中,以便开发人员可以分析和调试应用程序中的问题,不仅有依赖包,还有浏览器插件,数据详细,适用于性能深入分析。
gltf-pipeline 是一个用于转换、优化和验证 GLTF 文件的命令行工具。它可以
- 校验 GLTF 文件的结构、语法和语义是否正确;
- 优化 GLTF 文件的大小和加载性能;
- 转换 GLTF 文件到其他格式,如 glb、FBX、OBJ、Collada、Three.js JSON 等。
压缩glb文件命令行
gltf-pipeline -i male.glb -o male-processed.glb -d
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
const loader = new GLTFLoader();
// 创建解码器实例
const dracoLoader = new DRACOLoader();
// 设置解压库文件路径
dracoLoader.setDecoderPath(DECODER_PATH);
// 加载解码器实例
loader.setDRACOLoader(dracoLoader);
loader.load(MODEL_FILE_PATH, (gltf) => {
let model = gltf.scene;
// ....
});
import { Clone } from "@react-three/drei";
function Model() {
const { scene } = useGLTF(MODEL_FILE_PATH);
return <primitive object={scene} />;
}
import { Clone, useGLTF } from "@react-three/drei";
function Model() {
const { nodes } = useGLTF(MODEL_FILE_PATH);
return <Clone object={nodes.foo} />;
}
① 在移动端等网络环境较差的情况下,压缩模型可以减少加载时间和流量消耗;
② 当模型文件较大,需要优化渲染性能时,可以使用压缩工具进行优化;
③ 在需要快速加载和渲染大量模型的场景中,压缩文件可以提高整体性能。
InstancedMesh 是 Mesh 的一个特殊版本,支持实例化渲染。如果您需要渲染大量具有相同几何和材质但具有不同世界变换的对象,请使用 InstancedMesh。使用 InstancedMesh 将帮助你减少绘制调用的数量(draw calls),从而提高应用程序的整体渲染性能。
InstancedMesh 是一种优化技术,它允许在场景中创建多个具有相同形状和材质的对象,同时只使用一次网格数据。它的原理是通过使用单个网格实例来渲染多个实例,而不是为每个实例创建单独的网格。
在实现上,InstancedMesh 的原理是使用 instancing 技术,通过将变量和矩阵传递到着色器中,来控制每个实例的位置、旋转和缩放等属性。
性能测试例子、性能测试代码
不使用instanceMesh
drawcalls为1000
核心代码如下:
const count = 1000;
const matrix = new THREE.Matrix4();
for ( let i = 0; i < count; i ++ ) {
randomizeMatrix( matrix );
const mesh = new THREE.Mesh( geometry, material );
mesh.applyMatrix4( matrix );
scene.add( mesh );
}
使用instanceMesh
drawcalls为1
核心代码如下:
const count = 1000;
const matrix = new THREE.Matrix4();
const mesh = new THREE.InstancedMesh(geometry, material, count);
for (let i = 0; i < count; i++) {
randomizeMatrix(matrix);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
实现 instanced 的例子
将 glb/gltf 文件自动转为 gltfjsx 的工具:gltfjsx
(拖拽文件生成 https://gltf.pmnd.rs/ ,但是自动生成的可能不对,需要自己调整一下)
再举个
// 定义InstancesProvider
import React, { useMemo, useContext, createContext } from 'react'
import { useGLTF, Merged, } from '@react-three/drei'
const context = createContext()
export function InstancesProvider({ children, ...props }) {
const { nodes } = useGLTF(MODEL_FILE_PATH)
const instances = useMemo(() => ({ Screw1: nodes['Screw1'], Screw2: nodes['Screw2'] }), [nodes])
return (
<Merged meshes={instances} {...props}>
{(instances) => <context.Provider value={instances} children={children} />}
</Merged>
)
}
// 定义Model
export function Model(props) {
const instances = useContext(context)
return (
<group {...props} dispose={null}>
<instances.Screw1 position={[-0.42, 0.04, -0.08]} rotation={[-Math.PI / 2, 0, 0]} />
<instances.Screw2 position={[-0.42, 0.04, -0.08]} rotation={[-Math.PI / 2, 0, 0]} />
</group>
)
}
// 使用Model
import { InstancesProvider, Model } from './Model'
<InstancesProvider>
<Model position={[10,0,0]}>
<Model position={[-10,0,0]}>
<Model position={[-10,10,0]}>
</InstancesProvider>
(在这些场景下,使用 InstancedMesh 可以减少 GPU 的工作量,从而提高渲染性能):
① 大量相似的物体需要被渲染,例如草、树、石头等;
② 展示大规模的复杂场景,例如城市或者森林等;
③ 大量重复的元素需要被渲染,例如在复杂的建筑中的砖块、玻璃面板等;
④ 需要在不同的地方渲染同一个物体,例如在多个镜头之间切换的时候。
(在以下场景下使用 InstancedMesh 有可能会使性能变差):
① 当需要在每个实例之间进行大量不同的计算时,例如不同的动画或者物理模拟;
② 当需要在每个实例之间进行大量不同的着色器计算时,例如每个实例有不同的材质或者纹理;
③ 当需要经常更新实例的位置、旋转或者缩放时。
- 减少模型面数:可以通过优化 3D 模型来减少面数,从而提高性能。可以使用 Blender 等建模软件来进行优化,也可以使用 Three.js 自带的 SimplifyModifier 和 DecimationModifier 来进行简化。
- 使用纹理贴图:纹理贴图可以减少几何体的面数,同时提高渲染效率。可以使用 UV 映射技术将纹理贴图应用到 3D 模型上。
- 合并几何体:将多个几何体合并成一个可以减少渲染调用次数,从而提高性能。可以使用 Three.js 自带的 MergeGeometry 和 BufferGeometryUtils 工具类来实现。
- 使用 LOD(Level of Detail):使用 LOD 技术可以根据距离远近来切换不同的模型细节级别,从而提高性能。
- 使用 GLTFpack 实现快速压缩和优化 GLTF 文件,提高加载速度。
本文先简单介绍了三种性能监测工具:stats.js \ r3f-perf \spector.js,再针对运用原生three.js和react-three/fiber加载三维模型提供了多种优化性能的方案,其中,主要探讨了使用 gltf-pipeline 压缩文件和
使用 instanceMesh 减少 draw call的原理、实践方法、优缺点及使用场景。
在实践中,注意要根据项目特性选择适合的优化方案,多进行性能对比再使用~
(由于本人刚入three.js的坑,对于3D技术实践较少,如有错误,请告知,谢谢啦~)