今回は、Unityプロジェクトの最適化のヒントについて解説するシリーズの第3回目です。より少ないリソースでより高いフレームレートで動作させるためのガイドとしてご利用ください。これらのベスト・プラクティスを試したら、シリーズの他のページもぜひご覧ください:
- パフォーマンスを向上させるためのUnityプロジェクトの設定
- ハイエンドグラフィックス向けのパフォーマンス最適化
- 高度なプログラミングとコード・アーキテクチャ
- スムーズなゲームプレイのための物理演算性能の向上
ターゲットハードウェアの制限を理解し、グラフィックスのレンダリングを最適化するためにGPUをプロファイルする方法を理解する。GPUの負荷を軽減するために、以下のヒントとベストプラクティスをお試しください。
無料電子書籍では、他にも多くのベストプラクティスをご覧いただけます、 コンソールとPC向けにゲーム・パフォーマンスを最適化する.
GameObjectを画面上に描画するために、UnityはグラフィックスAPI(OpenGL、Vulkan、Direct3Dなど)にドローコールを発行します。ドローコールのたびにリソースを消費する。
マテリアルの切り替えなど、ドローコール間のステート変更は、CPU側でパフォーマンスのオーバーヘッドを引き起こす可能性がある。PCやコンソールのハードウェアは、多くのドローコールをプッシュすることができるが、各コールのオーバーヘッドは、それらを削減しようとする正当な理由があるほどまだ高い。モバイルデバイスでは、ドローコールの最適化が不可欠である。これはドローコールのバッチ処理で実現できる。
描画コールのバッチ処理は、このような状態の変化を最小限に抑え、オブジェクトのレンダリングにかかるCPUコストを削減する。Unityは、High Definition Render Pipeline(HDRP)またはUniversal Render Pipeline(URP)を使用して、いくつかのテクニックを使用して複数のオブジェクトをより少ないバッチに結合できます:
- SRPバッチ処理:Pipeline Assetの Advancedで SRP Batcherを有効にする。互換シェーダを使用する場合、SRP バッチャーは描画呼び出し間の GPU セットアップを削減し、マテリアルデータを GPU メモリ内で持続させます。これにより、CPUのレンダリング時間を大幅に短縮することもできる。SRPのバッチ処理を改善するために、最小限のキーワードでより少ないシェーダーバリアントを使用する。あなたのプロジェクトがこのレンダリングワークフローをどのように活用できるかは、SRPのドキュメントを参照してください。
- GPU インスタンシング同じメッシュとマテリアルを持つ同じオブジェクトが大量にある場合は、GPUインスタンス化を使ってグラフィックハードウェアでバッチ処理します。GPUインスタンスを有効にするには、Inspectorの Projectウィンドウでマテリアルを選択し、Enable instancingをチェックします。
- 静的バッチング:非可動ジオメトリの場合、Unityは同じマテリアルを共有するメッシュのドローコールを減らすことができます。これはダイナミック・バッチよりも効率的だが、より多くのメモリを使用する。Inspectorで、決して動かないすべてのメッシュをBatching Staticとしてマークします。Unityは、ビルド時にすべてのスタティックメッシュを1つの大きなメッシュに結合します。StaticBatchingUtilityクラスを使用すると、実行時に(例えば、非可動部品の手続きレベルを生成した後に)これらの静的バッチを作成することもできます。
- ダイナミックバッチング小さなメッシュの場合、UnityはCPU上で頂点をグループ化して変換し、それらを一度に描画することができます。ただし、十分なローポリメッシュ(各頂点300個以下、頂点アトリビュート合計900個以下)がない限り、これを使うべきではありません。そうでなければ、バッチ処理する小さなメッシュを探すのにCPU時間を浪費することになる。
バッチ処理を最大限に活用するには、いくつかの簡単な方法がある:
- シーンではできるだけテクスチャを使わない。テクスチャの数が少なければ、必要な独自の材料も少なくなり、バッチ処理が容易になる。さらに、可能な限りテクスチャ・アトラスを使用する。
- ライトマップは常に可能な限り大きなアトラスサイズでベイクしてください。ライトマップの数が少なければマテリアルの状態変更も少なくて済むが、メモリフットプリントに注意すること。
- 不用意に材料をインスタンス化しないように注意してください。アクセス にアクセスします。にアクセスすると、マテリアルが複製され、新しいコピーへの参照が返されます。これにより、すでにその素材を含む既存のバッチは破棄される。バッチオブジェクトのマテリアルにアクセスしたい場合は代わりに Renderer.sharedMaterialを使用してください。
- 最適化中にProfilerまたはレンダリング統計を使って、静的および動的バッチカウント数と、総描画コール数を監視してください。
詳細については、Draw call batchingのドキュメントを参照してください。
フレームデバッガを使用して、シングルフレームで再生をフリーズし、Unityがシーンを構築するプロセスをステップスルーします。そうすることで、最適化の機会を特定することができる。不必要にレンダリングしているGameObjectを探し、フレームごとの描画呼び出しを減らすために無効にする。
フレームデバッガの主な利点の1つは、描画コールをシーン内の特定のGameObjectに関連付けることができることです。これにより、外部フレームデバッガでは不可能な特定の問題を調査しやすくなる。
注:フレーム・デバッガは、個々の描画呼び出しや状態の変化を表示しません。詳細な描画コールやタイミング情報を得られるのはネイティブGPU プロファイラだけですが、フレームデバッガはパイプラインの問題やバッチングの問題をデバッグするのに非常に役立ちます。
詳しくはフレーム・デバッガーのドキュメントをお読みください。
フィルレートとは、GPUが1秒間に画面にレンダリングできるピクセル数のことです。あなたのゲームがフィルレートによって制限されている場合、これはGPUが処理できるよりも多くのピクセルをフレームごとに描画しようとしていることを意味します。
同じピクセルの上に何度も描画することをオーバー描画という。過剰描画はフィルレートを低下させ、余分なメモリ帯域幅を要する。オーバードローの最も一般的な原因は以下の通りである:
- 不透明または透明なジオメトリのオーバーラップ
- 複雑なシェーダー、多くの場合複数のレンダーパス
- 最適化されていない粒子
- 重なり合うUI要素
その影響を最小限に抑えるべきだが、オーバードローを解決するための万能のアプローチはない。その影響を軽減するために、以下のテクニックを試してみてください。
他のプラットフォームと同様に、コンソールでの最適化はしばしばドローコールのバッチを減らすことを意味する。ここでは、その助けとなりそうなテクニックをいくつか紹介しよう:
- 使用 オクルージョンカリングを使用して、前景オブジェクトの後ろに隠れたオブジェクトを削除し、オーバードローを減らします。そのため、GPUからCPUに作業を移すことが実際に有益であることを検証するために、プロファイラーを使用してください。
- GPUインスタンスは、同じメッシュとマテリアルを共有する多くのオブジェクトがある場合、バッチを減らすこともできます。シーン内のモデル数を制限することで、パフォーマンスを向上させることができます。芸術的に仕上げれば、反復的に見せることなく複雑なシーンを作り上げることができる。
- SRP Batcherは、Bindと Draw GPUコマンドをバッチ化することで、描画呼び出し間のGPUセットアップを減らすことができます。このSRPバッチングの恩恵を受けるには、必要なだけマテリアルを使用しますが、互換性のある少数のシェーダ(例えば、URPとHDRPのLitシェーダとUnlitシェーダ)に限定します。
カリングはカメラごとに行われ、特に複数のカメラが同時に有効になっている場合、パフォーマンスに大きな影響を与える可能性があります。ユニティは2種類のカリングを使用する:
- フルスタムカリングはすべてのカメラで自動的に実行されます。の外側にあるGameObjectを確実に ビューの外側にある GameObject がレンダリングされないようにします。
- レイヤーごとのカリング距離は、Camera.layerCullDistancesで手動で設定できます。これにより、小さなGameObjectをデフォルトのfarClipPlaneプロパティよりも短い距離で小さなGameObjectをカリングすることができます。そのためには、GameObjectをLayerに整理します。.layerCullDistances配列を使用して、32 個のレイヤーのそれぞれに farClipPlane より小さい値を割り当てます (farClipPlane をデフォルトにするには 0 を使用します)。
- ユニティはまず層ごとに淘汰する。カメラが使用するレイヤー上のGameObjectのみを保持します。その後、フルスタムカリングによって、カメラフルスタムの外にあるゲームオブジェクトが削除されます。
- フルスタムのカリングは、利用可能なワーカースレッドを利用する一連のジョブとして実行される。各レイヤーのカリングテストは短時間で済む(基本的には単なるビットマスク操作)。しかし、GameObjectの数が多ければ、このコストはかさむ。これがプロジェクトで問題になる場合、UnityのLayer/Frustumカリングシステムへの負担を軽減するために、ワールドを「セクタ」に分割し、Camera Frustumの外側にあるセクタを無効にするシステムを実装する必要があるかもしれません。
- オクルージョンカリングは、GameObjectがCameraに見えない場合、GameViewから削除します。他のオブジェクトの後ろに隠れたオブジェクトは、潜在的にまだレンダリングされ、リソースのコストがかかる可能性があります。オクルージョンカリングを使用して、それらを破棄する。
- 例えば、ドアが閉まっていてカメラが部屋の中を見ることができない場合は、部屋のレンダリングは不要です。オクルージョンカリングを有効にすると、パフォーマンスが大幅に向上しますが、ディスク容量、CPU時間、RAMの使用量も増えます。Unityはビルド中にオクルージョンデータをベイクし、シーンのロード中にディスクからRAMにロードする必要があります。
- カメラビュー外のフルスタムカリングは自動で行われますが、オクルージョンカリングはベイクド処理です。オブジェクトをスタティック、オクルーダー、オクルーディとしてマークし、ウィンドウ > レンダリング > オクルージョンカリングダイアログでベイクするだけです。
詳しくは、チュートリアルのオクルージョンカリングの使い方をご覧ください。
Allow Dynamic ResolutionCamera(動的解像度カメラを許可)設定により、個々のレンダーターゲットを動的に拡大縮小し、GPUの負荷を軽減することができます。アプリケーションのフレームレートが低下した場合は、解像度を徐々に下げてフレームレートを一定に保つことができます。
Unityは、パフォーマンスデータがGPUバウンドの結果フレームレートが低下しそうだと示唆した場合に、このスケーリングをトリガーします。このスケーリングは、スクリプトを使って手動で先制的に発動させることもできる。これは、アプリケーションのGPUを多用するセクションに近づいている場合に便利です。徐々に拡大縮小していけば、動的解像度はほとんど気にならなくなる。
サポートされているプラットフォームのリストについては、ダイナミック解像度のマニュアルページを参照してください。
試合中に複数の視点からレンダリングする必要があることがあります。例えば、FPS(ファースト・パーソン・シューティング)では、プレイヤーの武器と環境を別々の視野(FOV)で描くのが一般的だ。これにより、前景の被写体が背景の広角FOVを通して歪んで見えるのを防ぐことができる。
あなたは カメラスタッキングを使えば、複数のカメラ・ビューをレンダリングできます。しかし、カメラごとにかなりのカリングとレンダリングが行われる。有意義な仕事をしているかどうかにかかわらず、各カメラには何らかのオーバーヘッドが発生する。
レンダリングに必要なCameraコンポーネントのみを使用する。モバイルプラットフォームでは、アクティブなカメラは、何もレンダリングしていないときでも、最大1ミリ秒のCPU時間を使用する可能性がある。
オブジェクトが遠くに移動すると、LOD(Level of Detail)が調整されたり、より単純なマテリアルとシェーダーで低解像度のメッシュを使用するように切り替わったりします。これによりGPUの性能が強化される。
詳しくはUnity LearnのWorking with LODsコースをご覧ください。
ポストプロセッシングエフェクトのプロファイルを作成し、GPUにかかるコストを確認します。ブルームや 被写界深度のようなフルスクリーンエフェクトの中には高価なものもあるので、ビジュアルクオリティとパフォーマンスのバランスが取れるまで試してみる価値がある。
後処理は実行時にあまり変動しない。ボリュームオーバーライドが決まったら、ポストプロセッシングエフェクトを総フレーム予算の静的な部分に割り当てます。
テセレーションは、形状をそれ自身の小さなバージョンに細分化し、ジオメトリを増やすことでディテールを向上させることができる。Unityのデモ「Book of the Dead」の木の皮のように、テッセレーションが最も理にかなっている例もありますが、GPUに負荷がかかるため、コンソールではテッセレーションを避けるようにしましょう。
死者の書」デモについての詳細はこちら。
テッセレーション・シェーダーと同様に、ジオメトリ・シェーダーとバーテックス・シェーダーは、GPU上で1フレームにつき2回実行することができます。
GPU 上で頂点データを生成したり修正したりする場合は、特にジオメトリシェーダに比べて、コンピュートシェーダの方が良い選択になることがよくあります。この作業をコンピュートシェーダで行うことで、実際にジオメトリをレンダリングするバーテックスシェーダがより速く動作できるようになります。
Shaderのコア・コンセプトについてもっと知る。
GPUに描画コールを送信すると、その作業は多くの波面に分割され、UnityはGPU内の利用可能なSIMDに分散させます。各SIMDには、同時に実行できる波面の最大数がある。
波面占有率とは、最大波面数に対して現在使用されている波面数のこと。GPUのポテンシャルをどれだけ引き出せているかを測るものだ。コンソール開発用のプロファイリングツールは、波面占有率を詳細に表示する。
UnityのBook of the Deadの上の例では、頂点シェーダの波面は緑色で表示され、ピクセルシェーダの波面は青色で表示されます。一番下のグラフでは、ピクセルシェーダーがあまり活動せずに、多くの頂点シェーダーの波面が現れている。これはGPUが十分に活用されていないことを示している。
ピクセルにならないバーテックスシェーダー作業をたくさんしているなら、それは非効率を示すかもしれない。波面占有率が低いことが必ずしも悪いわけではありませんが、シェーダーの最適化や他のボトルネックのチェックを始める際に使える指標です。例えば、メモリや演算処理でストールが発生した場合、占有率を上げることでパフォーマンスを向上させることができる。一方、飛行中の波面が多すぎると、キャッシュのスラッシングを引き起こし、パフォーマンスが低下する可能性がある。
GPUが十分に活用されていない間隔がある場合、非同期コンピュー トを活用して、コンピュートシェーダー作業をグラフィックスキューに並列に移動させる ことができます。たとえば、シャドウマップ生成中、GPUは深度のみのレンダリングを行います。この時点では、ピクセルシェーダーの作業はほとんど行われず、多くの波面は占有されないままである。
一部のコンピュートシェーダー作業を深度のみのレンダリングと同期させることができれば、GPUの全体的な使い方が良くなります。未使用の波面は、SSAO(Screen Space Ambient Occlusion:スクリーン空間周囲オクルージョン)や、現在の作業を補完するあらゆる作業に役立つ。
Uniteのハイエンドコンソール向けパフォーマンスの最適化に関するセッションをご覧ください。
これまでで最も包括的なガイドの1つで、PCとコンソール向けにゲームを最適化する方法について、80以上の実用的なヒントを集めている。サクセスとアクセラレート・ソリューションズの専門エンジニアが作成したこれらの詳細なヒントは、Unityを最大限に活用し、ゲームのパフォーマンスを向上させるのに役立ちます。