一千二百个实例:把花园压进几次绘制

草叶六百、花一百二、苔石八十五、灌木四十二 —— 花园的密度不靠减数量,靠 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。

阴影也要算进去

实例化会一次性投影,实例多的时候阴影渲染同样变重。这里的做法是让整个场景的阴影贴图只算一次,于是实例数量的增长不再影响每帧成本。

收尾

实例化的收益不是「快一点」,而是把密度从成本问题变成设计问题:能不能种六百根草,从此只取决于你想不想,而不是渲染器答不答应。

← 返回文章列表

评论

…