上一篇我们提到过,Three.js模型卡顿,很多时候都可以先从Draco压缩和LOD分级入手,用来解决模型加载慢、首屏卡的问题。
但在实际项目里,还有一种更加常见的情况:模型已经加载出来了,场景也正常显示了,可你一操作镜头就开始掉帧,特别是物体一多,卡顿就更加明显。这个时候,问题往往不是出在加载上,而是运行时的渲染压力太大了。
我自己排查这类问题时,通常会重点看两个方向:实例化渲染和合并几何体。
实例化渲染:重复模型多的时候很有效
当你的场景里有大量重复物体,比如树、路灯、椅子、设备、零件这类,就适合用实例化渲染来做轻量化。
因为这些模型虽然数量很多,但实际上可能只是同一个模型重复摆放了很多次。要是还按普通方式一个个渲染,drawcall分分钟上去,所以场景一复杂就掉帧。
实例化的核心思路,就是让这些重复物体共用同一份几何体和材质,再批量渲染。这样能明显减少渲染提交次数,对运行时性能帮助很直接。
通常来说,使用InstancedMesh可将 Draw Call 从 O (n) 降至 O (1),性能提升 10 倍以上。
javascript
import { InstancedMesh, Matrix4 } from 'three';
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial();
const count = 1000; // 1000个立方体
const instancedMesh = new InstancedMesh(geometry, material, count);
const matrix = new Matrix4();
for (let i = 0; i < count; i++) {
// 设置每个实例的位置、旋转、缩放
matrix.setPosition(Math.random()*100, Math.random()*100, Math.random()*100);
instancedMesh.setMatrixAt(i, matrix);
}
scene.add(instancedMesh);
合并几何体:碎模型太多时更有用
还有一种情况是,场景里虽然没有特别多重复的模型,但小物体特别多、特别碎。就好像建筑构件、工业零件、装饰面片之类,单个不复杂,但数量一大,Three.js照样吃不消。
这时候就得用合并几何体了。它的思路也很简单,就是把原本分散的小几何体尽量合并为一个几何体,大幅减少减少场景中的节点数和drawcall,把渲染压力降下来。
javascript
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
const geometries = [];
for (let i = 0; i < 100; i++) {
const geometry = new THREE.BoxGeometry();
geometry.translate(Math.random()*10, Math.random()*10, Math.random()*10);
geometries.push(geometry);
}
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);
// Draw Call从100降为1
为什么我后来会用轻装3D
要是模型比较少,手动做实例化或者合并其实也能做。但项目一大,你就根本不会想要手动了。
比如哪些模型适合实例化、哪些适合合并、模型改版后要不要重新处理,这些不花费大量的时间根本搞不定。而且很多优化不是前端代码层临时补一补就能彻底解决的,问题根源其实在模型资产本身。
我用轻装3D最直观的感受是,它一键就能把几何体实例化和几何体合并这两步做掉。也就是说,在模型进Three.js之前,就已经把那些容易造成运行时卡顿的问题处理掉了。

通过对重复模型的实例化减少重复渲染压力;通过合并零散小物体降低场景绘制负担。这样用轻装3D处理完的资源就是更适合实时渲染的状态,场景跑起来会轻很多。
小结
如果说Draco和LOD解决的是"模型进来太重"的问题,那实例化和合并几何体解决的就是"场景跑起来太累"的问题。对模型多、场景复杂的项目来说,直接用轻装3D会省事很多,优化效果也更稳定。