跳到主要内容
3D Playground
Three.js 核心第 9 / 13 节

实例化渲染

进阶WebGL

瓶颈常常不是三角形,而是绘制调用

每个普通 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. 1把 BoxGeometry 换成 IcosahedronGeometry(1, 1),观察三角形数量和帧率的变化。
  2. 2把 count 设为 4000,比较两种模式下 draw calls 和 CPU 时间。
  3. 3在循环里也调用 setColorAt,让颜色随波浪高度变化(记得设置 instanceColor.needsUpdate)。
  4. 4删掉 instanceMatrix.needsUpdate = true 这一行,看看画面会怎样。
运行中