经纬志
首页 / 实战手册 / three.js 渲染

InstancedMesh 摆了五千棵树,全歪了

先看结论

用实例化渲染种树,位置大致对,但每棵树的旋转和缩放完全不受控制,还有一半根本不出现。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

InstancedMesh 创建成功,count 为 5000, 画面里能看到树,但朝向随机、大小不一,与设置的每棵参数无关。 相机移动到某片区域时整片树突然消失,移回来又出现。

原因

实例变换存在 instanceMatrix 属性里,修改后必须置 instanceMatrix.needsUpdate = true,否则 GPU 端仍是初始的单位矩阵。

第二个原因是矩阵构造顺序。Matrix4.compose(position, quaternion, scale) 要求传入四元数而不是欧拉角,传错类型不会报错,会得到一个乱掉的变换。

第三个原因是包围球。实例化网格的包围球默认只覆盖基础几何, 场景中的实例分布在上百米范围时,整体包围球过小,视锥裁剪会误判为不可见。

排查过程

把实例数量临时改成 2,逐个打印矩阵的平移分量,确认写入是否生效:

var m = new THREE.Matrix4();
mesh.getMatrixAt(0, m);
console.log('第 0 个实例平移', new THREE.Vector3().setFromMatrixPosition(m));

解决方案

用 Matrix4.compose 构造,位置、四元数、缩放分开给, 并在最后统一更新标志位:

var treeGeo = new THREE.ConeGeometry(0.6, 3, 6);
var treeMat = new THREE.MeshStandardMaterial({ color: 0x2e7d32 });
var count = 5000;

var trees = new THREE.InstancedMesh(treeGeo, treeMat, count);
var dummy = new THREE.Object3D();

for (var i = 0; i < count; i++) {
  dummy.position.set(
    (Math.random() - 0.5) * 800,
    0,
    (Math.random() - 0.5) * 800
  );
  dummy.rotation.y = Math.random() * Math.PI * 2;
  dummy.scale.set(1, 0.8 + Math.random() * 0.6, 1);
  dummy.updateMatrix();

  trees.setMatrixAt(i, dummy.matrix);
}

trees.instanceMatrix.needsUpdate = true;
trees.computeBoundingSphere();      // 关键:按实例分布重算
scene.add(trees);

需要按实例单独着色时,用 setColorAt,同样要置更新标志:

var color = new THREE.Color();
for (var j = 0; j < count; j++) {
  color.setHSL(0.28, 0.5, 0.35 + Math.random() * 0.2);
  trees.setColorAt(j, color);
}
trees.instanceColor.needsUpdate = true;

实例数量会变化(例如按视野动态增删)时,不要重建网格, 直接改 count 并同步包围球:

trees.count = visibleCount;
trees.instanceMatrix.needsUpdate = true;
trees.computeBoundingSphere();

验证

随机采样 10 个实例,读出矩阵平移分量,与写入的坐标一致。 把相机拉到 1 公里外再回来,整片树始终可见,不会因裁剪消失。 renderer.info.render.calls 为 1,说明确实只发了一次绘制。

易踩的坑:复用同一个 Object3D 做 dummy 时, 每轮都要先重置 rotation 与 scale。 只在第一轮设了,后面所有实例都会继承上一次的值。

引用本文

经纬志(2026)。《InstancedMesh 摆了五千棵树,全歪了》。https://www.jingweizhi.com/cookbook/three-instanced-transform.html(访问日期 2026-11-03)。