Unityが発行する無料eBookの"Optimize Your Mobile Game Performance"を読み解いていこう!というこの企画も折り返し地点に到達しました。(前回はAssets編)
今回のGraphics and GPU optimization編は山場になるでしょう。2Dだろうが3Dだろうが、グラフィックスはゲームにとって最も重要な要素の一つです。特に3Dレンダリングは適切に行われないとゲームのパフォーマンスに大きな悪影響を与えかねません。
今回の章では、グラフィックやGPUに関する最適化テクニックが解説されていくので、しっかりと学んでいきたいです!
Graphics and GPU optimization
毎フレームごとにUnityはどのオブジェクトが描画されるべきかを決定し、Draw callを発行します。
Draw call(ドローコール)とは、オブジェクトを描画するためのグラフィックスAPIに対して行われるコール(命令呼び出し)で、batchとは一緒に実行されるDraw callのグループを意味します。
あなたのプロジェクトがより複雑になるにつれて、GPUの作業を最適化するパイプラインが必要になるでしょう。ユニバーサル・レンダーパイプラインは現在、シングルパスのフォワード・レンダラーを使用しており、モバイル・プラットフォームにおいて高品質のグラフィックをもたらします。(※2021.2以降、Deferred Rendererも使えるようになりました)
スマートフォンやタブレットに合わせる形で、コンソールやPCゲームで使われるのと同じ物理Lightingやマテリアルを使うことも出来ます。
以下のガイドラインが、あなたのグラフィックを高速化するのに役立つでしょう。
ドローコールをまとめよう!
同時に描画されるオブジェクトを一括化(batching)することで、それぞれのオブジェクトを描画する際に必要な状態遷移を最小化します。これによりレンダリングする際のCPUのコストを減らすことで、パフォーマンスを改善する方向に向かわせられます。
Unityはいくつかのテクニックによって、複数のオブジェクトを少数のbatchに融合させることが出来ます。
・動的一括化 (Dynamic Batching)
小さなメッシュにおいて、頂点をCPU上でグループ化し変換することが出来、一度にすべてを描画します。ただし、これは十分にポリゴンの少ないメッシュにのみ利用可能です。
Dynamic batcherは300頂点以上のメッシュを一括化することが出来ません。なので、有効にすると、batchingするために小さなメッシュを探して、毎フレームCPUの処理時間を無駄にしてしまうでしょう。
・静的一括化 (Static Batching)
動かないジオメトリにおいて、Unityは同じマテリアルを共有するメッシュに対するDraw callを減らすことが可能です。これはDynamic Batchingよりも効率的ですが、メモリ使用率が増加します。
・GPUインスタンシング
大量の同一オブジェクトがある場合、GPUを利用することで、より効率的にそれらを一括化します。
・SRP batching
ユニバーサル・レンダーパイプラインのAdvanced項目で、SRP batcherを有効化しよう。シーンによりますが、これにレンダリング時におけるCPUの処理を軽くすることが出来ます。
SRP batcher : Speed up your rendering
フレーム・デバッガーを使おう
フレーム・デバッガーは、個別のドローコール群から、どのように毎フレームの画が組み上げられるかを表示します。
これはシェーダーのプロパティに関するトラブルシューティングの際にかけがえのないツールで、あなたのゲームがどのようにレンダリングされるかを理解するのに役立ちます。
多すぎる動的ライトは避けよう
URPは旧フォワード・レンダラーに比べて、ドローコールの数を減らしています。(しかし、)あなたのモバイルアプリケーションにあまりに多くの動的照明を追加するのは避けましょう。
静的メッシュへの書き込みライトと同じように、動的メッシュに対して、シェーダーエフェクトやlight probeなどの代替案も考えましょう。
リアルタイム照明に関する、URPとビルトイン・レンダーパイプラインの機能的制限を確認してみてください。
影を無効化しよう
影の描画はメッシュ・レンダラーとライトごとに設定可能です。ドローコールを減らすために、可能ならば影を無効化しましょう。
キャラクター下にシンプルなメッシュを敷いて、そこに画像を適用することで疑似的な影を作ることが出来ます。別の手段として、改造シェーダーもあります。
ライト・マップに照明を焼き付けよう
グローバル・イルミネーションを利用するために、静的ジオメトリ向けの劇的な照明を加えよう。StaticメニューのContribute GIにチェックを入れることで、ライト・マップとして、照明情報を保存できます。
ベイクされた影と照明はゲーム実行中にパフォーマンスに大きな負荷をかけることなく描画されます。
プログレッシブCPUやGPUライト・マッパーでグローバル・イルミネーションのメイクを高速化することが出来ます。
ライトマッピングに関するマニュアルやLight baked prefab & other tips to get 60 FPS on low-end phonesを確認しましょう。
ライト・レイヤーを使おう
複数の照明がある複雑なシーンにおいて、レイヤーを使ってオブジェクトを分けよう。そして、カリング・マスクを指定することで、照明の影響を制限しよう。(※URPでも、2021.2からライト・レイヤーが使えるようになりました)
動く物体にLight Probeを使おう
Light Probeはシーン内の何もない空間における照明情報を保存し、高品質な照明を施します。
Light probeはSpherical Harmonicsを使い、動的照明よりも素早く計算することが可能です。
詳細度レベル(LOD)を使う
オブジェクトが遠くに動くにつれて、LODはよりシンプルな複数のモデルに切り替えることが出来ます。
Unity LeranのLODに関するコースがあるので、詳しくはここを見てください。
隠れたオブジェクトを除外するのにオクルージョン・カリングを使おう
ほかのオブジェクトの背後に隠れたオブジェクトでも、潜在的にはまだ描画されており、リソースを消費しています。
これらを取り除くのにオクルージョン・カリングを使いましょう。(※Occlusionとは遮蔽のこと)
カメラ視点外における視錐体カリングは自動で行われますが、オクルージョン・カリングは手動のベイク処理によって行われます。
Staticドロップメニューから、Occluder(隠す側)かOccludees(隠される側)にチェックをいれ、Window >Rendering > Occlusion Culling メニューからベイクします。
あらゆるシーンに適合するわけではないですが、多くの場合はこれでパフォーマンスが向上します。
モバイルのネイティブ解像度を避ける
スマートフォンやタブレットは年々進化してきていて、新しいデバイスの画面はより高い解像度を持ちます。
Screen.SetResolution(width, height, false)を使って、出力の解像度を下げ、幾分かパフォーマンスを回復させましょう。いくつかの解像度を実際に測定して、クオリティとパフォーマンスのバランスがもっとも良いものを見つけましょう。
カメラ利用の制限
複数のカメラ運用は、それが意味の有る無しに関わらず、オーバーヘッドを発生させます。レンダリングに必要なカメラ・コンポーネントのみを使いましょう。
低スペック・モバイルにおいては、各カメラごとに1ms、CPUの処理時間を消費するということに注意してください。
シェーダーをシンプルに!
ユニバーサル・レンダーパイプラインでは、モバイル向けにいくつかの軽量なLit, Unlitシェーダーがあらかじめ用意されています。
ゲーム実行中に大きな影響を与え得る、シェーダーの変数は出来る限り少なくするようにしましょう。
もしデフォルトのURPのシェーダーがあなたの要望に合わない時は、シェーダーグラフを使って、あなたのマテリアルの見た目をカスタマイズすることが出来ます。
上書き、アルファ・ブレンディングを最小に
不必要な透明、半透明の画像を描画するのは避けましょう。モバイル・プラットフォームにおいて、上書き(オーバードロー)やアルファ・ブレンディングの影響は非常に大きいです。
ほとんど見えない画像や視覚エフェクトを重ねるのはやめましょう。グラフィック・デバッガーのRenderDocでオーバードローの確認することが出来ます。
ポスト・エフェクトを制限する
全画面におけるブルーム効果のようなポストエフェクトは、パフォーマンスを劇的に低下させる恐れがあります。そのゲームタイトルのアート方針の中で、注意して使いましょう。
Renderer.materialに気をつける
スクリプト上でのRenderer.materialへのアクセスは、マテリアルを複製し、その新しいコピーへの参照を返します。これは既にあるマテリアルを含む、現状のbatchを壊してしまいます。
もし、一括化(batched)されているオブジェクトのマテリアルにアクセスしたい場合は、代わりにRenderer.sharedMaterialを使ってするようにしてください。
スキンメッシュ・レンダラーを最適化
スキンメッシュの描画は非常にコストが高いです。スキンメッシュ・レンダラーを使用している全てのオブジェクトにおいて、それが必要であるか確認してください。
もし、いくつかのタイミングにおいてのみアニメーションが必要ならば、BakeMesh機能を使ってスキンメッシュを静的なポーズに凍結し、ランタイム中はよりシンプルなMeshRendererに切り替えるようにしてください。
Reflection Probeを最小化せよ!
Reflection Probeはリアルな反射を生み出すことが出来ます。しかし、これは一括処理の面で、非常にコストが高いです。
低解像度のキューブマップ、カリングマスク、テクスチャ圧縮を使うことで、ゲーム実行時のパフォーマンスを改善しましょう。
まとめ
全部で4000文字を超え、これまでで最長の章だったのではないでしょうか。解説された項目も非常にたくさんありました。
ただし、多くの項目において、問題点とその解決案を軽く触れる程度で、詳細については各々更に調べていく必要があると思います。
とは言え、グラフィックにおけるパフォーマンス上の懸念点の全容を把握するには、この上ないガイドブックであり、これを公式でしかも無料で出しているのは非常にありがたいことだと思います。
この企画もこれで無事に半分を折り返したわけですが、かなり自分のためになっているのでやって良かったな~という感じです。この後の章も引き続きやっていきたいと思います!
次回はユーザー・インターフェイス編です!