UnityのビルトインレンダーパイプラインのForwardAddパスでちょっとした最適化がされていた話 Now in REALITY Tech #113
初めまして、アバターシステムチームのyamasakiです。
Unityのビルトインレンダーパイプライン(以降BRPと略)でProjectSettingsのPixel Light Countの値が2 以上の場合、ForwardAddパスは二つ目以降の動的ライトの計算が行われますが、このパスでGPUに対してちょっとした最適化が行われていたので、紹介したいと思います。
最適化に気づいた経緯
まず初めにUnityのBRPの複数ライトの計算の仕組みのおさらいを兼ねて、どういった経緯で最適化に気づいたのかを説明します。
以前、Scene中オブジェクトがピクセル毎に何個のLightの数が影響しているのか確認できるツール(以降ヒートマップツールと呼びます)を作ろうとしたことがあって、UnityのShaderのソースを調査していた事がありました。当時Unity初心者だった私の目論見としては、コンスタントバッファーに_(Directionl / Spot / Point)LightNum的な変数が宣言してあり、その数値を色に変換して表示してやれば解決するだろうと言うものでした。
しかし調査していくうちに連れて、その目論見は外れていた事がわかり、どうやらUnityではForwardAddパスなるものがあり、二つ目以降のライトの計算はこのパスで行われ、影響するライト毎にDraw Callが一つづつ増える(一つ一つがForwardAddパスに対応する)という事がわかりました。
であればForwardAddパスで、加算を使って値を入れて行けば、動的にライティングされるピクセルのヒートマップが作れるだろうと考え、まずは簡単にForwardBaseパスでPropertyで指定した色、ForwardAddパスで白を出力するようなShaderを作成しUnity上で確認してみました。
まずはForwardAddパスをまだ実装してないシーン
中央付近にPoint Lightが配置してあります。わかりにくですが、ブルーのCapsuleとグリーンのPlaneがPoint Lightの影響を受けるような配置となっています。
次の図がForwardAddパスで単純に白を返すような実装を入れたものになります。自分の予想(その次の図)としてはCapsuleとPlane全体が白く塗られてるだろうと思っていたので、パッとみた瞬間はバグかな?と思いましたが、Point Lightのライティングの計算としてはオブジェクトの全体を塗る(計算する)必要はなく、影響のある部分だけを計算すれば良いので何かしらの最適化がされているのだと考えました。(ここで一旦ヒートマップツールの制作はストップ)
調査結果
ここからが本題です。
具体的にどういった手法で無駄なピクセルをカリングしているのか、XcodeのGPU Frame Caputure機能を使って調査しました。その結果の一部が次の図です。
キャプチャーした結果からブルーのCapsuleのForwardAdd パスであろうと思われるDraw Callを見つけ、その付近のMetal APIを見てみると、Scissor Testを行っている事が確認できます。
GPU Frame Caputureの結果によりUnityのBRPのForwardAddパスでは、オブジェクト毎にライトの影響が出るであろう部分を含むRectを事前に計算し、Scissor Testを行う事で、その範囲外の無駄な部分のピクセルの計算を省いてGPUの最適化を行っている事が判明しました。
BRPにおける二つ目以降ライトの設置については、オブジェクト毎にForward Addパスが走るため、どうしてもCPUの負荷が高くなりがちで、有効にするのは敬遠されるのですが、GPUに関してはちょっとした最適化が行われているようです。
補足
Scissor TestがGPUのパイプラインでどの辺りで動いてるのかはそのGPU次第ですが、スマートフォンのGPUに限るとこの辺りの資料で詳しく説明されています。
補足2
今回はXcodeのGPU Frame Captureが描画コマンド確認しやすかったので、iPhoneで動作確認を行いましたが、Android(OpenGL ES 3.0、Vulkan)でも同じ結果になることを確認しています