Babylon.jsのインスタンシングについて知りたい
いっぱいオブジェクト配置したい。
インスタンシングは、あらかじめ描画するためのインスタンスを作成しておくことで、一回の描画処理で複数のモデルを描画できるから速く描画できるよ!みたいなことをしているらしい。
とりあえず、スプレッドシートでセルを一つ一つコピペするよりも、複数選択して一発貼り付けしたほうが速いよね?という雑な理解をしておけばいいかな。
めちゃ丁寧な記事見つけた
コピーとインスタンス
// コピー
clonedHouse = house.clone("clonedHouse")
// インスタンス
instanceHouse = house.createInstance("instanceHouse")
Solid Particle System(ソリッドパーティクルシステム)っていうのもあるらしい
単純にインスタンスを作った場合、インスタンスごとに変えられるのは、位置、回転、スケール だけっぽい?
色を変える場合はカスタムバッファを使用するらしい。なんかいろいろやり方ありそう。
最初のサンプルの Using Node Material with Instances で何やっているのかと思ったら、Loading from a snippet っていうのを使って色設定しているらしい。
ParseFromSnippetAsync("DGN9E2#1")
の部分でノードマテリアルエディタの内容を読み込んでいるそうな。
ノードマテリアルエディタは便利そうだけど、まだよくわかっていないのでひとまずスルーしよう。
このあたりも見ておいたほうが良さそう。たぶんオブジェクトを更新しないなら計算処理を削減する、みたいなのがあると思う。
サンプルの alwaysSelectAsActiveMesh = true;
← これなんだろうと思ったら、カメラを移動させても正常に?表示させるために必要らしい?
インスタンスで色を変えるには、カスタムバッファを使うっぽい。
事前に色のデータを作っておいて、メッシュのバッファに設定しておく。
let instanceCount = 1000;
// 事前に色のデータを作成
let colorData = new Float32Array(4 * instanceCount);
for (let index = 0; index < instanceCount; index++) {
colorData[index * 4] = Math.random();
colorData[index * 4 + 1] = Math.random();
colorData[index * 4 + 2] = Math.random();
colorData[index * 4 + 3] = 1.0;
}
// バッファに設定
var buffer = new BABYLON.VertexBuffer(engine, colorData, BABYLON.VertexBuffer.ColorKind, false, false, 4, true);
box.setVerticesBuffer(buffer);
カスタムバッファのもう一つの方法。こっちだとインスタンスごとにバッファ設定するから、あとから変更が可能っぽい。
以下は、Custom Buffers Example 2 をちょっと変更したもの。
// 色のカスタムバッファを設定できるように
box.registerInstancedBuffer("color", 4);
box.isVisible = false;
// インスタンス作成
let baseColors = [];
for (var index = 0; index < instanceCount; index++) {
let instance = box.createInstance("box" + index);
instance.position.x = 20 - Math.random() * 40;
instance.position.y = 20 - Math.random() * 40;
instance.position.z = 20 - Math.random() * 40;
instance.alwaysSelectAsActiveMesh = true;
// 色設定
const color = new BABYLON.Color4(Math.random(), Math.random(), Math.random(), 1)
baseColors.push(color);
instance.instancedBuffers.color = color.clone();
}
// 色更新
let alpha = Math.random();
scene.registerBeforeRender(() => {
alpha += 0.01
for (var instanceIndex = 0; instanceIndex < box.instances.length; instanceIndex++) {
let cos = Math.abs(Math.cos(alpha + instanceIndex / box.instances.length));
var instance = box.instances[instanceIndex];
// ベースカラー * cos の値にインスタンスの色を更新
baseColors[instanceIndex].scaleToRef(cos, instance.instancedBuffers.color);
}
});
scene.freezeActiveMeshes();
scene.freezeActiveMeshes();
はよくわかってないけど、処理速度が速くなる?みたいな設定?
この設定をするのであれば instance.alwaysSelectAsActiveMesh = true;
も一緒にする必要があるよう。
試しに
-
instanceCount = 100000
にして、 -
instance.alwaysSelectAsActiveMesh = true;
とscene.freezeActiveMeshes();
を- ありの場合と
- なしの場合でfps見てみたら、
ありだと30fpsだったのが、なしにすると15fpsになった。
ありのほうが2倍くらい速い。
(だから Custom Buffers Example 1 のほうは scene.freezeActiveMeshes();
を忘れているんじゃないかなーとか思ったり)
world matrix instanced buffer というものもあって、カスタムすればいろいろできるらしい?
よくわかってないけど、サンプルだとマウス位置でインスタンスの位置が変わる。
パフォーマンス気にするなら、このあたりを気にする必要あり?サンプル見るとこれらの設定があったりする。
instance.alwaysSelectAsActiveMesh = true;
instance.freezeWorldMatrix();
instance.isPickable = false;
instance.material.freeze();
scene.freezeActiveMeshes();
Thin Instances というものもあるらしい。あとから変更しない、かつ、あらかじめインスタンスの個数がわかっているならこっちの方がパフォーマンス的に良い?
Thin Instances Example を見る限り、バイナリデータ的に変換行列と色のデータを作ってバッファに設定するとできるみたい。
// 変換行列と色のバイナリデータっぽいもの
let matricesData = new Float32Array(16 * instanceCount);
let colorData = new Float32Array(4 * instanceCount);
// データの設定
//(省略)
// バッファに設定
box.thinInstanceSetBuffer("matrix", matricesData, 16);
box.thinInstanceSetBuffer("color", colorData, 4);
一番大きな違いはこれかな?
通常のインスタンスだと、別々のメッシュとして扱われているけど、
Thin Instances だと一つのメッシュ扱いになっているっぽい
一つのメッシュっぽいけど、個別のインスタンス情報自体は持っているから、マウス選択でどのインスタンがクリックされたかの情報は取れるよう。
Thin Instances Picking Exampleのサンプルでは、インスタンスを選択しているように見せている。
- Thin Instances 自体の更新はできない(たぶん)
- その代わりに選択したインスタンスの上に別のオブジェクトを配置
- 選択したインスタンスの色が変わっているように見える
いや、Thin instances previous matrices motion blur のサンプルだとThin instancesの更新してるな?previousMatrix
が何かのトリックなのかな?