だらけ者だらけ

だらけ者だらけの遊び場

【UE4】GPUコストの高いHZBとはどんな機能?

※本記事へのフォーラムを立ち上げました。色んなご意見がありましたら、是非こちらのフォーラムでご指摘や議論をしていければと思います。

HZBの仕組みと負荷の高さについてまとめました(2014 advent calender )



発端: HZBはOffにしたほうが良い?

Oculus開発などのために、最近ではUE4でパフォーマンスを出す方法がよく議題にあがります。
そんな時によく

"HZBをオフにしてみては?"

という意見を聞きます。


例えば、EpicGames下田さんのこの記事では。。。

Oculus ConnectでのNickのプレゼンテーションでShowdownデモの最適化でHZBはOFFにしたと説明していましたが「r.HZBOcclusion 0」とコンソールコマンドを打つ事でOFFにできます。

http://qiita.com/junyash/items/3ba225d8a2f86046cd23


またUE4の公式でも、最適化のアドバイスの部分で。。。
Unreal Engine | GPU プロファイリング

オクルージョン カリング:HZB オクルージョンの負荷は常に高いですが、オブジェクト当たりの負荷は小さめです。r.HZBOcclusion を切り替えて、オンにしなくても大丈夫が確認します。

https://docs.unrealengine.com/latest/JPN/Engine/Performance/GPU/index.html

このように、最適化の第一手として、
"HZBをOn/Offにして負荷を計測し、高速な方を選ぶ。"
というのが通例になっています。
が、"HZBという機能は何をしてんの?"という日本語資料は、現時点ではあまりないようです。
今回はこのHZBについて簡単にまとめてみたいと思います。
作業効率が倍になったり、新しい表現ができるようになわるわけではないです。スッキリさせたい人向けです。
検証不十分のため誤りがあるかもしれませんが、よろしくお願いします。

アジェンダ: UE4のHZB(Hierarchical Z-Buffer)について

  1. HZBとはどんなデータか?
  2. HZBオクルージョンカリングの仕組み
  3. HZBはどうして負荷が高い?
  4. HZBが有効なシーンは?
  5. 終わり
  6. 備考. ScreenSpaceReflectionのBuildHZBは?

検証バージョンはUE4.6、またDX11の範囲のみ絞っております。
(基本的なアルゴリズムは同じなので問題無さそうです。)
(HZBとは本来はデータ形式ですが、UE4ではHZBを用いたオクルージョンカリングを"HZB"と言っているので、今回はHZB=HZBオクルージョンカリングとして説明させていただきます。)


1. HZBとはどんなデータか?

まず、HZBとはHierarchical Z-Bufferの略です。名前の通り、階層化されたZ-Bufferです。そういうと特殊なものに聞こえますが、つまりただのテクスチャのMipMapです。
UE4では、1フレーム前のZバッファからこのHZBというテクスチャを作成します。
(デフォルトは、一番下が512*256ピクセルで、8段です。)
1フレーム前を使って平気なのかと言うと、30fps/60fpsでは1フレーム前のデプスも現在のフレームのデプスもだいたい同じだろうという前提のもとで行っており、問題になるようなシビアなシーンは殆どないと考えられています。
f:id:tempkinder:20141216131535p:plain

ただしこのMipmapの作成は一工夫されています。UE4のHZBは、近隣のデプス値の中から、"一番遠いデプス値"を選んでMipMapを作成していきます。(下図)
f:id:tempkinder:20141216131536p:plain

このHZBを使って、後にオクルージョンカリングを行います。


2. HZBオクルージョンカリングの仕組み

前の節で作成したHZBを使って、オクルージョンカリングを行います。
オクルージョンカリングとは、前景のオブジェクトによって完全に見えないオブジェクトを前もって探しておき、それらをレンダリング計算から除外して計算コストを下げる手法です。
不透明な壁の裏に500万ポリゴンのキャラクタがいても、映らない事を判定して前もって省いておくので、(GPUの)レンダリングコストを減らしてくれます。

ではどうやって、あるオブジェクトが見えるか見えないかを判定するか?非常に単純な3工程なので以下で簡単に説明します。

2-1. オブジェクトのバウンディングボックスがスクリーンのどれくらいの範囲に描画されるかを調べます。

f:id:tempkinder:20141216131532j:plain

2-2. 1で調べた範囲のZ値を、前もって作って置いたHZBから読み込みます。これは、”バウンディングボックスの描画範囲で最遠のデプス値”となります。

このとき、バウンディングボックスを内包できるMipのレベルを調べて、そこからから読み込みます。
f:id:tempkinder:20141216131533j:plain


(備考)もしも半端なところにボックスがある場合、4近傍の周りのデプスを取って来て、それらの最遠のデプス値を使ったりもしてます。
f:id:tempkinder:20141216145945j:plain

2-3. "(HZBから読み込んだ)その範囲の一番遠いデプス" よりも "バウンディングボックスが完全に後ろにある場合" オブジェクトは絶対に映らないので、描画されない用フラグをつける。

f:id:tempkinder:20141216133004j:plain


まとめると以下の流れです。

HZBオクルージョン判定の流れ(SubmitHZB(のTestHZB))

  1. オブジェクトのバウンディングボックスがスクリーンのどれくらいの範囲に描画されるかを調べます。
  2. その範囲の最遠のZ値を、前もって作って置いたHZBから読み込みます。
  3. "(HZBから読み込んだ)その範囲の一番遠いデプス" よりも "バウンディングボックスが完全に後ろにある場合" オブジェクトは絶対に映らないので、描画されない用フラグをつける。

このようにして、どんな高ポリゴンのオブジェクトだろうと、バウンディングボックスとHZBテクスチャの比較のみで済むので、非常に低負荷にオクルージョン判定ができます。*1



じゃあ、なんで重たいんだよ?



3. HZBはどうして負荷が高い?~GPU⇒CPUへのデータ転送の重たさ。~

上で説明したとおり、HZBでやることは2つ。

  1. HZBを作る。(BuildHZB)
  2. HZBを使って、見えないオブジェクトを算出する。(SubmitHZB)

UE4内でこの2つの作業はBuildHZB, SubmitHZBと呼ばれます。

実際にProfileGPUの中身を見てみます。
f:id:tempkinder:20141216103619j:plain

この中のHZBの中を見てみると、確かにBuildHZB、SubmitHZBがあります。
f:id:tempkinder:20141216103526p:plain
こう見ると、HZBを作成しているBuildHZBは処理負荷が全くかかっていません。というか今回の場合、HZBの負荷の1%にもなっていません!
ミップマップを作っているだけなので、そこまで重たくなることは確かになさそうです。

処理負荷の99%は、SubmitHZBです。ここでSubmitHZBの中を見てみると。。
f:id:tempkinder:20141216104459p:plain
TestHZBというのが0.02出てきました。これは実はSubmitHZBの”見えないオブジェクトを算出する”というメインの部分です。やはりこちらも軽いのです。バウンディングボックスとテクスチャ(HZB)の値を比較しているだけですから。(ですので、大量にオブジェクトがあったとしても、この部分の負荷が跳ね上がりはしないと考えられます。)
ということでHZBのアルゴリズムの外で、98%以上の負荷がかかっています。
では何をやっているかというと。。

HZBのメインのボトルネックは、オクルージョンの判定結果をGPUからCPU(のメモリ)に転送しているからです。

というのが答えになります。

HZBの作成も、オクルージョンのテストも、どちらもGPUで行われています。ということは、CPU側に”どのオブジェクトが見えて、どのオブジェクトは見えない?”という結果を返してあげなければいけません。

図のように、オクルージョンの判定(TestHZB)もGPUで行われるため、GPU側はオクルージョンの判定結果をCPUに戻してあげないといけません。これが非常に重たい原因です。しかも、転送コストは一定なので、ユーザがアセットの作り方やレベルの作成方法などで削減できない部分です。


4. HZBが有効なシーンは?~Oculusには無理じゃね?~

うーん。。。。色々と調査していたのですが、具体的に判明しておりません。
実際、HZBによって沢山のオブジェクトがカリングされるようなシーン。。。があるのか微妙な気がします。(調査不足なのは否めませんが。)

というのも、この"カリング"という工程はHZBオクルージョンだけじゃなく、視野の外のオブジェクトを除外するフラスタムカリング、スタティックなオブジェクトの見える領域を前もって計算するPrecomputed Visibilityなど色々あります。深く検証はしていませんが、HZBよりも前に走る上記2つのカリングが非常によく効き、HZBまで至るオブジェクトはそんなにないのでは??と考えています。

動的に動く壁によって隠れる大量のキャラクタ。。。なのかとも考えましたが、ちょっと調べきれておりません。海外Forumでも有効な意見は発見できなかったので、みな効き目を模索しているところでしょうか。何れにせよ、動的なオブジェクトが大量に出てくる物量の多いシーンでこそHZBは効いてくると思われます。

しかし、断言できそうな事もあります。Oculusで75FPS(はたまた今後は90FPS)を出そうとすると、CPU/GPU共にかなりシビアに負荷を制御しなければいけません。
90FPS出そうとするならば、CPUの制限からも、そんなに大量のオブジェクトを投入できそうもありません。結果、HZBが効果的に効くシーンを、そもそも作れない可能性が高いです。
ということで、やはり最初の提案に至ります。

"HZBをオフにしてみては?(特にOculusするなら)"



5. おわり

読んで頂きありがとうございます。
f:id:tempkinder:20130423003103j:plain
HZBについて簡単にまとめてみました。
”名前は聞くけど、何してんの?”と思っていた方々が少しでもすっきりできたら幸いです。
ソースを読んで、面白いことなども色々発見できたので、それはまた後日改めてまとめようと思います!


次回はとげとげさん@火曜西く25a (@checkela) | Twitterさんの"UE4でミクさんに可愛く踊ってもらう方法(予定)"です!華のある投稿になりそうなので、いまから楽しみです!
Unreal Engine 4 (UE4) Advent Calendar 2014 - Qiita





備考: SSR(ScreenSpaceReflection)のBuildHZBとは?

r.HZBOcclusionのOn/Offに関係なく、SSR(ScreenSpaceReflection)をOnにすると、BuildHZBがあります。

実はSSRもHZBを使います。もしもHZBオクルージョンで先にHZBを作成していた場合、SSRで呼ばれるBuildHZBは何もしません。もしもHZBオクルージョンをオフにしてた場合、SSRを計算するタイミングでBuildHZBによりHZBが作成されます。いずれにせよ、BuildHZBはHZBを作成する作業なので、負荷はほとんどありません。

”あれ?HZBOcclusionをOffにしたはずなのにBuildHZBとかいうのがプロファイルに出てくる。。”と考えてた方は気にしないで大丈夫と思います。
SSRの内部で、高速かつ粗くRaycastを行うためにHZB必要としています。
ですが、HZBオクルージョンのようにCPUに結果を戻したりしないというか、そもそもSubmitHZBの作業はSSRでは全く走らないので問題ないということです。
それとは関係なく、SSRも重いですけど。。。


参考資料



https://answers.unrealengine.com/questions/24425/whats-submit-hzb-its-consuming-high-gpu-resous.html/
深度バッファのレンジと精度を最大化する

*1:正規化デバイス座標系への変換やどのMipレベルを選ぶかなどは勿論ありますが、高負荷とはいえません。