Unity 6が正式にリリースされて、この文章の執筆時点で約2年近くの時間が経過しました。Unity 6リリースの大きな目玉の一つとして、Render Graphの登場がありました。Render Graphは、Frostbite(DICE)が2017年に発表したFrame GraphやUnreal EngineのRDG(Render Dependency Graph)のように、現在のモダンなレンダリングエンジンには多く取り入れられている、1フレームのリソース依存関係を宣言的に記述し、実行前にグラフ全体を最適化する考え方に基づいた仕組みです。
リソース依存性を記述することによって、レンダーテクスチャの使いまわしや不要なレンダーテクスチャの破棄、適切なメモリのロードストアの自動設定など様々な恩恵を受けることができます。
UnityではHDRPに先行して導入されていましたが、URPには導入されておらず、ついにUnity 6に合わせて待望のリリースとなりました。
本書の第1部1章「Unity 6 Render Graphタイルメモリ最適化」では、Render Graphのパワーを最大限引き出すために必要なTBDRアーキテクチャのGPUとRender Graphの関連性について紐解いていきます。
続く第1部2章「RenderGraphのUnsafePassを使うべき場面」ではRender Graphの恩恵が弱くなるUnsafe Passについて解説されています。Render Graphは通常Raster Render PassやCompute Passを利用することで、Render Graphによる最適化の恩恵を受けることができます。多くの場面でRaster Render PassやCompute Passを選ぶのが最善の選択になります。その中であえて、Unsafe Passを選ぶべき場面についてご紹介します。パフォーマンス向上のため、移植のため、Unsafeでしか実現できない機能を実装するために、適切にUnsafe Passを選ぶケースについて紹介します。
このようにRender Graphの登場によって、レンダリングシステムは大きく変わりました。Render Graphは自動的に最適化してくれますが、仕組みを理解せずに使っていると、最適化の恩恵を最大限生かすことはできません。新しいレンダリングシステムを理解し、ハンドリングする責任は我々開発者にあります。
さて、一方でRender Graphの登場によって、これまでの最適化の概念が全て覆ったのかというとそうではありません。Render Graphはグラフィックス領域では高いレイヤーの話です。よりハードウェアに近い、低いレイヤーに向けると昔ながらの知識は依然として有効です。例えば、シェーダーではsin、cos、tan、powなどの超越関数の演算処理はコストが高いという話があります。このような話は現代のモバイル環境であっても同様に通じます。第1部4章「モバイル向けシェーダー最適化:超越関数の計測と軽量化」では、現代のモバイル環境において超越関数を利用したときの計測結果の一例を示します。そのうえで、超越関数の軽量化として、いくつかの近似関数の紹介を行います。私自身も演算性能が向上した、現代のモバイル環境でも近似関数は必要なのか、という考えを持ったことがあります。そのような疑問の一つの答えを示してくれます。第1部5章「Renderer Shader User Valueを活用したグラフィックス最適化」と第1部6章「GraphicsStateCollectionによるシェーダーウォームアップ」で紹介される機能も、Unity 6以前から残っている問題を解決するために追加された機能です。
Renderer Shader User Value(RSUV)はUnity 6.3から追加された新しい機能です。この機能が解決しようとしている問題は、昔からある大量のオブジェクトを描画するときのCPU負荷、特にSet Pass Callの負荷です。この問題を解決する手段として、SRP BatcherやGPUインスタンシングなどがあります。
しかし、SRP Batcherには「オブジェクトごとに見た目を少しだけ変えたい」という場面で制約がありました。SRP BatcherはMaterialPropertyBlockを使ったレンダラーごとのプロパティ変更に対応していないため、SRP Batcherを維持したまま個別のパラメーターを渡すには、カスタマイズしたいレンダラーの数だけMaterialを複製する必要がありました。わずかなパラメーターの違いのためにMaterialを増やすのは、メモリ的にも管理面でも望ましくありません。
RSUVはこの問題に対するより軽量な選択肢です。RSUVを利用すると、レンダラーごとに32bit符号なし整数を1つ持たせ、シェーダー側から参照できます。この値の設定はバッチングを壊さないため、SRP Batcherを維持したままパラメーターを変更できます。渡せるのは32bitの値1つという制限はありますが、色やインデックス、フラグなどをビットパッキングして詰め込むことで、多くの用途をカバーできます。
GraphicsStateCollectionによるシェーダーウォームアップが解決するのは、シェーダーコンパイル時のスパイクの問題です。GraphicsStateCollectionはUnity 6でExperimental(実験的機能)として追加されて、Unity 6.5で正式な機能としてリリースされました。
以前からあるShaderVariantCollection.WarmUpなどのウォームアップAPIはVulkan/Metalなどでは期待通り機能していませんでした。これはVulkan/Metalなどのシェーダーウォームアップの仕組みを知ると分かることですが、旧来のAPIには正しくウォームアップするために必要なパラメーターを渡すことができませんでした。そのため、Vulkan/Metalではウォームアップを完全に行うことはできませんでした。そこで、開発者はゲームプレイ中のスパイクを回避するため、画面暗転中に裏で描画するなどのワークアラウンド的なウォームアップを行う必要がありました。
GraphicsStateCollectionはこの問題を解決する新しいAPIです。GraphicsStateCollectionはシェーダーウォームアップに必要な頂点レイアウトなどの様々な情報をアプリ実行時に集めるための仕組みです。これらの情報を収集することで、完璧にシェーダーをウォームアップすることができます。しかし、GraphicsStateCollectionには情報の収集漏れなど、いくつかの新たな問題があります。これは、GraphicsStateCollectionの問題というよりは、UnityのShader Variant Stripに起因するものや、Vulkan/Metalなどの現在のモダンなグラフィックスAPIの仕様に起因しているものなど様々です。
Render Graphの登場でレンダリングパイプラインを構築するシステムは大きく変わりました。リソース依存性を定義することで不要なリソースを破棄でき、タイルベースGPUとの相性も高い新しいレンダリングシステムの登場です。
しかし、高いレイヤーのレンダリングシステムが一新されたからといって、よりハードに近いレイヤーの仕組みが変わったわけではありません。Render Graph、GraphicsStateCollection、RSUVが解決しようとしている問題は以前から抱えている問題です。
第1部では、Unity 6で導入された新しい機能、普遍的なもの、第1部3章「URPのFinalBlitPassを省略する方法」のように、明日からすぐに使えるTips的なものも紹介しています。
これらの内容が皆様の開発の一助になれば幸いです。