一千二百个实例:把花园压进几次绘制
草叶六百、花一百二、苔石八十五、灌木四十二 —— 花园的密度不靠减数量,靠 InstancedMesh 把同一种东西合并成一次绘制。
岛上的花园大约一千二百个物体。如果每个都建一个 Mesh,光这一片就是一千二百次绘制调用;实际只用了四五次。
同一种东西,一个 InstancedMesh
草叶是圆锥、花茎是圆柱、苔石是二十面体。每一种只建一个几何体、一个材质、一个 InstancedMesh,然后把每个实例的位置、缩放、旋转写进矩阵:
const dummy = new THREE.Object3D();
transforms.forEach((item, index) => {
dummy.position.set(...item.position);
dummy.scale.set(...(item.scale ?? [1, 1, 1]));
dummy.rotation.set(...(item.rotation ?? [0, 0, 0]));
dummy.updateMatrix();
object.setMatrixAt(index, dummy.matrix);
if (item.color) object.setColorAt(index, new THREE.Color(item.color));
});
setColorAt 是这里的关键。草有三种绿、花头有三种颜色、苔石有三种灰 —— 如果颜色写死在材质里,就得为每种颜色各建一个 InstancedMesh,实例化带来的收益立刻减半。
位置也要挑
不是随机撒满整个圆盘就行。生成草的时候要排除三类地方:水洼(椭圆区域)、溪流(矩形区域)、亭子脚下的台基。排除条件写成一串布尔判断,读起来不优雅,但比事后修补快得多:
if (x * x + z * z > 16.7) continue;
if (((x - 0.48) ** 2) / 2.7 + ((z - 1.26) ** 2) / 1.5 < 1) continue;
if (x > 1 && x < 2.6 && z > 1.5) continue;
什么时候不该用实例化
花瓣没有走实例化 —— 它需要每帧改矩阵:下落、旋转、循环重生。用 InstancedMesh 依然可以(改完置 instanceMatrix.needsUpdate = true),但萤火虫那种只做缓慢漂移的,用 Points 更省。
判据很简单:每帧都要重写全部矩阵的,优先用 Points 或 Sprite;只写一次的,用 InstancedMesh。
阴影也要算进去
实例化会一次性投影,实例多的时候阴影渲染同样变重。这里的做法是让整个场景的阴影贴图只算一次,于是实例数量的增长不再影响每帧成本。
收尾
实例化的收益不是「快一点」,而是把密度从成本问题变成设计问题:能不能种六百根草,从此只取决于你想不想,而不是渲染器答不答应。

评论
…