DirectXの話 第184

SSAO with Visibility Bitmask

23/05/06 up

Visibility Bitmask

今回はVisibility Bitmaskを利用したScreen Space Ambient Occlusion(SSAO)を実装してみました。
元は間接照明まで含めていますが、こちらの実装ではAO部分のみの実装となっています。

https://arxiv.org/abs/2301.11376

この技術はHorizon Based AO(HBAO)をベースにしたSSAO技術となります。
HBAOは半球を平面でスライスし、スライスごとに最大の遮蔽角度を求める手法です。

あとはこのスライスを180°で積分すれば遮蔽量を求められるという寸法です。
角度は対象ピクセルのビュー空間位置と周辺ピクセルのビュー空間位置から求めることが出来るので、スライスのタンジェント方向に深度をサンプリングしてビュー空間を求めて最大の遮蔽角度を求めます。

この手法は壁などの形状には良い結果をもたらしますが、厚みを持ったオブジェクトに対して遮蔽が強くなってしまう場合があります。

図の球が遮蔽物だとすると、その背後は当然遮蔽されていません。
しかし遮蔽角度はかなり大きくなってしまうので、大きく遮蔽されていると認識されてしまいます。
実際にはオブジェクトの裏側は遮蔽されていないので、大きく遮蔽されるのは困ります。

Visibility Bitmaskはこのような問題に対処することが可能です。
Visibility Bitmaskではスライスの半円を一定角度で分割し、その分割ごとにビットを割り当てます。
最終的には遮蔽されている角度のビット数を計数して分割数で割るという、比較的単純な手法です。

また、遮蔽されている角度を求める場合、一定の厚みがあると仮定して処理を行います。

この図では半円を8分割しています。
ここで、あるピクセルをサンプルした場合に遮蔽物に接触したとすると、そこに一定の厚みtが存在するものとします。
接触点Pfから視線ベクトルvの方向にtだけ移動した点をPbとし、このPfPbの範囲の角度を遮蔽角度としてマークします。
この図では左右方向に1サンプリングしかしていませんが、実際には複数サンプリングしていき遮蔽される角度を都度マークしていきます。

実装について

実装はペーパーに擬似コードがありますのでそちらを参考に実装しています。
ただし擬似コードにはバグがありますので注意してください。
また、私の実装では1スライスあたりを半円ではなく90°の扇形で処理しています。

サンプルコードはVisibility Bufferサンプルに追加しています。

https://github.com/Monsho/VisibilityBuffer

対象のコードはssao.c.hlslCalcVisibilityBitmaskOcclusionです。

Bitmaskのサイズは8としています。今回は90°を8分割することになります。
baseVecは半円の基準ベクトルとなります。このベクトルとビュー空間座標から求められるベクトルから角度を求めます。
forループ内はタンジェント方向へのサンプリング数だけ行います。サンプリング先が画面外に移動した段階で探索は終了です。
スライスのループは関数の呼び出し先です。

まずは各サンプリングごとにビュー空間座標を求めます。これをSfとします。
自身のピクセルのビュー空間座標viewPからSfのベクトルとbaseVecの内積からcosθが求められますので、ここからacosを使って角度を求めます。
角度を求める理由はBitmaskの分割が角度によって等分されているためです。
基準ベクトルとの角度が90°以上であれば処理を行いませんが、未満であれば定数の厚みcbAO.thickness分だけ視線方向にずらした位置をSbとし、Sfの時と同様に角度を求めます。
この2つの角度Tf, Tbからビット値を求め、maskに追加していきます。
最終的にはビット値をカウントし、Bitmaskのサイズで割れば完了です。

弱点

この手法はサンプリング数を増やせば妥当なBitmaskを得ることが出来ますが、サンプリング数が少ないと妥当なBitmaskを得られないことがあります。
特に部屋の隅などで発生しやすいです。
例えば以下のような状況を考えましょう。

サンプリング数、サンプリング範囲、視線の角度などによっては最初のサンプリングポイントが図のように対象位置より上部に移動してしまい、固定の厚みでは正常なBitmaskが得られません。
この問題に対しては、距離が遠いサンプリングポイントの場合は厚みを大きく調整する、最初のサンプリングポイントを隣接ピクセルで行うなどの対応も必要になりそうです。
今回のサンプルではこのような対応を行っていませんので、サンプリング範囲が広くサンプリング数が少ない場合には、下図のように角の部分に隙間ができてしまいます。

HBAOやGTAOと比べて逆にアーティファクトが目立ってしまうので、この問題は何らかの対応をしなければならないでしょう。

最後に

今回のサンプルではHBAOの実装も行っていますが、こちらはなんだか怪しい感じもしますので、あまり信用しないでください。
HBAOが必要であれば、NVIDIA社のHBAO+を利用しても良いかもしれません。

https://github.com/NVIDIAGameWorks/HBAOPlus

また、GTAOはIntel社が実装を公開しています。

https://github.com/GameTechDev/XeGTAO

パフォーマンス的には今回のサンプルではHBAOよりだいぶパフォーマンスが低いです。
理由としてはVisibility Bitmaskの方はGBufferからノーマルをサンプリングしているのが大きな要因です。
これのお陰でキャッシュヒット率が低く、VRAM Throughputが悪くなっています。
また、acosも問題がありますので、分割数に合わせたLUTを用意することも考慮したほうが良いかもしれません。
HBAO+のようなデインターリーブを利用するのもいいかもしれないですね。