GPU Resident Drawerの動作原理を徹底解説 — Unity 6はどのようにドローコールを削減するのか

Unity 6に新たに追加されたGPU Resident Drawerは、ドローコールを最大50%削減できます。しかし、正確にはどのようにそうなるのでしょうか?

この記事では、GPU Resident Drawerが内部的にどのような原理で動作するのか、どのような条件で効果があり、どのような場合には機能しないのかを深く掘り下げます。「オンにすれば速くなる機能」ではなく、原理を理解した上で使うことで、本来の力が発揮できます。


目次


まず、従来のレンダリングパイプラインのボトルネックを把握する

GPU Resident Drawerの価値を理解するには、まず従来のCPUベースのレンダリングがなぜ遅くなるのかを知る必要があります。

Unityの従来のレンダリングフローは、おおよそ以下のようになっています:

1. CPU:シーンのすべてのRendererを巡回し、カリングを判定
2. CPU:ドローコールリストを整理(SRP Batcher、Static Batchingを適用)
3. CPU → GPU:ドロー命令(ドローコール)を1つずつ転送
4. GPU:実際のレンダリングを実行

問題は3番です。CPUが毎フレーム「これを描け、あれを描け」をGPUに1つずつ伝えるのに、相当な時間がかかります。シーンにオブジェクトが数千、数万個あれば、このやり取り自体がボトルネックになります。GPUはすでに前の作業を終えて待機しているのに、CPUがまだ次の命令を準備中という状況が生まれます。


BatchRendererGroup — GPU Resident Drawerの基盤

GPU Resident DrawerはBatchRendererGroup(BRG)APIの上に構築されています。BRGはもともとDOTS(Entities)グラフィクスパッケージが内部的に使用していた低レベルAPIですが、Unity 6では通常のGameObjectにも自動的に適用されるようになりました。それがGPU Resident Drawerです。

BRGの核心的なアイデアは、ドロー命令の主導権をCPUからGPUに移すことです。

従来の方式ではCPUが「オブジェクトAを位置Xに描け」という命令をGPUに伝える構造でしたが、BRGの方式ではCPUがインスタンスデータ(位置、回転、スケール、マテリアルプロパティなど)をGPUメモリにアップロードし、GPUが直接取り出して描画します。

従来:
CPU [オブジェクト巡回 → ドローコール生成] → GPU [ドローコール受信 → レンダリング]

BRG:
CPU [インスタンスデータをGPUメモリにアップロード] → GPU [データを直接参照 → レンダリング]

この構造により、CPU-GPU間の通信回数が大幅に削減され、GPUは待機なしに作業を継続できます。


GPUインスタンシングと間接レンダリング(Indirect Rendering)

BRGがGPUメモリにアップロードするデータはStructured Bufferの形式です。同じメッシュを使用するオブジェクトのトランスフォーム情報が配列としてGPUに常駐(Resident)します。名前に「Resident」が付いている理由がまさにここにあります。

実際の描画命令はDrawMeshInstancedIndirect系の間接レンダリング方式で実行されます。「Indirect(間接)」という名称が示すように、CPUが直接「N個描け」と指示するのではなく、GPUがバッファを参照してインスタンス数と範囲を自ら判断します。

従来のGraphics.RenderMeshInstanced()は呼び出しあたり最大1,023個までしかインスタンスを処理できませんでしたが、GPU Resident Drawerにはこの制限がありません。内部的にバッチを自動分割するため、理論上インスタンス数に上限はありません。


GPUオクルージョンカリング — カリングもGPUへ

カリング、つまり画面に映っていないオブジェクトをレンダリング対象から除外する処理も、従来はCPUの担当でした。GPU Resident Drawerはこれもまたまたのこれまた GPUに移します。

方式は前フレームの深度バッファ(Depth Buffer)の活用です:

1. 前フレームのレンダリング完了 → 深度バッファを保持
2. 現在フレームの開始時、Compute Shaderが各インスタンスのバウンディングボックスを深度バッファと照合
3. 遮蔽された(occluded)インスタンスをドローリストから除外
4. 通過したインスタンスのみレンダリング

この方式の利点は速度です。CPUオクルージョンカリングはレイキャストやPVS(Potentially Visible Set)構造をCPU側で計算する必要があるのに対し、GPU並列処理は数千のインスタンスを同時に判定します。

ただし、ひとつの欠点があります。1フレームの遅延です。前フレーム基準でカリングするため、カメラが非常に素早く移動・回転すると、カリングエラー(誤って除外されたオブジェクトが1フレームだけ点滅する現象)が生じることがあります。高速移動カメラのあるゲームでは、この点を検証しておく必要があります。


DOTSインスタンシング — シェーダーの要件

GPU Resident Drawerを機能させるには、使用しているシェーダーがDOTSインスタンシングをサポートしている必要があります。URP/HRDPのLit、Unlitなど Unity組み込みシェーダーはすでに対応していますが、カスタムシェーダーは確認が必要です。

DOTSインスタンシングシェーダーでインスタンスデータを読み取る方式は、従来のGPUインスタンシングとは異なります:

// 従来のGPUインスタンシング
UNITY_INSTANCING_BUFFER_START(Props)
    UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)

// DOTSインスタンシング(BRG方式)
#ifdef UNITY_DOTS_INSTANCING_ENABLED
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
    UNITY_DOTS_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
#endif

カスタムシェーダーを多用するプロジェクトでは、GPU Resident Drawerを有効化する前にシェーダーの互換性を確認しておく必要があります。

この詳細については、次の記事カスタムシェーダーとGPU Resident Drawer — DOTSインスタンシング完全攻略で詳しく解説しています。


実際の数値で見る効果

公開されているテスト結果の中で印象的な事例があります。植生オブジェクト35,000個のシーンにおいて:

項目 GPU Resident Drawer無効 GPU Resident Drawer有効
ドローコール数 43,500 128
CPUレンダリング負荷 基準値 約50%削減
GPUメモリ使用量 基準値 +約100MB増加

ドローコールが43,000個から128個に減少するという数値はかなり衝撃的です。一方でメモリは約100MB増加します。インスタンスデータをGPUメモリに常駐させるコストがそのままメモリオーバーヘッドとして現れています。

メモリ予算が厳しい低スペック向けプロジェクトでは、有効化前後をProfilerで必ず計測してから使用するか判断してください。


有効化の方法(URP)

3か所の設定を変更する必要があります:

① Shader Strippingの設定
Project Settings → Graphics → Shader Stripping
BatchRendererGroup VariantsKeep All

② URP Assetの設定
Project Settings → GraphicsでアクティブなURP Assetを選択
SRP Batcher有効化
GPU Resident DrawerInstanced Drawing

③ Universal Rendererの設定
Renderer Listのレンダラーをダブルクリック
Rendering PathForward+

ビルド時間が長くなる点は覚悟が必要です。BRGシェーダーバリアント(Variant)をすべてコンパイルする必要があるためです。エディターのPlay Mode起動速度には影響しません。


対象外となる場合

以下の条件のいずれかに該当すると、そのGameObjectはGPU Resident Drawerから自動的に除外されます:

除外条件 理由
MaterialPropertyBlock APIの使用 インスタンスごとのCPUコールバックが必要な構造
OnRenderObjectなどのper-instanceコールバック 同じ理由
DOTSインスタンシング非対応のシェーダー BRGデータにアクセス不可
Light ProbeがProxy Volumeを使用(LPPV) GPU常駐データモデルとの競合
Skinned Mesh Renderer 現在未対応

LPPVと競合する理由は、データフローが根本的に異なるためです。LPPVはCPUが毎フレーム、インスタンスごとに、各Rendererのワールドポジションをもとにプロキシボリューム内のどの地点のSH(Spherical Harmonics)係数をサンプリングするかを計算し、インスタンスごとに異なるライティングデータを注入する必要があります。一方GPU Resident Drawerは、インスタンスデータをGPUメモリバッファに一度アップロードし、CPU介入なしにGPUが直接読み取る構造です。LPPVが要求する「毎フレーム・インスタンスごと・CPU主導のGIデータ更新」がこのGPU常駐モデルと相容れないため、BRGは該当オブジェクトを除外する選択をしています。

ライティング品質を維持しながらGPU Resident Drawerを使いたい場合は、LPPVの代わりにベイク済みLight Probe、またはこの構造をサポートするよう設計されたAPV(Adaptive Probe Volumes)を使用することをお勧めします。

上記の条件には該当しないが、特定のオブジェクトを明示的に除外したい場合は、Disallow GPU Driven Renderingコンポーネントを追加すればよいです。


デバッグ — 正しく動作しているかの確認方法

Frame Debuggerでドローコールが「Hybrid Batch Group」という名前でまとめられて表示されていれば、GPU Resident Drawerがそのオブジェクトを処理しているということです。
Frame DebuggerでHybrid Batch Groupにまとめられている例

GPUオクルージョンカリングの確認は、Window → Analysis → Rendering Debugger → GPU Resident DrawerタブOcclusion Test Overlayチェックボックスをオンにすると、カリングされたオブジェクトをシーンビューで視覚的に確認できます。
GPUオクルージョンカリングの確認


まとめ — いつ使い、いつ使わないか

GPU Resident Drawerは同じメッシュが多数存在するシーンで劇的な効果を発揮します。都市背景、森、群衆など、同一メッシュが大量に分布するケースが典型的な受益ケースです。

一方、Skinned Meshが多いシーン(キャラクターなど)や、MaterialPropertyBlockで個別制御が必要なオブジェクトが多いシーンは、現時点では恩恵を十分に受けにくい状況です。このような場合は、むやみに全体有効化するよりも、エリアごとに分けて適用しProfilerで検証する方が賢明です。


→ 次の記事: カスタムシェーダーとGPU Resident Drawer — DOTSインスタンシング完全攻略


参考資料

コメントする