UnityのSRP BatcherでCPU負荷を軽減する

SRPではSRP Batcherという描画負荷軽減テクニックが追加されています
強力な機能ですので調べてみました

## SRPってなに
ScriptableRenderPipeline
過去解説したのでこちらをどうぞ

https://qiita.com/tsune2ne/items/7e52ba8b15f965042359

簡単に言うとUnityのC#レンダリングパイプライン
URPもSRPの実装の一つ
SRP BatcherはSRP全般で使えるのでURPでもHDRPでも使える
Built-in RPでは使えません

## 基礎知識
https://light11.hatenadiary.com/entry/2018/06/06/001843
> レンダリングする際には、まずCPUが全てのレンダリング対象オブジェクトの情報を集めます。
> 
> 次にこれらのうち、マテリアルが同じだったりといったバッチング条件に合うものを一つのメッシュにまとめます。
> こうしてまとめたメッシュごとに、Batchという処理単位を作ります。
> 
> その後このBatchごとに、SetPass Call -> Draw Callの順に処理をしていきます。
> 
> SetPass Callとは、マテリアルの設定値をCPUがGPUに教える処理です。
> もし一回前にSetPassした時とマテリアルが同じであれば、SetPass Callは不要なのでスキップされます。
> 
> その後Draw Callが呼ばれます。
> Draw Callとは、CPUがGPUに、SetPass Callで設定された値を用いてメッシュを描画することを命令することです。

## SRP Batcherの概要
https://blog.unity.com/ja/technology/srp-batcher-speed-up-your-rendering

Built-in RPではマテリアルごとにGPUに送信してた
SRPではそこを見直してパラメータ部分だけまとめてGPUに送信するようにした

同一シェーダーのマテリアルを同一SetPassで描画してくれます
あくまでSetPassが減るだけなのでDrawCallの回数自体は変わりません

シェーダーバリアントが少なく、多くのマテリアルに分岐してるメッシュの描画を効率化するのが目的


## 簡単なサンプルで比較
実際にプロファイルしながら見てましょう
FrameDebugerを開きながらSRP BatcherをON/OFFします

### サンプルシーン
| 検証 | バージョン |
|:---:|:---:|
|Unity | 2020.3.26f1 |
|UDP | 10.8.1 |

<img width="800" alt="SampleScene.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/f3c2df96-d59d-01a5-4041-7818231ab328.png">

ShaderGraphで「DefaultLit」を作成し、マテリアルを3つ作成して色だけ変えてあります
このマテリアルをCube/Sphere/Capsuleに割り当てています

### FrameDebugerの変化
![FrameDebuger_ON_OFF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/c0255c12-32d6-4d39-5ed6-b46ea822aa33.png)

同一シェーダーの描画が「SRP Batch」としてまとめられています

### Statisticsの変化
![Statistics比較_BuiltinRP_SRPBatch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/596cbf04-005e-97ed-2733-c06b72db9e85.png)

SetPassが減っています
FPSやBatchesには変化がありませんでした

# SetPassが減ってなにがうれしいの？
SetPassは「マテリアルの設定値をCPUがGPUに教える処理」で、CPUの負荷が軽減されます

これはイコールFPS改善につながるわけではありません
FPSはメモリアクセスやGPUなど多彩な要因で数値が下がっていきます
SRP Batcherを使ってもFPSが変わらないならボトルネックは別の場所にあります

UnityはSRP Batcherの効果を計測するために専用のスクリプトを配布しています

https://github.com/Unity-Technologies/SRPBatcherBenchmark/blob/master/Assets/Scripts/SRPBatcherProfiler.cs

これを使って別のシーンを比較しました

### サンプルシーン
![SRPBatcher負荷テスト30ms.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/026066c2-1a9e-c8b9-c415-0a88e298bce4.gif)

ShaderGraphを7つのマテリアルにし、19x19の4種類のメッシュに割り当てました
またそれぞれは上下に移動し、RGBの移動する点光源によってライティングされています

### SRPBatcherBenchmark
![Profiler比較.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7680/a5a48eec-3463-ddd9-9cca-1cf5c830787c.png)

「CPU Rendering time」が0.51msから0.20msに減少しています
Global Main Loopは21.98msから2.14msに減少しています
期待通りの高速化ができました🎉

## ⚠SPR Batcherの注意点⚠
SRP Batcherは何にでも適用できるわけではありません

:::note warn
ShaderLabで対応するには考慮が必要です
・シェーダーバリアント部分をCBUFFERの定義にいれる必要がある
・HLSLで記述する必要がある
・使用するシェーダーバリアントはできるだけ少なくする必要があります
:::

URPやHDRPでデフォルトで提供されているシェーダーは対応されています
ShaderGraphも自動で対応してくれます

:::note warn
GPUインスタンシングとの共存はできません
:::

詳しくは[公式ドキュメント](https://blog.unity.com/ja/technology/srp-batcher-speed-up-your-rendering)を参照ください

# 他の高速化と何が違うの？
## DynamicBatching
https://docs.unity3d.com/ja/2019.4/Manual/DrawCallBatching.html

同じマテリアルを共有していて特定条件を満たしている場合、1つのDrawCallにまとめて処理する機能
DrawCall自体の処理が重くなってしまうなどデメリットになるケースも多数あるため
現在では非推奨となっています

## MaterialPropertyBlock
https://light11.hatenadiary.com/entry/2019/04/01/233503

コードでMaterialのパラメータを変更する場合、普通であればMaterialインスタントを増やしてしまう
色を変えただけでインスタント増やされるのは塵積で馬鹿にならないコストになりますよね
これでMaterialインスタントを増やさないで済むのがMaterialPropertyBlock
※Draw CallやSetPass Callが減るわけではない
※あくまでもコードで変更したときMaterialインスタンスを増やさないだけ

## GPUインスタンシング
https://light11.hatenadiary.com/entry/2019/09/25/220517

GPUインスタンシングでは同一マテリアルのメッシュを大量に描画する場合に高速化されます
SRP Batcherはマテリアルが違っても同一シェーダーなら高速化されるので
そこが大きな違いとなります


# 他参考資料

https://docs.unity3d.com/ja/2019.4/Manual/SRPBatcher.html

https://blog.unity.com/ja/technology/srp-batcher-speed-up-your-rendering

https://amagamina.jp/blog/srp-batcher/

https://light11.hatenadiary.com/entry/2021/07/15/201733

https://www.slideshare.net/UnityTechnologiesJapan/unity20182019