Unity を検索

SRP Batcher:レンダリングをスピードアップ

2019年2月28日 カテゴリ: Engine & platform | 12 分 で読めます
シェア

Is this article helpful for you?

Thank you for your feedback!

2018 年、Unity はスクリプタブルレンダーパイプライン(SRP)というカスタマイズ性に優れたレンダリングテクノロジーを導入しました。この中には、ローレベルエンジン向けの新しいレンダリングループ「SRP Batcher」が用意されています。SRP Batcher を使うと、CPU によるレンダリング処理の速度がシーンに応じて 1.2 倍から 4 倍に向上します。この機能をフル活用する方法をご覧ください!

このコンテンツはサードパーティのプロバイダーによってホストされており、Targeting Cookiesを使用することに同意しない限り動画の視聴が許可されません。これらのプロバイダーの動画の視聴を希望する場合は、Targeting Cookiesのクッキーの設定をオンにしてください。

上記のビデオは Unity にとって最悪のシナリオを示しています。具体的には、各オブジェクトが動的であり、かつそれぞれ異なるマテリアル(色、テクスチャ)を使用している様子を示しています。このシーンには多数の類似したメッシュがありますが、オブジェクトごとにメッシュが異なる場合でも同じように実行されます(このため、GPU インスタンシングは使用できません)。PlayStation 4 では約 4 倍に高速化されます(このビデオは PC で、DirectX 11 を使用しています)。

注:4 倍の高速化について説明する際は、CPU レンダリングコード(「RenderLoop.Draw」および「ShadowLoop.Draw」プロファイラーマーカー)を取り上げますが、グローバルフレームレート(FPS)に関しては取り上げていません。

Unity とマテリアル

Unity エディターには非常に柔軟性のあるレンダリングエンジンが搭載されています。フレームの間はいつでも、どの Material プロパティーでも更新することができます。さらに、Unity は、歴史的に見て非定数のバッファーを考慮して設計されており、DirectX 9 などのグラフィックス API に対応しています。ただし、そういった便利な機能にもいくつかの欠点があります。たとえば、ドローコールが新しいマテリアルを使用する際は必要な処理が多数生じます。したがって基本的には、シーンに含まれるマテリアルが増えるにつれて、GPU データのセットアップに必要な CPU が増加します。

標準的な Unity のレンダリングワークフロー

内部のレンダーループ内では、新しいマテリアルが検出されると、CPU はすべてのプロパティーを収集して GPU メモリ内でさまざまな定数バッファーを設定します。GPU バッファーの数は、シェーダーがその CBUFFER を宣言する方法によって異なります。

SRP Batcher の仕組み

SRP テクノロジーを開発するにあたり、私たちはローレベルエンジンの一部を書き直さなければなりませんでした。これは GPU のデータ永続性など、いくつかの新しいパラダイムをネイティブに統合する格好の機会でした。私たちが目指したのは、シーンでさまざまなマテリアルが使用されているにもかかわらずシェーダーバリアントは非常に少ないという、一般的なケースをスピードアップすることでした。

現在ローレベルのレンダーループは、GPU メモリでマテリアルデータを永続化できるようになりました。マテリアルのコンテンツが変わらなければ、バッファーを設定して GPU にアップロードする必要はありません。加えて、大きな GPU バッファーで組み込みのエンジンプロパティーをすばやく更新することに特化したコードパスを使用しています。新しいフローチャートは次のようになりました。

SRP Batcher のレンダリングワークフロー

ここでは、CPU は、「object matrix transform」というラベルの付いた、組み込みのエンジンプロパティーのみを処理しています。どのマテリアルも、すぐに使用できる永続的な CBUFFER を GPU メモリ内に持ちます。まとめると、高速化を実現する条件は次の 2 点です。

  • 各マテリアルのコンテンツが GPU メモリで永続化されている
  • 「オブジェクトごとの」大きな GPU CBUFFER を管理する専用のコードがある

SRP Batcher を有効にする方法

プロジェクトでは、ライトウェイトレンダーパイプライン(LWRP)、HD レンダーパイプライン(HDRP)、自作のカスタム SRP のいずれかを使用する必要があります。HDRP または LWRP で SRP Batcher を有効化するには、SRP Asset のインスペクターでそのチェックボックスをオンにするだけです。

また、実行時に SRP Batcher を有効化/無効化し、パフォーマンスの改善具合についてベンチマークテストするには、C# コードを使用して次に示すグローバル変数を切り替えます。

GraphicsSettings.useScriptableRenderPipelineBatching = true;

SRP Batcher の互換性

オブジェクトが SRP Batcher コードパスを通じてレンダリングされるように、次の 2 つの要件があります。

  1. オブジェクトはメッシュ内に存在しなければならない。パーティクルやスキンメッシュでは要件を満たせません。
  2. SRP Batcher と互換性のあるシェーダーを使用する必要がある。HDRP と LWRP のすべての Lit シェーダーおよび Unlit シェーダーはこの要件を満たします。

シェーダーに SRP との互換性を持たせるためには、次の要件があります。

  • 組み込みのエンジンプロパティーをすべて、「UnityPerDraw」という名前の単一の CBUFFER で宣言する必要がある。unity_ObjectToWorld や unity_SHAr などがあります。
  • マテリアルのプロパティーをすべて、「UnityPerMaterial」という名前の単一の CBUFFER で宣言する必要がある。

シェーダーの互換性の状態は「Inspector」パネルで確認できます。この互換性のセクションが表示されるのは、プロジェクトが SRP をベースとしている場合のみです。

どのシーンにおいても、SRP Batcher と互換性のあるオブジェクトもあれば、互換性がないプロジェクトもありますが、それに関係なくシーンは正常にレンダリングされます。互換性のあるオブジェクトは SRP Batcher コードパスを使用し、それ以外のオブジェクトは標準の SRP コードパスを使用します。

プロファイリングの技術

SRPBatcherProfiler.cs

特定のシーンで SRP Batcher による高速化の具合を測定するには、SRPBatcherProfiler.cs という C# スクリプトを使用します。シーンにこのスクリプトを追加するだけです。このスクリプトの実行中に F8 キーを押せば、オーバーレイの表示と非表示を切り替えることができます。また、F9 キーを押せばプレイ中に SRP Batcher のオンとオフを切り替えることも可能です。(F8 キーを押して)再生モードでオーバーレイウィンドウを有効化すると、次のように役立つ情報を確認できます。

時間の測定単位はいずれもミリ秒(ms)です。このような時間の測定結果から、Unity SRP レンダリングループで使用される CPU の消費がわかります。

注:ここで表示される計測時間とは、スレッドの所有者に関係なくフレーム中に呼び出されるすべての「RenderLoop.Draw」および「Shadows.Draw」マーカーの蓄積された時間のことです。たとえば「1.31ms SRP Batcher code path」がおそらく意味するのは、メインスレッドで 0.31 ミリ秒が使用され、すべてのグラフィックジョブに 1 ミリ秒が分散しているということです。

オーバーレイの情報

次の表では、再生モードの場合に表示されるオーバーレイの各設定を上から順に説明します。

注:最適化の際には FPS の指標について細心の注意を払う必要があるため、オーバーレイの一番下に FPS を追加することにためらいがあります。まず FPS は線形ではないので、FPS が 20% 改善したという数値が出てもシーンがその数値だけ最適化されたことにはなりません。そして、FPS はフレーム全体にわたってグローバルです。FPS(またはグローバルフレームの計時)は C# のゲームプレイ、物理演算、カリングなど、レンダリング以外の多くの要素によって変わります。

SRPBatcherProfiler.cs は GitHub の SRP Batcher プロジェクトテンプレートから入手できます。

各種シーンのベンチマーク

ここからは、さまざまな状況における高速化について確かめるために SRP Batcher のオフとオンを切り替えた Unity のスクリーンショットをいくつか示します。

HDRP を使用した PlayStation 4 の『Book of the Dead』では、速度が 1.47 倍に向上します。このシーンは GPU バウンドなので FPS に変化がないことに注意してください。CPU 側では 12 ミリ秒の余裕が生まれました。高速化の程度は PC でもほぼ同じです。

HDRP を使用した PC(DirectX 11)の『FPS Sample』では、速度が 1.23 倍に向上します。ただし、SRP Batcher との非互換性が原因で標準のコードパスに 1.67 ミリ秒が残っています。ここでは、スキンメッシュといくつかのパーティクルがマテリアルのプロパティーブロックを使用してレンダリングされています。

LWRP を使用した PlayStation 4 の『Boat Attack』では、速度が 2.13 倍に向上します。

サポートされているプラットフォーム

SRP Batcher はほぼすべてのプラットフォームで動作します。次の表に、プラットフォームと必要とされる Unity の最小バージョンをまとめました。Unity 2019.2 は現在オープンアルファ版が公開されています。

VR の簡単な紹介

SRP Batcher の高速なコードパスは SinglePassInstanced モードのみで VR 対応しています。(SinglePassInstanced モードにより)VR を有効化しても CPU 時間が増加することはありません。

よくある質問

SRP Batcher を最適に活用しているかどうか確認する方法を教えてください。

SRPBatcherProfiler.cs を使用し、まず SRP Batcher がオンになっていることを確認してください。その後、「標準のコードパス」の時間を確認します。これが 0 に近い数字であること、そしてすべての時間が「SRP Batcher コードパス」で計測されていることが必要です。シーンでスキンメッシュやパーティクルをいくつか使用している場合は標準のコードパスにある程度時間が費やされることもあります。GitHub にある SRP Batcher ベンチマークプロジェクトをチェックしてください。

SRP Batcher がオンかオフかにかかわらず、SRPBatcherProfiler で示される時間が類似しています。これはなぜですか。

まず、ほぼすべてのレンダリング時間が、(上述の)新しいコードパスを経ていることを確認する必要があります。それが確認できたら、オンとオフの数値が類似している場合は「flush」の数値に注目してください。これは SRP Batcher がオンになっていれば大きく下がるはずの数値です。経験上、10 で割った値であれば非常によく、2 で割った値はほぼ良好といえます。flush の数値が大幅に下がらない場合は依然としてシェーダーバリアントが多いということになるため、シェーダーバリアントの数を減らすことをおすすめします。さまざまなシェーダーを多数実行した場合は、多くのパラメーターを持つ「ウーバー」シェーダーを作成してみてください。これにより、マテリアルのさまざまなパラメーターを非常に多く利用することが可能になります。

SRP Batcher を有効にしてもグローバル FPS は変わりませんでした。これはなぜですか。

上記の 2 つの質問をチェックしてください。SRPBatcherProfiler の「CPU Rendering time」で速度が 2 倍になったことが示され、FPS に変更がなかったのであれば、CPU レンダリング部分はボトルネックではありません。これは CPU バウンドでないという意味ではなく、むしろ使用している C# のゲームプレイや物理演算要素が多すぎるのかもしれません。いずれにせよ「CPU Rendering time」で速度が 2 倍になったのは良いことです。この記事で最初に紹介したビデオでは、3.5 倍の高速化を遂げてもシーンが依然として 60fps だったことにお気付きでしょうか。これは VSync をオンにしているためです。実際のところ、SRP Batcher により CPU 側で 6.8 ミリ秒の時間短縮が実現しており、この 6.8 ミリ秒を別のタスクに割り当てることができます。また、モバイルであればバッテリー寿命をいくらか節約することも可能です。

SRP Batcher の効率をチェックする方法

SRP Batcher のコンテキストにおいて「バッチ」が意味するものを理解することは重要です。これまで、CPU のレンダリングコストを最適化するためにドローコールの数を減らすという措置が取られる傾向にありました。この本当の理由は、エンジンがドローを発行する前に多くの設定を行わなければならないためです。そして実際の CPU コストは(GPU コマンドバッファーに送信する数バイトに過ぎない)GPU ドローコールそのものではなく、この設定によって発生しています。SRP Batcher はドローコールの数を減らすわけではなく、単にドローコール間の GPU の設定コストを削減するにすぎません。

その仕組みを次のワークフローで示します。

左側のワークフローが標準的な SRP レンダリングループで、右側が SRP Batcher ループです。SRP Batcher のコンテキストでは、「バッチ」は単に「バインド」「ドロー」「バインド」「ドロー」が繰り返される一連の GPU コマンドです。

標準的な SRP では、速度の遅い SetShaderPass がそれぞれの新しいマテリアルに対して呼び出されます。SRP Batcher のコンテキストでは、それぞれの新しいシェーダーバリアントに対して呼び出されます。

最大限のパフォーマンスを発揮させるには、これらのバッチをできるだけ大きく保つ必要があります。このためシェーダーバリアントの変更は避ける必要がありますが、同じシェーダーを使用しているのであれば異なるマテリアルをいくつでも使用してかまいません。

SRP Batcher の「バッチ群」の長さを調べるには Unity のフレームデバッガーを使用できます。次に示すように、フレームデバッガーでは、各バッチが「SRP Batch」という名前のイベントとして表示されます。

左側の SRP Batch イベントを参照してください。また、バッチのサイズにも注目してください。「Draw Calls」の数値がサイズになります(ここでは 109)。これは非常に効率のよいバッチといえます。また、1 つ前のバッチが壊れている理由(「Node use different shader keywords」)も表示されていますが、これは、使用されたシェーダーキーワードが前回のバッチと今回のバッチで異なることを意味します。つまり、シェーダーバリアントが変更されているため、バッチを壊さなければなりません。

シーンによっては、次のように一部のバッチのサイズが非常に小さくなることがあります。

バッチのサイズはわずか 2 となっています。使用されているシェーダーバリアントの種類が多すぎる可能性があるため、独自の SRP を作成している場合は、最小限のキーワードを使用して汎用の「ウーバー」シェーダーを記述してみてください。プロパティーセクションで追加するマテリアルパラメーターの数について気にする必要はありません。

注:フレームデバッガーに SRP Batcher の情報を表示するには Unity 2018.3 以降が必要です。

互換性のあるシェーダーで独自の SRP を作成する

注:このセクションは、スクリプタブルなレンダーループとシェーダーライブラリを独自に作成している上級ユーザーを対象としています。Unity が提供するシェーダーはすべて SRP Batcher と互換性があるため、LWRP または HDRP ユーザーはこのセクションを読み飛ばしてかまいません。

独自のレンダーループを記述している場合、シェーダーは、SRP Batcher コードパスを経由するためにいくつかの規則に従う必要があります。

「マテリアルごとの」変数

まず、「マテリアルごとの」データはすべて、「UnityPerMaterial」という単一の CBUFFER で宣言する必要があります。「マテリアルごとの」データとは何かというと、基本的には、「シェーダープロパティー」セクションで宣言したすべての変数のことです。つまり、マテリアルの GUI インスペクターを使用してアーティストが微調整できる変数すべてです。たとえば、次のように簡単なシェーダーを見てみましょう。

Properties

{

_Color1 ("Color 1", Color) = (1,1,1,1)

_Color2 ("Color 2", Color) = (1,1,1,1)

}

float4 _Color1;

float4 _Color2;

このシェーダーをコンパイルすると、このシェーダーのインスペクターパネルでは次のように表示されます。

その部分を修正するには、次のようにすべての「マテリアルごとの」データを宣言するだけです。

CBUFFER_START(UnityPerMaterial)

float4 _Color1;

float4 _Color2;

CBUFFER_END

「オブジェクトごとの」変数

SRP Batcher には、「UnityPerDraw」という非常に特別な CBUFFER も必要です。この CBUFFER には、Unity の組み込みのエンジン変数をすべて含める必要があります。

「UnityPerDraw」CBUFFER の中で変数を宣言する順番も重要です。どの変数も「ブロック機能」というレイアウトを考慮する必要があります。たとえば「Space Position block feature」には、次の順番でこれらすべての変数を含める必要があります。

float4x4 unity_ObjectToWorld;

float4x4 unity_WorldToObject;

float4 unity_LODFade;

float4 unity_WorldTransformParams;

必要なければ、これらのブロック機能の一部は宣言しなくてもかまいません。「UnityPerDraw」における組み込みのエンジン変数はすべて、float4 または float4x4 にする必要があります。モバイルでは、GPU 帯域幅をいくらか節約するために real4(16 ビットのエンコードされた浮動小数点値)を使用することをおすすめします。UnityPerDraw 変数のすべてで「real4」を使用できるわけではありません。下の表の「Could be real4」列を参照してください。

次の表に、「UnityPerDraw」CBUFFER で使用できる可能性のあるすべてのブロック機能を示します。

注意:ブロック機能の変数のいずれかが real4(half)として宣言される場合、そのブロック機能に他の変数があればすべて real4 として宣言する必要があります。

ヒント 1:インスペクターで新しいシェーダーの互換性の状態を必ず確認してください。潜在的なエラー(UnityPerDraw のレイアウトの宣言など)をいくつかチェックし、互換性がない場合はその理由が表示されます。

ヒント 2:独自の SRP シェーダーを記述する際は、LWRP または HDRP パッケージを参照し、UnityPerDraw CBUFFER 宣言からインスピレーションを得ることをおすすめします。

今後の予定

Unity では、今後も、いくつかのレンダリングパス(特に Shadow パスと Depth パス)のバッチサイズを拡大し、SRP Batcher の改良を続けていく予定です。

また、SRP Batcher で自動 GPU インスタンシングを使えるように取り組んでいます。さらに、Unity は新しい DOTS レンダラーを導入しました。これは 『MegaCity』デモで使用されています。Unity エディターで 10 FPS だったものが 50 FPS になるほどの目覚ましい速度の改善が見られます。

SRP Batcher & DOTS レンダラーを使用したエディター内の MegaCity です。パフォーマンスの違いは劇的で、グローバルフレームレートも 5 倍高速化します。

注:正確に言えば、SRP Batcher を有効化した際に劇的に高速化されるのはエディターのみです。エディターは現在 Graphics Jobs を使用していないためです。スタンドアロンプレイヤーモードの場合は速度が約 2 倍向上します。

エディター内の MegaCity です。このビデオを 60Hz で再生すれば SRP Batcher を有効にした場合の高速化を体感できるはずです。

注:SRP Batcher と DOTS レンダラーはまだ実験段階にあり、現在も開発に取り組んでいます。

2019年2月28日 カテゴリ: Engine & platform | 12 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

関連する投稿