Search Unity

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


上記のビデオは 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# コードを使用して次に示すグローバル変数を切り替えます。

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 インスペクターを使用してアーティストが微調整できる変数すべてです。たとえば、次のように簡単なシェーダーを見てみましょう。

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

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

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

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

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

必要なければ、これらのブロック機能の一部は宣言しなくてもかまいません。「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 レンダラーはまだ実験段階にあり、現在も開発に取り組んでいます。

24 コメント

コメントの配信登録

コメント受付を終了しました。

  1. Does shader Variant mean the same shader but with a different property or value? (like using a glossyness slider in a different way)

  2. Another weekend exploring new optimization solutions. Keep em coming Unity!

  3. The option doesn’t show on 2018.3.7 for me, what am I missing? I have installed the LWRP package (obviously)

  4. yeah, ok…. but please, remember to make public a tool when the tool is finished (not a preview), integrated, good documented (with examples of practique use and not just a “maybe you can use this in this way, understand it by yourself”), designed for a massive use and not only for some programmers, thanks.

    1. Setriakor Nyomi

      3月 6, 2019 6:05 am

      I disagree. The preview programme is very important. It allows us to give feedback about upcoming features while they’re still in development and vastly shortens the dev cycle. Why get the opinions of only a handful of developers in a room when you can get it from 10’s of thousands all over the world?

      1. Well, is my opinion, I want to made videogames, not to be a Unity tester.

        They changed and replaced a lot of stuff on Unity 2018.3 and 2019 with tools that are on development (like the nested prefabs that broken with all the previous prefab workflow, I hate that and we don´t have alternatives) and they are showing some of that un-finished tools (like the SRP) as the principal characteristics of the newer versions, that tools are undocumented, aren’t full tested and have a lot of instability. Great if that tools are on preview, but please don’t sell it like a finished tools. Take your time, finished the tool´s development and launch a real Unity update, not this.

  5. I see only half of article.

  6. In the per object variables section, you write: Please refer to the “Could be real4” column.
    However, this column does not exist in the table.

  7. John KP @ Mindshow

    3月 1, 2019 7:08 pm

    The SRP Batch code path isn’t running for me when VR is enabled in my project. This was replicated in a clean project with just art and unmodifed code as well:

    2019.1 b4
    Unmodified version of LWRP
    VR Enabled (Single Pass Instanced)
    SRP Batching Enabled in Lightweight Render Pipeline Asset.
    Graphics Jobs Enabled
    Windows Standalone
    DX11
    All materials in scene are using the Lit shader provided by LWRP.

    Result:
    SRP Batching Profiler (and frame debugger) reveal that the SRP Batches aren’t happening.

    If VR is disabled (or if frame debugger is sampled while play mode is inactive in editor)
    SRP Batching works as expected.

    Is there something I’m overlooking?

  8. Great article!
    By the way, if I see this blog from japan,
    the url is redirected to “blogs.unity3d.com/jp/2019/02/28/srp-batcher-speed-up-your-rendering/” and the content looks broken.

  9. Michael Marcin

    2月 28, 2019 10:16 pm

    The SRP Batcher seems to break my Statistics and Frame Debugger windows. Stats shows 6 batches and 1k tris for a scene with 1k batches and 800k tris with it disabled. The frame debugger has no Depth Prepass or GBuffer at all with the batcher enabled.
    This is on Unity 2018.3.6f1 with HDRP 4.10.0.

  10. Very cool!
    Is Unity working on a similar render approach as Frostbite/Unreal? I got this from a post:
    “The new mesh processor works by uploading the entire scene data to gpu memory. Instead of setting uniform parameters per-drawcall, it just uploads everything to GPU buffers and then refers back to those though indices. This allows unreal to do a lot more multithreading in the renderer, even in DX11 or phones, and it also makes unreal able to automatically instance everything possible.”

    No idea how this is called. Maybe not the best place to ask but anyway, is Unity doing something similar or researching?

    1. Er, that is what is happening here. GPU data persistence.

      1. No, it’s not quite right (partially). Epic changed a big chunk of renderer using Data-Oriented design along with an automatic instancing.

        1. They are working on a ECS based renderer. So I think, they would be refactoring their mesh drawing pipeline.
          Also where did you get this “EPIC changed a big chunk of renderer using Data-Oriented design”. I know they refactored and removed the legacy mesh drawing system, but I dont think its DOD-based.

    2. Alan Mattano

      3月 1, 2019 3:36 pm

      You are well come. I’m a user, ex UDK. I think what you are looking for is this:

      [Unity ECS: https://unity.com/dots#burst-compiler%5D

      Unity is working on this since 2017 2018 (I think) and so there are tutorials on YouTube.

      Explanation: [https://www.youtube.com/watch?v=d9Z4EUZ5apo]

      Here there is a Unity example that they are working on:

      [https://unity.com/megacity]

      And a Unity tutorial:

      [https://unity3d.com/learn/tutorials/topics/scripting/introduction-ecs?_ga=2.87982238.788035148.1551051194-2090915164.1475000191]

      So is not a job system for the mesh. Is also for the engine itself. To do this Unity is migrating to a special C# now called HPC# to get the performance of C++ in multithreading and using this memory layout that gives to the GPU all mesh, textures, animation inline order. The slogan for Unity engine improvement is: performance by default.

      [https://unity.com/dots]

      That will be introduce in this 2019 probably at GDC now in march.

    3. Alan Mattano

      3月 1, 2019 4:00 pm

      Here in Unity, there are 4 or 5 rendering engines: a traditional Unity5, a Desktop HDRP unity high definition render pipeline, the lightweight render pipeline LWRP for movies, other for VR with low performance and a custom rendering pipeline SRP. This article refers to this last one. I think is for advanced users or industries that want to migrate to Unity (and I presume If you are an expert you can put Unreal rendering in it if you wish).

  11. Awesome post. Thanks !

    Does that mean that we have to stop using MaterialPropertyBlocks and actually set the changing property directly on the material ?
    Or you working on a new MPB ?

    Thanks !

  12. Awesome work as usual! Excited about all these great improvements being made these past few years, the future looks bright for Unity.

    > If you could play the video at 60hz you would feel the speed up when enabling SRP Batcher.

    This is a 60fps video though, so 99.9% of people should be able to see it at 60hz!

  13. Great work, is this available on the Vulkan/DX12 APIs as I think these API’s are also more threadable and could potentially have greater gains in performance?

  14. Isaac Surfraz

    2月 28, 2019 10:40 am

    The images labelled “SRP Batcher rendering workflow” and “standard pipeline rendering workflow” are identical, which seems to be a mistake. Otherwise whats the point of differentiating when they are the same?

    1. Isaac, these definitely a mistake. I’m sure Arnaud will fix it soon

    2. Community Team

      2月 28, 2019 2:10 pm

      Thanks for noticing, and sorry for the confusion, we’ve just fixed it!

      1. First video in the post dont work in Firefox. :)

        Batcher works for OpenGL ES 3.1+
        Is there plans to make Batcher work on OpenGL ES 3.0? if no what is best workflow to create game for gles 3.0 and gles 3.1 for best performance?