MMD WebAssembly ランタイム
このセクションでは、WebAssembly (WASM) で実装されたMMDランタイムの使用方法について説明します。
babylon-mmdはWASMで実装されたMMDランタイムを提供します。
この図は、babylon-mmdのWebAssemblyランタイムのアーキテクチャを示しています。
このWASMランタイムは、元のMmdRuntimeクラスのJavaScriptインプリメンテーションをRustで完全に書き直し、WASMにコンパイルしたものです。
WASMランタイムは、様々な最適化テクニックを適用することで、JavaScriptランタイムよりも優れたパフォーマンスを提供します。
適用されている最適化テクニックは以下の通りです:
- IKソルバー以外のすべてのオペレーションをfloat32で処理。
- 128ビットSIMDインストラクションを使用してベクターオペレーションを並列処理。
- ワーカーベースのマルチスレッディングを採用し、モデルごとに並列処理を実行。
- Bullet PhysicsエンジンをFFIでバインドし、物理シミュレーションを処理(emscriptenを使用せず)。
MmdWasmInstance
WASMランタイムを使用するには、まずbabylon-mmdが提供するWASMバイナリをロードする必要があります。これはgetMmdWasmInstance()ファンクションを使用して行うことができます。
const mmdWasmInstance = await getMmdWasmInstance(new MmdWasmInstanceTypeSPR());
getMmdWasmInstance()ファンクションは非同期でWASMバイナリをロードし、WASMモジュールインスタンスを返します。
babylon-mmdでは、バイナリを選択する際に3つのオプションを提供します:
- シングルスレッドまたはマルチスレッド:S / M
- Bullet Physics含む、または含まない:P / (なし)
- リリースビルドまたはデバッグビルド:R / D
そのため、8つのWASMインスタンスタイプのうち1つを選択できます:
MmdWasmInstanceTypeSR:シングルスレッド、リリースビルドMmdWasmInstanceTypeSD:シングルスレッド、デバッグビルドMmdWasmInstanceTypeMR:マルチスレッド、リリースビルドMmdWasmInstanceTypeMD:マルチスレッド、デバッグビルドMmdWasmInstanceTypeSPR:シングルスレッド、フィジックス、リリースビルドMmdWasmInstanceTypeSPD:シングルスレッド、フィジックス、デバッグビルドMmdWasmInstanceTypeMPR:マルチスレッド、フィジックス、リリースビルドMmdWasmInstanceTypeMPD:マルチスレッド、フィジックス、デバッグビルド
使用シナリオに適したバイナリを選択できます。
理論的には、最高のパフォーマンスを持つバイナリはMmdWasmInstanceTypeMPR(マルチスレッド、フィジックス、リリースビルド)です。
ただし、SharedArrayBufferをサポートしていない環境では、マルチスレッディングが動作しないため、シングルスレッドバージョンを使用する必要があります。
物理シミュレーションが不要な場合は、フィジックスエンジンなしのバイナリを選択して、ロード時間を短縮できます。
また、開発時には、ランタイム内で発生するエラーを追跡するためにデバッグビルドの使用が推奨されます。リリースビルドでは、パニックが発生した際のエラー診断が困難です。
フィジックスエンジンなしのバイナリを選択した場合でも、MmdPhysics、MmdAmmoPhysics、またはMmdBulletPhysicsクラスを使用して物理シミュレーションを処理することは可能です。ただし、フィジックスエンジンが含まれたバイナリを使用する場合と比較して、パフォーマンスが劣る可能性があります。
MmdWasmRuntimeクラス
MmdWasmRuntimeクラスは、WASMで実装されたMMDランタイムクラスで、MmdRuntimeクラスとほぼ同じAPIを提供します。
使用するには、単純に元のMmdRuntimeクラスの代わりにMmdWasmRuntimeクラスを使用し、MmdWasmInstanceをコンストラクタに渡すだけです。
const mmdWasmRuntime = new MmdWasmRuntime(mmdWasmInstance, scene);
そうすると、タイプが自動的に伝播され、createMmdModelファンクションの戻り値のタイプもMmdWasmModelタイプになります。
MmdWasmAnimationクラス
WASMランタイムでデータを処理するには、データをWASMメモリースペースにコピーする必要があります。
しかし、MmdAnimationコンテナはJavaScript側のArrayBufferインスタンスにデータを格納します。
そのため、MmdAnimationに格納されたアニメーションデータは、WASM側で評価することができません。この場合、アニメーション評価はJavaScript側で処理され、その後IKソルブ、アペンドトランスフォーム、ボーンモーフ、物理シミュレーションがWASM側で処理されます。
この図は、MmdAnimationデータがWASMメモリースペースにコピーされていない場合のアニメーション評価の処理方法を示しています。
babylon-mmdでは、アニメーションデータのコピーをWASMメモリースペースにサポートするため、MmdWasmAnimationクラスを提供します。これにより、アニメーション評価を含むほぼすべてのアニメーション計算がWASM側で処理されます。
この図は、MmdWasmAnimationデータがWASMメモリースペースにコピーされた場合のアニメーション評価の処理方法を示しています。
これを行うには、単純にMmdWasmAnimationインスタンスを作成し、MMDモデルにバインドします。
const mmdWasmAnimation = new MmdWasmAnimation(mmdAnimation, mmdWasmInstance, scene);
const runtimeAnimationHandle = mmdWasmModel.createRuntimeAnimation(mmdWasmAnimation);
mmdWasmModel.setRuntimeAnimation(runtimeAnimationHandle);
この方法で、アニメーション評価がWASM側で処理され、可能なすべてのアニメーション計算がWASM側で処理されることが保証されます。
MmdAnimationとは異なり、MmdWasmAnimationでは手動でのメモリー解放が必要です。
不要になった場合は、MmdWasmAnimation.dispose()メソッドを呼び出してメモリーを解放してください。
バッファード評価
WASMランタイムはバッファード評価をサポートしており、この機能は、マルチスレッディングランタイム(例:MR、MPD)を使用する際に、レンダリングとは別のスレッドでアニメーション計算を処理します。
この機能はデフォルトで無効になっています。有効にするには、MmdWasmRuntime.evaluationTypeプロパティをMmdWasmRuntimeAnimationEvaluationType.Bufferedに設定します。
mmdWasmRuntime.evaluationType = MmdWasmRuntimeAnimationEvaluationType.Buffered;
バッファード評価が有効になると、アニメーション計算は1フレーム遅延で処理され、レンダリングスレッドは前のフレームから計算された結果を使用します。これは、レンダリングスレッドがアニメーション計算を待つことなく、すぐにレンダリングを実行できるようにするパイプライン技術の一種です。
以下は、バッファード評価とイミディエート評価の違いを示すイメージです:
この図は、バッファード評価とイミディエート評価の違いを示しています。
制限事項
WebAssemblyにコンパイルされたコードは、JavaScriptコードとは異なり、プロトタイプを変更したり、継承を通じて動作を変更することができません。
したがって、高レベルのカスタマイゼーションが必要な場合は、WebAssemblyランタイムではなく、JavaScriptランタイムの使用が推奨されます。
詳細情報
Enhancing Browser Physics Simulations: WebAssembly and Multithreading Strategies
この論文では、babylon-mmdのWebAssemblyランタイムの最適化に適用された様々な技術について説明しており、このページで使用されている画像の一部も、この論文から抜粋されています。
この論文では、使用された最適化技術についての詳細な説明と、その結果としてどの程度のパフォーマンス向上が達成されたかについて説明されています。