目次
GPU 最適化へのフォーカス

『The Book of the Dead』(BOTD)は、Unity のデモチームによって制作されました。これは HD レンダーパイプライン(HDRP)で実現可能なビジュアルクオリティを目の当たりにできる、リアルタイムでレンダリングされている短編アニメーションです。
HDRP は、Unity によって構築された、コンピュートシェーダーと互換性のある最新のプラットフォームをターゲットとするよう設計された、既成の忠実度の高いスクリプタブルレンダーパイプラインです。HDRP は、物理ベースのライティングテクニック、リニアライティング、そして HDR ライティング、さらにタイルやクラスターさらにディファードやフォワードを組み合わせて構成が可能なライティングアーキテクチャを使用します。
『BOTD』のアセットとスクリプトコードはすべて、アセットストアから入手できます。
このデモにおける目的としては、業界随一のクオリティで提供されている典型的なゲームタイトルでは当たり前になっている水準に匹敵するような、手に取り確かめられるような環境下で『BOTD』の世界を自由に動き回れようなエクスペリエンスをユーザーにインタラクティブな形で提供する、という所にありました。具体的には、Xbox One と PS4 で動作する『BOTD』をお見せしたいと考え、私たちはパフォーマンスの要件を 1080p、30fps 以上と決定しました。
これはデモであり、完成したゲームというわけではなかったことから、最適化の主眼はレンダリングです。
全編を通して、突然大量のパーティクルを生成したり、アニメーション化されたキャラクターが多数登場するシーンなどはなかったことから、『BOTD』のパフォーマンスはかなり安定しています。
Rob とデモチームは GPU 負荷において、パフォーマンスが格段に悪いビューを見つけました。それが上の画像です。
シーンで起こっていることは、特段他と何か変わったことがあるわけではありません。しかし、ここでは、「カメラのビュー内に何が入っているのか」が異なります。このシーンの処理を軽くすることができれば、デモ全体でのパフォーマンスの向上が見込めます。
このシーンのパフォーマンスが悪い理由、それは中心を覗き込む形でレベルの外観を捉えたビューであったためでした。そのせいで、相当に広い範囲の大多数のアセットがカメラの錐台に含まれてしまっていたのです。その結果、たくさんのドローコールが発生してしまっているのです。
まとめると、このシーンは次のようにレンダリングされています。
- HDRP を使用
- アーティスト作成によるテクスチャのほとんどは 1K ~ 2K のサイズのマップ。 4K のマップは少数
- 間接光にはベイクしたオクルージョンとベイクした GI を使用し、太陽からの直接光には動的シャドウを投影する 1 つの光源で対応
- どの時点においても大体数千回程度のドローコール発行(ドローコールとコンピュートシェーダーのディスパッチ)
- 最適化パスの開始時点では、このビューは GPU バウンドで、PS4 Pro での所要時間は約 45 ミリ秒
パフォーマンス上のボトルネックの発見

Rob とチームがステップごとに GPU フレームを見ていくと、パフォーマンスは以下のようになっていました。
- Gbuffer は 11 ミリ秒
- モーションベクターとスクリーンスペースアンビエントオクルージョンは、それぞれ 0.25 ミリ秒と 0.6 ミリ秒でかなり高速
- 動的ライトを使ったディレクショナルシャドウの投影からのシャドウマップが、桁外れに大きい 13.9 ミリ秒を消費
- ディファードライティングに 4.9 ミリ秒
- 大気散乱に 6.6 ミリ秒
上の図は、開始から完了までの GPU フレームの内容を示しています。
これを見ると、GPU フレームは 45 ミリ秒で、オレンジ色の 2 本の縦線はそれぞれの 30 fps と 60 fps を達成するための短縮目標を示しています。
では、このシーンのパフォーマンスを改善するためにチームが実行した 10 のことを見ていきましょう。
1.バッチの数は抑えるようにする
『BOTD』はデモであり、完全なゲームに必要なすべてのシステムに付随するスクリプトコードの複雑さはないため、CPU のパフォーマンスはチームにとってさほど問題ではありませんでした。
それでもバッチ数を低く抑えることに留意しておくのは、あらゆるプラットフォームに対して有益なアプローチです。プロジェクトでいずれかのビルトインレンダラーを使用している場合はオクルージョンカリング、中でも GPU インスタンシングを使うことで、これが実現可能です。パフォーマンスの向上に貢献することが確実という場合をのぞいて、コンソールでダイナミックバッチングを使用することは避けるようにしましょう。
SRP のどれかを使用されているのであれば、SRP Batcher を使用してバッチ数を管理することができます。SRP Batcher は一連のバインドとドローの GPU コマンドをバッチングすることにより、ドローコール間の GPU 設定を減らします。レンダリングで最大限のパフォーマンスを得るには、こういったバッチをできるだけ大きくしておくことが非常に大事です。同じシェーダーを使用する異なるマテリアルはいくつでも使用できるものですが、この目標を達成するためには、使用するシェーダーバリアントの数はできる限り少なくしておかなければなりません。
もう 1 つ、良い話があります。実は、このシーンを作るために使用されたアセットの数は、ごくわずかなのです。チームは質の高いアセットを使用し、うまく配置することで何度も使いまわしているように見えない精密なシーンを作成しました。
2.複数のコアを併用する
Xbox One と PS4 は両者共にマルチコアデバイスです。最高の CPU パフォーマンスを得ることを考えた場合、このコア全部が常に何らかの処理をおこなっているようにすべきと言えます。
Unity の新しい高性能マルチスレッドシステムである DOTS を使用すれば、今日の(そして将来的にも)利用可能なマルチコアプロセッサーをゲームで最大限に活用できるようになります。DOTS は、Entity Component System、C# Job System、Burst コンパイラーの 3つのサブシステムで構成されています。
DOTS パッケージの一部はプレビュー段階であるため、本番環境での使用はお勧めできません。
しかし、マルチコアは「Player Settings」->「Other Settings」の下の「Graphics Jobs」モードを通じて利用できます。
少数のバッチしか描画しない場合を除けば、「Graphics Jobs」を使用することにより、コンソールのほぼすべてのパフォーマンスの最適化を実現できます。これには次の 2 種類があります。
- PS4 と Xbox One 用 DirectX 11 で利用可能な「Legacy Jobs」
- 他のコアに作業を分散させることで、メインスレッドの負荷を軽減します。ただし、相当大規模なシーンにおいては、この処理が「レンダースレッド」(Unity
がプラットフォームホルダーのグラフィックス API
とやりとりするために使用するスレッド)のボトルネックとなることがあるので注意してください。
- 他のコアに作業を分散させることで、メインスレッドの負荷を軽減します。ただし、相当大規模なシーンにおいては、この処理が「レンダースレッド」(Unity
がプラットフォームホルダーのグラフィックス API
とやりとりするために使用するスレッド)のボトルネックとなることがあるので注意してください。
- PS4 と Xbox One 用 DirectX 12 で利用可能な「Native Jobs」(2019.3 では新規プロジェクトに対するデフォルト)
- できるだけ多くの作業を利用可能なコア間に分散します。これは大規模なシーンに絶好のオプションです。
マルチスレッドレンダリングとグラフィックスジョブの詳細については、こちらをご覧ください。
3.プラットフォーム固有の分析ツールを使用する
Microsoft と Sony から、CPU と GPU の両方についてプロジェクトのパフォーマンスを分析するための素晴らしいツールが提供されています。コンソールで開発を行っている方は、これらのツールを無料で利用可能です。早期にこれらのツールの使い方を習得しておいて、開発サイクル全体にわたって使用し続けれるようにしましょう。Xbox One 向けの Pix と PlayStation 向けの Razor Suite は、それぞれのプラットフォームで最適化を行う場合に不可欠なツールです。
4.ポストプロセッシングエフェクトのプロファイリングを行う
ポストプロセッシングエフェクトが大量のフレームレートを消費することがあります。これは多くの場合、主に PC 用に作成されたポストプロセッシングアセットをアセットストアからダウンロードすることで発生します。こういったアセットはコンソールで問題なく動作するように見えますが、実際はコンソール用には最適化されていません。
そういったエフェクトを適用する場合は、GPU での所要時間をプロファイリングし、ビジュアルクオリティとパフォーマンスの満足できるバランスが見つかるまで繰り返し調整するようにしましょう。その後は、もう放っておきしょう。調整した後はどのシーンでも固定コストとなり、使用できる GPU 帯域幅がどれだけ残っているかが判明しているからです。
5.(正当な理由がない限り)テッセレーションの使用を避ける

一般に、コンソールゲームのグラフィックスではテッセレーションを使用しないでください。ほとんどのケースでは、GPU 上でランタイムテッセレーションを行うよりも、アーティストによって作成された同等のアセットを使用する方が有効です。
しかし『BOTD』の場合は、テッセレーションを使用する正当な理由がありました。それは木の皮をレンダリングするためでした。
テッセレーションを適用したディスプレイスメントを使って、正しくセルフシャドウイングされる深いくぼみとごつごつしたディテールをジオメトリに加えることができました。これは法線マッピングでは行えない手法です。
樹木は『BOTD』の多くの部分で「主役」となるオブジェクトなので、テッセレーションの使用は理にかなっていました。LOD 0 と LOD 1 では、樹木に同じメッシュを使用して行われました。2 つの LOD の違いは、LOD 1 に切り替わる前にテッセレーションを適用されたディスプレイスメントのスケールを小さくして、その効果がわからなくなるようにすることだけです。
6.GPU で常に正常なウェーブフロントの占有率を確保することを目指す

ウェーブフロントは GPU 作業のパケットとして考えることができます。GPU へのドローコールか、コンピュートシェーダーのディスパッチをサブミットすると、その作業は多数のウェーブフロントに分割され、そのウェーブフロントは GPU で使用可能な全コンピュートユニット内のすべての SIMD に分散されます。
各 SIMD で一度に実行できるウェーブフロントの数には上限があることから、GPU 上で並列実行できるウェーブフロントの最大合計数はあらかじめ決められたものになっています。これらのウェーブフロントのうち、使用しているものの数が「ウェーブフロントの占有率」と呼ばれ、GPU の並列処理するための性能をどれだけうまく利用しているかを知る指標になります。
Pix と Razor では、ウェーブフロントの占有率を詳細に表示できます。上のグラフは、Xbox One 向けの Pix のものです。左側は、ウェーブフロントの占有率が良好な例です。下の緑色の領域は頂点シェーダーのウェーブフロントがいくつか実行されていることを示し、その上の青色の領域はピクセルシェーダーのウェーブフロントがいくつか実行されていることを示しています。
右側では、パフォーマンスの問題が発生していることがわかります。頂点シェーダーがかなり動いているのに比べ、ピクセルシェーダーのアクティビティが少なすぎるのです。これは、GPU のポテンシャルを十分に活用できていないということです。ここから、次の最適化へのヒントが得られました。
これはどのようにして起こるのでしょうか。この現象は、頂点シェーダーが動いているが、ピクセルが描画されていないときによく見られます。
7.デプスプレパスを利用する
Pix と Razor でさらに分析したところ、Gbuffer パスの間に多くのオーバードローが発生していることがわかりました。ここではコンソールでアルファテストしたオブジェクトを表示するときに顕著に悪く表れています。
コンソールでは、ピクセルのデータ破棄命令を出したり、ピクセルシェーダーのデプスに直接書き込むと、アーリーデプスリジェクションを利用することができません。これらのピクセルシェーダーのウェーブフロントは、ウェーブフロントによる処理結果が最後には破棄される場合でも実行されてしまいます。
この問題の解決策は、デプスプレパスを追加することでした。デプスプレパスは、非常に軽いシェーダーを使用して事前にデプスだけをレンダリングする方法です。こうすることで、これまで重い Gbuffer シェーダーバウンドがかかっていたデプスリジェクションを、より賢く行うための準備ができます。
HDRP には、すべてのアルファテスト済みオブジェクトのデプスプレパスが含まれていますが、必要に応じてデプスプレパスをフルに切り替えることもできます。HDRP を制御するための設定、どのレンダーパスを使用するか、どの機能を有効にするかはすべて、HD のレンダーパイプラインアセットで指定できます。
HDRP プロジェクトで HD のレンダーパイプラインアセットを検索すると、HDRP の挙動をすべて制御できるチェックボックスのセットが表示されます。
『BOTD』ではデプスプレパスの使用によって GPU のパフォーマンスを大幅に向上できたものの、一方で CPU に描画対象のバッチを追加するオーバーヘッドをかけてしまうことにもなるのは、注意してください。
8.シャドウマッピングのレンダーターゲットのサイズを小さくする

上述のように、シーンのシャドウマップは、シャドウを投影する 1 つのディレクショナルライトに対して生成されます。4 つの分割シャドウマップが使用され、当初は 32 ビット深度で 4K シャドウマップにレンダリングされていました。これは HDRP プロジェクトではデフォルトの設定です。シャドウマップにレンダリングするとき、ほとんどの場合、シャドウマップの解像度が制限要因となります。これは、Pix と Razor による分析で裏付けされました。
シャドウマップの解像度を下げることは、品質に影響を与える可能性はあるとはいえ、疑いの余地のない解決策でした。
そこでシャドウマップの解像度は 3k に下げることとしました。これはパフォーマンスに対して申し分なく受け入れられるトレードオフでした。また、デモチームは開発者が 16 ビット深度のシャドウマップにレンダリングできるようにするためのオプションを追加しました。このアプローチをご自身で試してみたい場合は、プロジェクトアセットをダウンロードしてください。
最後に、シャドウマップの解像度を変更したことで、ライトに関する設定も若干変更しなければなりませんでした。
この局面において、チームは縮小された新解像度で、もちうる限りのアイデアで最高のものにできるようにと模索していて、シャドウマップを修正したり、シャドウマッピングカメラを再配置したりするなどしていました。次に行うべきこととして、チームは一体何をしたでしょうか?
9.最もズームアウトされた最後のシャドウマップの分割を、レベルのロード時に 1 回だけ描画する

シャドウマッピングのカメラはあまり動かないので、この方法はうまくいきました。ちなみに、最もズームアウトされるカメラの分割は、通常、プレイヤーのカメラから最も遠い影をレンダリングするために使われます。
この方法を試した結果、品質の低下は見られませんでした。GPU のフレームレートの時間と CPU のバッチ数の減少の両方で節約できるため、とても有効な最適化であることが判明しました。
この一連の最適化が行われた後、シャドウマップの作成フェーズは 13 ミリ秒から 8 ミリ秒未満になりました。ライティングパスは 4.9 ミリ秒から 4.4 ミリ秒に、大気散乱パスは 6.6 ミリ秒から 4.2 ミリ秒になりました。
これが、チームがシャドウマッピングの最適化を行った後の状態です。PS4 Pro で 30fps で動作させるための目標値以内に収まりました。
10.Async Compute を活用する
Async Compute は、実用性に優れるコンピュートシェーダーを使って GPU の使用率が低い期間を最小限に抑えるのに役立つ手法です。PS4 でサポートされており、2019 サイクルで Xbox One でも利用できるようになりました。Async Compute には、Unity のコマンドバッファインターフェースからアクセスできます。これは SRP 専用ではありませんが、主に SRP での使用を意図した手法です。コード例は『BOTD』のアセットと、HDR PSOS で提供されています。
シャドウマッピングを使用した深度のみのフェーズは、GPU のポテンシャルをフルに活用していないという問題があります。Async Compute を使用すれば、コンピュートシェーダーをグラフィックキューと並行して動かすことができます。それによって、グラフィックキューが十分に活用していないリソースを利用することができます。
『BOTD』では、ディファードライティングの中でタイル化されたライトの一覧を収集するために Async Compute を使用しています。それらのほとんどは、HDRP のコンソール上のコンピュートシェーダーで処理されるほか、SSAO の計算にも使用されます。これらは両方とも、ウェーブフロントの使用率のギャップを埋めるためにシャドウマップのレンダリングとオーバーラップしています。
Async Compute を使った概念コードの説明については、Rob の Unite セッションを 35 分 30 秒からご覧ください。
