InstancedMesh 摆了五千棵树,全歪了
用实例化渲染种树,位置大致对,但每棵树的旋转和缩放完全不受控制,还有一半根本不出现。
现象
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)。