Unityパフォーマンス最適化シリーズ――レンダリングモジュール

4年前、Unityの各主要なモジュールのパフォーマンス最適化知識（初心者向け版）を1つずつ説明しました。近年、エンジン自体、ハードウェアデバイス、作成基準などのアップグレードに伴って、UWAは引き続き規則や方法を更新して、各開発者に提供しつつあります。「アップグレード版」のパフォーマンス最適化マニュアルとして、「Unityパフォーマンス最適化シリーズ」はより多くの開発者が利用できるように、シンプルでわかりやすい表現を心がけています。今回、レンダリングモジュールに関連する知識を共有します。

モバイル端末の最適化というと、レンダリングは避けて通れない課題です。パフォーマンスコストの大部分として、ほとんどすべてのゲームは、シーン、オブジェクト、および特殊効果のレンダリングから切り離せません。 如何に優れたシーンの視覚効果とスムーズな実行の間に、最適なバランスをはかるか、プランナー、アーティスト、プログラマーにとって常に悩みの種でした。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/440ce95a-e634-c6f6-140c-5423bd07dd38.png)

---
##一、レンダリングの効率を左右する最も基本的な2つのパラメーター：DrawCallとTriangle
###1、DrawCall
GOTオンラインのOverviewモードでは、レンダリングモジュールにDrawCall曲線が表示され、そこには具体的なDrawCall数とBatch数が確認することができます。下図のように：
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/c67754c1-bf4c-33a2-60f2-439dc1b0b89b.png)
現時点ではミドル・ローエンドデバイスにおいてBatchの主体範囲（5％〜95％）を[0,250]以内に制御することをお勧めします。

Unityでは、DrawCallとBatchを区別する必要があります。1つのBatchには複数のDrawCallがあります。下図のFrameDebuggerでは、2つのデフォルトParticleSystemが1つのBatchにバッチ処理処理されます。このように、1つのDynamic Batchには2つのDrawCallがあることがわかります。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/aaa5504e-1145-30bd-1f67-17d7b3454319.png)
Batchを減らすには、動的バッチ処理、静的バッチ処理、GPU InstancingとSRP Batcher 4つの方法があります。UWA Day 2020では、参考内容として、DrawCallとBatchとの関係、及び4つのバッチ処理使用詳解を共有しました。[「Unityモバイルゲームプロジェクト最適化のケース分析（上）」(中国語注意)](https://edu.uwa4d.com/course-intro/1/197)

###2、Triangle
通常、Triangleのパッチ数が多いほど、レンダリングに必要な時間が長くなります。したがって、レポートにはTriangleの使用状況が記載され、さらに半透過と不透過という区別があります。 一般的に、LODツールを使用してシーンにあるパッチ数を減らし、それによってレンダリングのコストを減らすことをお勧めします。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/279585cf-e73f-7393-416a-19d9bf0e8d17.png)
ここでのパッチ数は、現在のフレームのシーンモデルのパッチ数ではなく、現在のフレームでレンダリングされたパッチ数であることに注意してください。この数値は、モデルのパッチ数だけでなく、レンダリングされたパッチ数にも関わります。たとえば、シーンにおけるメッシュモデルのパッチ数は10,000であるが、使用するShaderに2つのレンダリングPassがある場合、または2つのカメラが同時にレンダリングしている場合には、Triangleの数値は20,000になります。

---
##二、Camera.Render関数スタックの分析
レンダリングモジュールの最適化において、Camera.Render関数の具体的なスタックを通してパフォーマンスのボトルネックを特定するのは非常に効果的な方法です。これらの関数は、実機であろうかGOTオンラインレポートであろうか、「コードの実行効率」で確認できます。 最適化する際によく見られる関数は次のとおりです：
###１、RenderForward.RenderLoopJob
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/02932c7b-7b88-b74d-6151-0f30a6c01cd2.png)
Camera.Renderの展開スタックにおいて、RenderForward.RenderLoopJob自身のコストが比較的に高いのは、Batchの数量が多いためです。
###２、Culling時間が長い
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/880149eb-ae72-0c01-1208-568daf099e76.png)
一般的に、Cullingが10％～20％の範囲で時間をかけるのは合理的です。Culling時間が長すぎる場合、以下の点からチェックできます：
>１）Culling時間は、シーンにおけるGameObjectオブジェクトの数量と高い相関関係を示しています。そういう場合、開発チームはシーンの作成方法を最適化することをお勧めします。また、シーンにおいてオブジェクトが多いからCulling時間が長くなっているかどうかことに注意します。Culling時間を最適化する方法として、動的ローディング、ブロック表示、またはCulling Group、Culling Distanceなどがあります。
>
２）プロジェクトでマルチスレッドレンダリングを使用している同時にOcclusion Cullingが有効になっていると、子スレッドに過度の圧力を加え、全体的なCullingが長くなることを引き起こします。

Occlusion Cullingはシーンのオブジェクトでオクルージョン関係を計算するため、Occlusion Cullingを有効にするとレンダリングコストが減りますが、自体のパフォーマンスコストも注目に値し、必ずしもすべてのシーンに適しているとは限りません。この場合、開発チームは、Occlusion Cullingの一部を選択的に無効にして、レンダリングデータの全体的なコストをテストして対比することをお勧めします。そのあと、この機能を有効にするかどうかを決定します。

###３、Render.Mesh
Render.Meshは、バッチ処理できないレンダリングコストに対応します。そのコール回数は対応するBatchの数量である。下図では、Render.Meshへのコール回数が269であることがわかります。これは、シーンにおいて、269個の不透過なオブジェクトトがバッチ処理されていなく、数量が多いことを示しています。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/7e39897b-71ff-bca3-8760-fb47617fa485.png)
Render.Meshコストが高いのは、一般的にはバッチ処理できないオブジェクトトが多すぎるからです。これは以下の点から最適化します：

>１）不透過なレンダリングキューに対し、Materialの冗長をチェックすることをお勧めします。例えば、インスタンスが異なるため、元々同じMaterial Sphereがバッチ処理できなくなったら、UWAのオンラインAssetBundleを利用し、AssetBundleにおけるMaterial冗長をチェックできます。
>
２）半透過なレンダリングキューについては、非NGUIとNGUIを区別する必要があります。NGUIの状況では、Render.MeshのコールはUIのDrawCallによって引き起こされました。Render.Meshのコール回数が多いことは、UIのDrawCallの高いことを表します。アトラスが適切にパッケージ化されていないかどうかを確認する必要があります。

非NGUIの状況では、半透過のオブジェクトトが差し挟みする現象があるかどうかを考える必要があります。RenderQueueを調整して、同じマテリアルのオブジェクトトを増やしてからバッチ処理します。
###４、ParticleSystem.ScheduleGeometryJobsとParticleSystem.Draw
1) ParticleSystem.ScheduleGeometryJobsとは、メインスレッドが子スレッドがParticleの位置を計算してからCullingすることを意味します。 多くの場合、戦闘インターフェースのコストは高くなります。
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/970ad298-1731-58f0-5ebf-87440f027610.png)
この関数の最適化に対し、開発チームはミドル・ローエンドデバイスでパーティクルシステムの複雑度をできるだけ軽減することをお勧めします。また、不必要なパーティクルシステムScheduleコストを削減するために、事前にレビューを通してカリング処理し、レビュー以外のパーティクルシステムにDeactiveを実行します。

2) ParticleSystem.Drawのコール回数はパーティクルシステムのDrawCallの数量に対応します。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/ccd760b8-39c3-6f70-6010-e7d3b390db56.png)
もしこの関数のコール回数は多すぎると、開発チームはパーティクルシステムの数量を減らすことをお勧めします。またUWA実機のテストレポートにおいて、「メモリ管理―具体的なアセット情報―パーティクルシステム」にあるリストを参考にして、さらな分析や最適化を行うことができます。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/a23da659-36e0-d619-a8a3-f86ceafa721e.png)
さらに、TextureSheetAnimationを使用するか、Order in Layerを修正するかの方法でパーティクルレンダリングの差し挟みを減らし、バッチ処理の可能性を高めることで、DrawCallを減らすことができます。

###５、Shader. Create GPU Program
当APIのCPU使用率は、Shaderが初めてレンダリングされるときの使用時間です。その使用時間は、レンダリングShaderの複雑度に関連しています。

下の図から、特定のフレームで、Shader.CreateGPUProgramの使用時間が203.87msに達し、それでゲームのフリーズを引き起こします。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/785e7bcc-7d5c-0350-c952-247f4ea5ce71.png)
この点で、ゲームの実行中にこのAPIコールがトリガーされないように、さらに局部のCPU使用時間が長いことを避けるため、ShaderVariantCollectionを介してShaderをプリロードし、完成後にShaderVariantCollection.WarmUpを介してShader.CreateGPUProgramをトリガーし、このSVCをキャッシュすることができます。

 

下記の情報を参考にしてください。
[「Shaderバリアントコレクションとパッケージコンパイルの最適化のアイデア」(中国語注意)](https://blog.uwa4d.com/archives/USparkle_Shadervariant.html)
[https://answer.uwa4d.com/question/5da86670e84db43d6efbda72(中国語注意)](https://answer.uwa4d.com/question/5da86670e84db43d6efbda72)

---
##三、マルチスレッドのレンダリングを有効にする
マルチスレッドのレンダリングを有効にすると、マインスレッドのレンダリング時間が大幅に短縮されます。そのため、有効にすることをお勧めします。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d3285996-1991-51a6-d001-47f4549eb1e5.png)
ただし、オンラインレポートのおいて、CPU使用時間にメインスレッドの使用時間のみをカウントすることで、マルチスレッドレンダリングが有効になっているバージョンだったら、レポートにはメインスレッドしかの使用時間が表示されていません。これはレンダリングのボトルネックに対する分析に役立ちませんから、注意してください。 したがって、内部テストをする時に、2つのバージョンを送信することをお勧めします。1つはReleaseバージョンのレンダリング時間の参考として、マルチスレッドを有効にします。もう一つは、レンダリングのボトルネックを精細に分析するため、マルチスレッドを有効にしません。

---
##四、GPU Instancing
GPU Instancingを使用すると、同じグリッドの複数のコピーを一度にレンダリングできますが、バリエーションを増やすため、各インスタンスに異なるパラメーター（たとえば、ColorやScale）を設定することができます。建物、木、草などのシーンに繰り返して現れるものをレンダリングする場合、GPU Instancingは各シーンのDrawCallの数を効果的に減らし、レンダリングパフォーマンスを大幅に向上させることができます。

しかし、GPU Instancingを使用する場合、以下の点に注意してください。
>1、互換性のあるプラットフォームとAPI
2、レンダリングされたインスタンスのメッシュはマテリアルと同じです
3、ShaderはGPU Instancingをサポートします
4、SkinnedMeshRendererをサポートしていません

特殊なケースでは、多数の半透過オブジェクトトのGPU Instancingレンダリング時間は高い使用時間をもたらします。これについては、UWA DAY 2019のコース『Unityエンジンレンダリング、UI、ロジックコードモジュールの量的分析と最適化方法』において、具体的な解説が記載されていました。

---
##五、SRP Batcher
ますます多くのチームはレンダリングパイプラインとしてURPを使用し始め、それによってSRP Batcherのバッチ処理範囲を大幅に拡大させ、レンダリング効率を向上させています。URPを使用する場合、レンダリング関数スタックは次のようになります：
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/6f7411db-6ac7-daa6-859e-2a364ea54e8e.png)
しかし、SRP Batcherを使用する際、なお以下の点に注意してください：
>
1、ShaderはSRPと互換性がある必要があります
2、SRP Batcherは、パーティクルシステムを一時的にサポートしていません
3、ShaderバリアントはDrawCallのバッチ処理を中断する可能性があります

**以上はレンダリングモジュールは最適化する際に注意すべきてんである。如何に操作すると、プロジェクトの実際な状況に基づき、同時にUWAサービスと結び、素早くパフォーマンスのボトルネックを特定できます。**

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

今なら、UWA GOTローカルツールが**15日間に無償試用**できます！！
よければ、ぜひ！

UWA公式サイト：https://jp.uwa4d.com
UWA GOT OnlineレポートDemo：https://jp.uwa4d.com/u/got/demo.html
UWA公式ブログ：https://blog.jp.uwa4d.com