瓶颈常常不是三角形,而是绘制调用
每个普通 Mesh 渲染时,CPU 都要向 GPU 发出一次绘制调用(draw call):切换着色器、上传矩阵和材质参数、提交命令。几千次调用之后,CPU 就成了瓶颈,哪怕每个物体只有 12 个三角形。
InstancedMesh:一份几何体,画 N 次
如果许多物体共享同一个几何体和同一个材质,只是位置、旋转、缩放、颜色不同,就可以用 InstancedMesh:
js
const mesh = new THREE.InstancedMesh(geometry, material, count);
dummy.position.set(x, y, z);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
mesh.instanceMatrix.needsUpdate = true;GPU 收到一次绘制调用,外加一个存放所有实例矩阵的缓冲区(instanceMatrix),着色器根据 gl_InstanceID 取出各自的矩阵。
每个实例的数据
setMatrixAt(i, matrix):写入第 i 个实例的变换。常见做法是用一个不加入场景的Object3D(dummy)设置位置旋转缩放,再调用updateMatrix()取它的矩阵。setColorAt(i, color):每个实例一种颜色,会和材质颜色相乘。- 修改后要设置
instanceMatrix.needsUpdate/instanceColor.needsUpdate。每帧都改的话,用setUsage(THREE.DynamicDrawUsage)。
读懂 renderer.info
renderer.info.render.calls 是上一次 render() 的绘制调用数,triangles 是三角形数。状态栏实时显示它们:instanced 模式下 calls 只有 1,而 separate meshes 模式下(上限 4000 个)calls 等于物体数量,CPU 时间明显更高——即使画的三角形更少。
注意事项
- 实例共享材质,不能各自用不同的材质类型或贴图(颜色、矩阵之外的差异需要自定义属性)。
- 视锥剔除以整个 InstancedMesh 为单位。实例在运动时包围球会过期,示例直接设置
frustumCulled = false;也可以调用computeBoundingSphere()。 - 每帧在 JS 里更新几万个矩阵本身也有成本;真正海量的动画可以把运动挪到顶点着色器里计算。
把 count 拉到 100000,再切换两种模式,比较状态栏里的 CPU 毫秒数。
核心 API
THREE.InstancedMeshinstancedMesh.setMatrixAtinstancedMesh.setColorAtinstanceMatrix.needsUpdateTHREE.DynamicDrawUsageObject3D.updateMatrixrenderer.info.render动手试试
- 1把 BoxGeometry 换成 IcosahedronGeometry(1, 1),观察三角形数量和帧率的变化。
- 2把 count 设为 4000,比较两种模式下 draw calls 和 CPU 时间。
- 3在循环里也调用 setColorAt,让颜色随波浪高度变化(记得设置 instanceColor.needsUpdate)。
- 4删掉 instanceMatrix.needsUpdate = true 这一行,看看画面会怎样。
还没有输出。console.log 的内容会显示在这里。
运行中