ドローコールがパフォーマンスのボトルネックになる
Three.js で 3D シーンを構築していると、オブジェクトの数が増えるにつれて FPS が急激に低下する場面に出くわします。原因の多くは、メッシュごとに発生するドローコールの増加です。
CPU から GPU への描画命令にはひとつひとつにオーバーヘッドがあるため、シーンに数百・数千のメッシュを配置すると、それだけで描画処理が重くなってしまいます。
同じ形状を繰り返し配置する場合は InstancedMesh でドローコールをまとめられますが、形状の異なるメッシュを複数描画したい場合には使えません。
そこで登場するのが、Three.js r159 で追加された BatchedMesh です。本記事では、BatchedMesh の基本的な使い方と InstancedMesh との違いを、サンプルコードとともに解説します。
InstancedMesh と BatchedMesh の違い
両者はどちらもドローコールを削減するための仕組みですが、扱えるジオメトリの種類が異なります。
- InstancedMesh は「同じ形状」を大量に複製するためのクラスです。草や弾丸のように、ジオメトリとマテリアルが共通のオブジェクトを位置や色だけ変えて並べる用途に適しています。
- BatchedMesh は「異なる形状」をまとめて 1 ドローコールに集約するためのクラスです。ユニークな建物や瓦礫など、ジオメトリが個別に異なるオブジェクトを大量に描画したい場面で威力を発揮します。
InstancedMesh で扱えるのはあくまで単一のジオメトリの複製なので、形状ごとに別々のメッシュを作るしかありませんでした。BatchedMesh を使うことで、その制限から解放されます。
BatchedMesh の基本的な使い方
BatchedMesh は、事前にインスタンス数・頂点数・インデックス数の上限を指定して生成し、addGeometry() でジオメトリを登録した後、addInstance() でインスタンスを追加するという流れで利用します。
サンプルコード
import * as THREE from "three";
// 最大インスタンス数・最大頂点数・最大インデックス数を指定して生成
const maxInstanceCount = 500;
const maxVertexCount = 10000;
const maxIndexCount = 30000;
const batchedMesh = new THREE.BatchedMesh(
maxInstanceCount,
maxVertexCount,
maxIndexCount,
new THREE.MeshStandardMaterial({ color: 0xffffff })
);
// 異なるジオメトリを登録
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const sphereGeometry = new THREE.SphereGeometry(0.5, 16, 16);
const coneGeometry = new THREE.ConeGeometry(0.5, 1, 16);
const boxId = batchedMesh.addGeometry(boxGeometry);
const sphereId = batchedMesh.addGeometry(sphereGeometry);
const coneId = batchedMesh.addGeometry(coneGeometry);
// インスタンスを追加して位置を設定
const matrix = new THREE.Matrix4();
const geometryIds = [boxId, sphereId, coneId];
for (let i = 0; i < 100; i++) {
const instanceId = batchedMesh.addInstance(geometryIds[i % geometryIds.length]);
matrix.setPosition(
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20,
(Math.random() - 0.5) * 20
);
batchedMesh.setMatrixAt(instanceId, matrix);
}
scene.add(batchedMesh);
このコードでは、ボックス・球・コーンという 3 種類のジオメトリを BatchedMesh に登録し、ランダムな位置に 100 個のインスタンスを配置しています。内部的にはこれらすべてが 1 回のドローコールで描画されるため、同数のメッシュを個別に生成した場合と比べて大幅に高速化されます。
インスタンスごとに色を変える
BatchedMesh では、setColorAt() を使うことでインスタンスごとに色を設定できます。複数のマテリアルを用意せずに色のバリエーションを表現できるため、パフォーマンスを損なわずに多彩な見た目を作れます。
const color = new THREE.Color();
color.setHSL(Math.random(), 0.7, 0.5);
batchedMesh.setColorAt(instanceId, color);
使用時の注意点
-
事前確保サイズの見積もりが必要
BatchedMesh はコンストラクタ呼び出しの時点で最大頂点数・最大インデックス数・最大インスタンス数をまとめて確保します。実際の使用量よりも余裕を持った値を指定しないと、途中でジオメトリやインスタンスを追加できなくなります。 -
マテリアルは 1 つだけ
BatchedMesh はドローコールを 1 つに集約する仕組み上、扱えるマテリアルは 1 つだけです。インスタンスごとに異なるテクスチャを貼りたい場合は、テクスチャアトラスで 1 枚のテクスチャにまとめて UV でずらす方法が定番です。 -
カリング単位の制約
視錐台カリング自体はインスタンスごとに行われますが、ドローコールはまとめて発行されるため、画面外のインスタンスだけを完全に省略することはできません。配置範囲が広い場合は、地域ごとに BatchedMesh を分割するといった工夫も検討してください。
まとめ
BatchedMesh は、形状の異なる多数のメッシュを 1 ドローコールで描画できる強力な仕組みです。InstancedMesh では扱えなかった「個別に異なる形状の大量配置」というケースで、描画パフォーマンスを大きく改善してくれます。
瓦礫や地形タイル、ランダム生成の小物など、ユニークな形状のオブジェクトをたくさん表示したい場面があれば、ぜひ BatchedMesh の導入を検討してみてください。
コメント