Oculus での自動インスタンス化

UE4 で Oculus Quest での自動インスタンス化をデバッグ

Choose your operating system:

Windows

macOS

Linux

ドローコール とは、オブジェクトを描画するための RHI コマンドです。自動インスタンス化 は、複数のドローコールを単一のインスタンス化されたドローコールに自動的にまとめる機能です。インスタンス化されたドロー コール は、さまざまな属性を持つ類似したオブジェクトの複数のインスタンスをグラフィック API で描画 (ドロー) する方法です。これらの属性は、位置、向き、色など、メッシュのレンダリングに関連するものになります。

複数のドローコールを一つにまとめることで、送信したグラフィック API ドローコールでの CPU オーバーヘッドが削減されます。すべてのドローコールを一つにまとめることも可能ですが、すべてのドローコールで頂点バッファ、ユニフォーム バッファ、シェーダ、ラスタライズ モード、その他多数のグラフィック API の状態に互換性がある必要があります。

互換性の要件は、エンジンがドローコールに適用する制約に応じてエンジンごとに異なります。

自動インスタンス化によるドローコールの結合が失敗する原因としては、明示的でない互換性の要件に違反している場合がほとんどです。Unreal での自動インスタンス化の仕組みを理解することで、デバッグを適切に行い、新たな要件を発見しやすくなります。

以下の CVar が有効になっていることを確認してください。

  • r.Mobile.SupportGPUScene=1 すべての Android デバイスがコンピューティング シェーダに対応しているわけではないため、.ini ファイル内でこれを手動で「1」に設定する必要があります。

  • r.MeshDrawCommands.DynamicInstancing このデフォルト値は「1」であるため、手動で設定する必要はありません。

  • r.MeshDrawCommands.UseCachedCommands このデフォルト値は「1」であるため、手動で設定する必要はありません。

インスタンス化における一般的な非互換性

ライティングありのマテリアルを使用する StaticMeshComponent では、

[ライトマップがビルドされていることを確認してください](working-with-content/types/static-meshes/lightmapping)

Go to Build menu, then Build Lighting Only to update lighting.

ライトマップがビルドされていない (もしくは最新のものではない) と、UE4 によって

[間接ライティング キャッシュ](building-virtual-worlds/lighting-and-shadows/global-illumination/cpu-lightmass/IndirectLightingCache)
に置き換えられます。間接ライティング キャッシュでは、ユニフォーム バッファ (DirectX では 定数バッファ と呼ばれる) を通じてライティング データがドローコールに渡されます。ユニフォーム バッファはそれぞれのドローコールに固有なものであるため、ドローコールが結合されなくなります。

ライトマップではこの問題は発生しません。ライトマップのデータ パスがインスタンス化をサポートするよう設計されているためです。

自動インスタンス化をエディタ内でテストするには、以下を手順を行います。

  1. [View Mode (表示モード)][Lit (ライティングあり)][Detailed Lighting (詳細ライティング)]、または [Reflections (反射)] のいずれかに設定されていることを確認します。多くのデバッグ表示モードはこのライトマップ データ パスと互換性がないため、このステップは必須です。

  2. 以下のモードが 有効になっている ことを確認します。

    • [Show (表示)] > [Advanced (詳細設定)] > [LOD Parenting (LOD ペアレンティング)]

    • [Show] > [Lighting Features (ライティング機能)] > [Volumetric Lightmap (ボリュメトリック ライトマップ)]

    • [Show] > [Lighting Features] > [Indirect Lighting Cache (間接ライティング キャッシュ)]

  3. 以下のモードが 無効になっている ことを確認します。

    • [View Mode (表示モード)] > [Lightmap Density (ライトマップ密度)]

    • [View Mode] > [Level of Detail Coloration (LOD 色分け)] > [Mesh LOD Coloration (メッシュ LOD 彩色)]

    • [View Mode] > [Level of Detail Coloration] > [Hierarchical LOD Coloration (階層 LOD 色分け)]

    • [Show] > [Advanced] > [BSP Split (BSP 分割)]

    • [Show] > [Advanced] > [Property Coloration (プロパティの彩色)]

    • [Show] > [Advanced] > [Mesh Edges (メッシュ エッジ)]

    • [Show] > [Advanced] > [Light Influences (ライトの影響)]

    • [Show] > [Advanced] > [Mass Properties (質量プロパティ)]

エディタ内でのテスト時に IsRichVew で True が返された場合、すべての FStaticMeshSceneProxybStaticRelevance を失い、 bDynamicRelevance を取得します。これにより、間接的に自動インスタンス化からスタティック メッシュが除外される結果となります。

上記のデバッグ表示モードでは、 IsRichVew で True を返させることができます。

検証

CVar r.MeshDrawCommands.LogDynamicInstancingStats 1 を使って自動インスタンス化の統計情報を表示します。このコンソール コマンドでは、インスタンス化による結合前と後のドローコール数の比率であるドローコール減少係数が出力されます。

ソフトウェア内でどのような処理がを行われているか興味があれば、 RenderDoc を使ってキャプチャすることができます。複数のオブジェクトが単一のインスタンス化されたドローコールにまとめられる様子を確認できます。

クリックして画像を拡大します。RenderDoc の注釈にはインスタンス数が表示されます。この注釈は Unreal Engine によって出力されます。

仕組み

システムが複雑なため、インスタンス化の非互換性をすべて示すことはできませんが、インスタンス化の非互換性をデバッグする場合に把握しておくべき基本的な内部関数を示すことは可能です。

ネイティブ コードにおける自動インスタンス化の仕組みを参照する前に、次の CVar を無効にしてください。

  • r.ParallelInitViews=0

  • r.MeshDrawCommands.ParallelPassSetup=0

キャッシュされたドローコール

それぞれのドローコールには、キャッシュされたドローコールの順序を決める 32 ビットの整数 FCachedMeshDrawCommandInfo::StateBucketId が関連付けられています。

Unreal では、パフォーマンスのために不変の StaticMeshComponent をキャッシュします。この動作は r.MeshDrawCommands.UseCachedCommands によって制御され、デフォルト値は 1 です。

これは、ドローコールがキャッシュされた際に FCachedMeshDrawCommandInfo::StateBucketId の値が計算されることを意味しています。この処理は FCachedPassMeshDrawListContext::FinalizeCommand で行われます。FCachedMeshDrawCommandInfo::StateBucketId の計算は、FMeshDrawCommand::GetDynamicInstancingHash で算出されるハッシュ値の評価に依存します。この関数の内部では、ハッシュ値が次の属性に依存していることがわかります。

  • IndexBuffer

  • VertexBuffers

  • VertexStreams

  • PipelineId

  • DynamicInstancingHash

  • FirstIndex

  • NumPrimitives

  • NumInstances

  • IndirectArgsBufferOrBaseVertexIndex

  • NumVertices

  • StencilRefAndPrimitiveIdStreamIndex

IndexBufferVertexBuffersVertexStreams、および NumVertices はスタティック メッシュで定義されるため、すべてのオブジェクトで同じスタティック メッシュを参照する必要があります。PipelineId はマテリアルとレンダラによって定義され、 DynamicInstancingHash もマテリアルによって定義されます。これら以外の属性は、UE4 での平均的なユース ケースにおいては関連性はあまりありません。

キャッシュされていないドローコール

FVisibleMeshDrawCommand クラス ( MeshPassProcessor.h 内) は、SkeletalMeshComponent や他のエディタ ウィジェットなど、動的に生成されてキャッシュされないドローコールを表しています。残念ながらこれはまだ機能しておらず、UE4.25 の時点では、キャッシュされていないドローコールで自動インスタンス化はサポートされていません。

マテリアル

ドローコールのインスタンス化の互換性は、PipelineId および DynamicInstancingHash のためにマテリアルに依存します。 PipelineId は、マテリアルのブレンド モードおよびシェーディング モデルの設定に影響を受けます。異なるブレンド モードおよびシェーディング モデルを使用すると、異なる Pipeline State Object が生成される結果となります。 DynamicInstancingHashFMeshDrawShaderBindings::GetDynamicInstancingHash 関数によって計算されます。出力ハッシュ値を決定する属性は以下のとおりです。

属性

説明

LooseParametersHash

マテリアルのテクスチャから算出されたハッシュ値の累積。

UniformBufferHash

マテリアル パラメータまたは マテリアル パラメータ コレクション の使用を参照します。

サイズ、頻度

いずれの要素もマテリアルから生成されるシェーダによって決まります。

2 つのマテリアルが同じ一連のテクスチャおよびユニフォーム バッファを参照する場合、これらは互いに互換性のあるマテリアルと見なされます。間接ライティング キャッシュでインスタンス化できないのは、ユニフォーム バッファのこの要件が原因です。ライティング データを固有のユニフォーム バッファとともにアップロードする必要性によって UniformBufferHash が変更されます。

Unreal Engine 4.25 以降では、マテリアル パラメータとパラメータ コレクションを使用してもインスタンス化が妨げられることはありません。Unreal のマテリアル式キャッシュでは、同一のパラメータの組み合わせを同じユニフォーム バッファに含めることができます。

CPU コスト

自動インスタンス化は錐台カリング後に行われます。つまり、画面上に表示されないものがある場合、それはドローコールの結合には含まれないことを意味するため、計算コストの削減につながります。

ただし、ドローコールの並べ替えおよび結合には CPU サイクルのコストがかかります。このため、自動インスタンス化を無効にすべきなのは、インスタンス化不可な非互換性のコンポーネントがシーンで使用されていることがわかっている場合のみになります。

ドローコールの結合にかかる CPU コストを確認することで、この処理の負荷が非常に低いことがわかります (モバイル VR の場合)。また、これらは複数のコア間で分散されています。

STAT_DynamicInstancingOfVisibleMeshDrawCommands は、互換性のあるドローコールを収集する CPU コストを表す CPU トレース イベントです。

GPU コスト

GPU コストは、コンピューティング シェーダがプリミティブ ユニフォーム バッファをインデックス可能なデータ構造体に含める方法によって決まります。このコンピューティング シェーダは、インスタンスごとのデータの変更に伴って更新が必要な場合のみに実行されます。

インスタンスごとのデータをフレームごとに更新するよう UE4 を強制的に設定するには、接続されているデバイスを含めてこのコマンドをコマンド プロンプトかパワー シェルで使用します。

adb shell "am broadcast -a android.intent.action.RUN -e cmd 'r.GPUScene.UploadEveryFrame 1'"

UploadEveryFrame が有効になっている場合、Oculus RenderStage トレース機能の RenderDoc ではコンピューティング シェーダのコストを測定します。

RenderDoc for Oculus capture shows the cost.

上記の Oculus の RenderDoc キャプチャには、27 ミリ秒の基本的なマテリアルを使って、球形の 15 個のインスタンスのインスタンス データを整理するコンピューティング シェーダのコストが示されています。これは、単一タイルのレンダリングよりも低いコストです。