实例化渲染
两千多栋楼共用一份几何体,一个绘制调用画完,与逐个 Mesh 的写法并排对比。
说明
实例化的核心是「一份几何、多次变换」:InstancedMesh
在 GPU 上保存一张实例矩阵表,绘制时用内置变量取出第 N 个矩阵,
把同一个立方体画到不同位置、不同高度。楼体数量从 400 调到 4000,
绘制调用始终是 1 次。
切换按钮把同样的布局换成逐个 Mesh,
此时每栋楼都要单独提交一次状态与绘制命令,帧率会显著下降。
这个对比是选择实例化与否最直接的判断依据。
楼体位置完全由代码生成,取黄金角散点加平方根半径,避免随机分布带来的空洞与重叠。 同样的思路可以把建筑底面中心与高度写进实例矩阵,用于批量表达体量数据。
| 设置 | 数值 | 说明 |
|---|---|---|
| 实例数量 | 400 – 4000 | 滑杆调整,切换时会重建实例表 |
| 几何体 | 1.4 × 1 × 1.4 立方体 | 原点平移到楼底,按高度缩放不会陷地 |
| 逐实例颜色 | setColorAt | 按高度映射色相与明度 |
| 材质 | Standard,粗糙度 0.72 | 所有实例共享,改一次全部生效 |
| 视锥裁剪 | 关闭 | 实例分布过广,整体包围球判定会误裁 |
| 对比项 | draw call 与 FPS | 工具栏实时显示,每半秒刷新 |
实例矩阵怎么写
常用做法是借一个 Object3D 当草稿:摆好位置、旋转、缩放,
调用 updateMatrix() 后把矩阵抄进实例表。
它不参与渲染,只是帮你把 TRS 合成矩阵。
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(x, 0, z);
dummy.rotation.set(0, yaw, 0);
dummy.scale.set(1, height, 1);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
mesh.instanceMatrix.needsUpdate = true;
setMatrixAt 只写缓冲,不会通知 GPU。
写入完成后必须把 instanceMatrix.needsUpdate 置为 true,
否则画面不会变化。若每帧都改,建议把用法标为 DynamicDrawUsage。
逐实例颜色与拾取
setColorAt 会创建 instanceColor 属性,
材质不需要开启 vertexColors,渲染器会自动识别。
修改已有颜色后同样要置 instanceColor.needsUpdate。
拾取时 Raycaster 返回的命中结果里带有
instanceId,据此可以回查是哪一栋楼、对应哪条记录。
这也是实例化能保留交互能力的原因——代价是每个实例不能有独立的几何形状。
什么时候不适合实例化
每个对象几何形状不同、材质参数差异很大、需要单独控制渲染顺序或独立透明时,
实例化会把这些问题集中到一次绘制里,反而更难处理。
这类场景更适合合并几何体(BufferGeometryUtils.mergeGeometries)
或按材质分组。
three.js 只负责画,不负责数据。实例表里只有矩阵与颜色, 没有属性表、没有要素 ID,也没有坐标系定义。 楼高、层数、名称这些信息要另存一份数组,用实例序号对应。