Open20

Babylon.jsのインスタンシングについて知りたい

kiyukakiyuka

インスタンシングは、あらかじめ描画するためのインスタンスを作成しておくことで、一回の描画処理で複数のモデルを描画できるから速く描画できるよ!みたいなことをしているらしい。

https://wgld.org/d/webgl/w075.html

とりあえず、スプレッドシートでセルを一つ一つコピペするよりも、複数選択して一発貼り付けしたほうが速いよね?という雑な理解をしておけばいいかな。

kiyukakiyuka

インスタンスで色を変えるには、カスタムバッファを使うっぽい。

事前に色のデータを作っておいて、メッシュのバッファに設定しておく。

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);
kiyukakiyuka

カスタムバッファのもう一つの方法。こっちだとインスタンスごとにバッファ設定するから、あとから変更が可能っぽい。

以下は、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();
kiyukakiyuka

試しに

  • instanceCount = 100000 にして、
  • instance.alwaysSelectAsActiveMesh = true;scene.freezeActiveMeshes();
    • ありの場合と
    • なしの場合でfps見てみたら、

ありだと30fpsだったのが、なしにすると15fpsになった。
ありのほうが2倍くらい速い。

(だから Custom Buffers Example 1 のほうは scene.freezeActiveMeshes(); を忘れているんじゃないかなーとか思ったり)

kiyukakiyuka

world matrix instanced buffer というものもあって、カスタムすればいろいろできるらしい?
よくわかってないけど、サンプルだとマウス位置でインスタンスの位置が変わる。

https://playground.babylonjs.com/#HJGC2G

kiyukakiyuka

パフォーマンス気にするなら、このあたりを気にする必要あり?サンプル見るとこれらの設定があったりする。

instance.alwaysSelectAsActiveMesh = true;
instance.freezeWorldMatrix();
instance.isPickable = false;
instance.material.freeze();

scene.freezeActiveMeshes();
kiyukakiyuka

Thin Instances というものもあるらしい。あとから変更しない、かつ、あらかじめインスタンスの個数がわかっているならこっちの方がパフォーマンス的に良い?

kiyukakiyuka

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);
kiyukakiyuka

一番大きな違いはこれかな?

通常のインスタンスだと、別々のメッシュとして扱われているけど、

Thin Instances だと一つのメッシュ扱いになっているっぽい

kiyukakiyuka

一つのメッシュっぽいけど、個別のインスタンス情報自体は持っているから、マウス選択でどのインスタンがクリックされたかの情報は取れるよう。

Thin Instances Picking Exampleのサンプルでは、インスタンスを選択しているように見せている。

  • Thin Instances 自体の更新はできない(たぶん)
  • その代わりに選択したインスタンスの上に別のオブジェクトを配置
  • 選択したインスタンスの色が変わっているように見える