Zバッファだけ先に作ってオーバードローの負荷を減らす「Depth Prepass」

高橋啓治郎氏(以下、高橋):次は僕からいくつか技術的に気になったポイントを紹介したいと思います。技術的に複雑だけどサラッとしか説明されていなくて、説明がないとよくわからないアップデートポイントがいくつかあった気がしたので、それらをピックアップしてみました。まずは、いくつか増えたレンダリング方式のオプション、「Depth Prepass」、「Deferred Rendering」、「Forward+」を紹介します。

Depth Prepassからいきたいと思います。「What's New」を見ると「Depth Prepassができるようになりました」と書いてありますが、何やねんそれという話なんです。

大下岳志氏(以下、大下):確かに(笑)。

高橋:Depth Prepassは、URPのレンダラーの設定の中にDepth Priming Modeというものが増えていて、デフォルトだとたぶんDisabledになっています。この設定をAutoにしたりForced(無理やり常に使う)にしたりできるんです。

主な用途は、Zバッファだけ先に作ることで、オーバードロー(重ね描き)の負荷を減らすことです。それ以外にも細かい使い道はあるのですが、オーバードローの負荷を減らすのが主な用途と見ていいと思います。

では、オーバードローの負荷とは何か。(スライドを指して)例えばこういうシーンで、キューブを描画してから、その上にこの銅像を描画します。次にこの球体を表示という順で描画すると、重ね描きしたエリアがあるわけです。キューブの上に重ね描きしていますよね。

大下:そうですね、どんどん上に描いていますね。

高橋:いったん胸像を描いたにもかかわらず、その上にまた球体を重ね描きするので、シェーディングを行ったのに、そのシェーディングが結果の画像でまったく使われず、シェーディングの負荷が無駄になってしまう部分があるんです。それをここでは「オーバーフローの負荷」と表現しています。実は、これを減らす方法があるんです。

高橋:オーバードローの負荷を減らすために「Zバッファ」だけ先に作るというテクニックを使います。おさらいですが、URPで画面を描画する時、カラーの画像のほかに、カメラからの奥行きのDepth情報(深度情報)を保存しておくためにZバッファがあります。その中でピクセルのどっちが手前にあるかを判定して、重ね描きを行っていきます。

ふだんは、カラーのバッファとDepthのバッファは同時に構築を行っているので、普通にキューブ描画して、その上にこの胸像を重ねて、その上に球体を重ねる、というように重ね描きして最終的に絵作りをするわけです。

Depth Prepassをオンにすると先にZバッファだけ描画してしまいます。Zバッファができあがったところで、あらためてカラーの描画をやり直すという順序になっています。実際にシェーディングを行うカラーの画面を描画する時は、どのピクセルが一番手前にあるか確定した状態から描き始めることができるので、重ね描きでどうせ消されてしまうエリアは描画せずに、本当に一番手前にあるピクセルだけをシェーディングして描画するという処理ができます。

大下:一致しているところだけを描く。

高橋:そういうことですね。最近のGPUには、Depth Prepassを速くするための仕組みが入っています。Depth Prepassする時のノッペリとした、テクスチャも貼っていないしシェーディングをしてもいない、本当にDepthを書き込むためだけに単純化されたシェーダーを動かすと、通常の描画よりもかなり高速に動くような仕組みになっています。

つまり、Depth PrepassでZバッファを構築する部分の描画はものすごく高速に終わって、その後じっくりシェーディングしながらカラーの情報を構築していくことになるんです。

なので、Depth Prepassとは、最近のGPUに特化した描画の高速化の手法なわけです。

Depth Prepassで本当に速くなるのか?

高橋:本当にDepth Prepassで速くなるのか、ちょっと疑っていたのでベンチマークを行ってみました。(スライドを指して)すごく極端なセットアップをしました。VFグラフを使って10万ポリゴンぐらいの細長い板っきれを何重にも重ねて描画しました。

大下:深度がややこしそうなやつですね。

高橋:そうです。ものすごい回数の重ね描きが発生するという、意地悪で極端なシーンを組んでみました。解像度も高めの4Kで、デスクトップのGPU、GeForceのRTX2060 SUPERで描画してみたところ、Depth Prepassなしだとベンチマークは45fpsくらいしか出なかったのが、Depth Prepassありだと200fpsでした。

大下:ぜんぜん違いますね。

高橋:これは極端ですが、やはり速くなるんです。なので、デスクトップクラスのCPUでフォワードレンダリングを使って画面を描画して、さらに重ためのシェーダーをよく使っている場合は、Depth Prepassを使うとかなり効果があるのではないかと思います。

ですが、実はここが今日の最大の重要ポイントで、モバイル向けのGPUの場合、恐らくDepth Prepassはほとんど効果がありません。

大下:おおっと(笑)。

高橋:条件を変えて、2021年末に出たMacBookProのM1 Maxでテストしてみました。M1 MaxはAppleシリコンなので、モバイルで開発されたモバイルGPUのパワーアップ版が搭載されています。しかし、それだとDepth Prepassあり・なしの違いは見られませんでした。逆にポリ数を増やすと、Depth Prepassを余計に行う分、Depth Prepassありが重くなっていくという逆転現象が見られました。

大下:ええー。

高橋:コメントでも「ですよね」と言っている方がいますね。事情に詳しい方はわかると思いますが、実はモバイル向けのGPUには一番手前のピクセルだけをシェーディングするという特殊な機能が搭載されているんです。

要するに、もともとGPUの中でDepth Prepassみたいな処理をハードウェア的に行っているので、Depth Prepass行わなくてもちゃんと高速化されているし、Depth Prepassをオンにするとその分余計な負荷がかかって重くなる可能性があるので、モバイル系のGPUではDepth Prepassを使わないほうがいいかもしれないという結果になりました。

大下:なるほど。

高橋:iOSで使われているPowerVR系のGPUのほか、AndroidでいうARMのMaliのGPUなどでは、こういう傾向が見られるんです。例えば、Nintendo Switchに搭載されているTegraはNVIDIAのデスクトップの流れを汲むGPUなので、もしかしたらDepth Prepassをオンにしたほうが速くなるのかもしれません。

GPUの種類によってDepth Prepassの効果が変わってくるので、そのへんはオンにしたりオフにしたりして検証して「このプラットフォームでは速くなったな」と確認してからでないと使えないと思われている、複雑な事情を抱えた機能でした。なので、この情報を参考にしてみなさんのアプリでオンにするかオフにするかを決めてみてください。ということで、説明が必要な機能その1、Depth Prepassでした。

大下:確かに。

念願の「Deferred Rendering Path」

高橋:次もレンダリング方式に関するアップデートなんですが、URP 12ではDeferred Renderingに対応しました。

大下:ついに。

高橋:対応しましたというか、以前から開発していて完成しましたという感じです。

大下:そうですね。

高橋:(スライドを指して)URPのレンダラーのセッティングの中のレンダリングパスでForwardとDeferredが選べるようになっています。ここでDeferredを選ぶことによって、「Deferred Rendering Path」を使えるようになります。

Deferred Renderingは以前からあった機能なので詳しく説明するまでもないと思いますが、サラッとおさらいします。最初にGバッファと呼ばれるマテリアル情報やノーマル情報を書き込んだバッファを構築してから、それに対してライティングの処理を適用していくことによって画面を作り上げる「レンダリング方式」という機能です。

コメント欄には「待っていました」「念願のDeferred」という書き込みがあるので、URPのDeferred対応を待っていた方が多いんですかね。

大下:本当ですね。お待たせしました。

高橋:URP 12では、ようやくビルトインレンダーパイプライン、以前からあったUnityのレンダリングパイプラインにおけるDeferredと同等ないし、それ以上の機能が全部そろった状態、フィーチャーパリティになっているはずなので、Deferredを使ってくれた方も安心してURPに移行できる状態になっていると思います。なっていなかったらバグ報告をお願いします。

Deferredにすることによって、すべてのライトがきちんと当たるようになる

Deferred Renderingの特徴は、なんといってもライトをたくさん置けることです。特にURPのForward Renderingはライトの個数を制約しています。デフォルトで4個、最大でも8個まで一部のオブジェクトに当たるライトの数が制約されます。

ライティングを凝りたい場合にはちょっと厳しい条件になりますが、Deferredでは無制限。ライトの範囲を絞らないと負荷が上がってしまいますが、狭い範囲に当たるライトをたくさん置くような表現には非常に適したレンダリング方式だと思います。

(スライドを指して)テストをしてみましたが、Forward方式だと、このようにライトの1オブジェクトに当たるライトの個数が制限されています。今デフォルトを4個にしたままテストシーンをレンダリングしてみますが、このようにライトがあるはずなのにライトが当たってない領域ができてしまうんです。だからライトが移動すると、ライトとオブジェクトの関係性が変化してしまって、色パカみたいな現象が起きてしまいます。Deferredにすることによって、すべてのライトがきちんと当たるようになります。

このように多数のライトを運用できる方式、Deferredが使えるようになりましたが、Deferredもいいことばかりではありません。例えば、事前にGバッファを構築するための負荷が生じてしまったり、Deferred Renderingでは半透明の物体を処理できないので、Deferredが終わったあとにForwardで上書きする処理が走ったりします。

Forwardよりもさらに狭い範囲でライトを適用できる手法「Forward+」

ほかに、DeferredだとMSAAが使えないのが個人的にはつらいところだと思っていますが、実はそういった場合にもう1つの方法が用意されているんです。それが「Forward+」です。まだexperimental機能なので正式な機能ではありませんが、一応URP 12で試せるようになっているので紹介させてください。

Forward+は、Deferredとは違ったライト数の絞り込み手法です。(スライドに描き込みながら)このようにカメラが置いてあります。視錐台というか、フラスタムがあります。この視錐台をグリッド状に区切ります。例えば点光源がこう入っていったら、この点光源はこのグリッドに影響を与えているとか、スポットライトがこのように当たっていたら、スポットライトはこのグリッドにかかっているという情報を事前に処理しておきます。

そして、ポリゴンをレンダリングする時にその領域にかぶっている光源だけを、先ほど作った情報のリストの中から取り出してきて、その光源だけでシェーディングを行います。今からシェーディングを行おうとしているピクセルにかかっているライトがどれなのかを詳しく絞り込めるという高速化手法です。ライトが当たっている範囲だけにライトの処理が適用されるので、Forwardよりもさらに狭い範囲でライトを適用できます。

「Forward+」を有効化する方法

高橋:実際に、Forward+をURP 12で有効化した状態で、先ほどのテストシーンをレンダリングしてみました。スライドのように、Deferredの時と同じようにすべてのライトが正しくすべてのオブジェクトに適用された状態になっています。

大下:なるほど。

高橋:Forward+はまだexperimental機能なので、デフォルトでは有効化されていません。Project Settingsの中にあるScript Define Symbolsに「URP_ENABLE_CLUSTERED_UI」という文字列を定義することによって、隠されていたForward+の設定がUIに出現します。

大下:だいぶ隠されていますね。

高橋:だいぶ隠しキャラ的なことをされていますが、試してみたい方はこれを入力してください。そうすると、先ほどのForwardやDeferredを選ぶRendering Pathの設定の中で、Forwardを選んだあとに「Clustered(experimental)」というチェックボックスが出現します。これをオンにするとForward+が有効化されるので試してみてください。以上、Rendering Path関係の機能の紹介でした。

ライトが自由に使える、Forward+にMSAAを有効にした状態でライトの個数をいくらでも増やせるのはすごくおいしい機能なので、お試しください。

大下:ForwardはURPを軽量モバイル向けと思っている方が多いと思うので、この2つの機能の追加はURPの可能性がかなり広がりますね。

高橋:そうですね。特にライトの個数制限は表現を作り込むうえでつらいケースがあったと思うので、その壁を突破できれば表現上の制約がかなり取り払われるのではないかと思います。

大下:はい。

(次回へつづく)