DirectXの話 第122回

縮小深度バッファを利用したオクルージョンカリング

今回は縮小深度バッファを利用したオクルージョンカリングをやってみました。

基本的には高速化のために使用される技術ではありますが、今回のサンプルでは速くなってません。むしろ遅くなってます。

まあ、色々問題はあるのですが、とりあえず形になったので公開します。

オクルージョンカリングとは遮蔽されているオブジェクトのカリングを行う技術です。

実はオクルージョンカリングは誰もが当たり前のように使っている技術で、深度テストによるピクセルの棄却はまさにオクルージョンカリングです。

深度テストによるオクルージョンカリングは確かに高速化に必要な手法ではあるものの、もっと早い段階でカリングできるならその方がいい、という場面も結構あります。

つまり、モデルの描画そのものをカリングできれば描画をかなり高速に行えるようになる場面もあるわけです。

例えば大量のゾンビが街中を徘徊している状況では、建物などの影に隠れているゾンビを描画する必要はありません。

しかし、見えているかいないかをどのように判断すべきでしょうか?

思いつく方法と言えばPotentially Visibility Sets(PVS) でしょうか。

狭い建物内や複雑に入り組んだ街などで使いやすい技術です。

部屋などをポータルで分割し、ある区画から別の区画が見えるかどうかを予め計算して求めておく方法です。

Aの部屋からBの部屋は見えるが、Cの部屋は見えないという状況があったとするなら、ゾンビがCの部屋にいる間はカリングし、Bの部屋に入ったら描画するという感じにします。

この手法の弱点はポータルを適切に設定し、適切な情報を事前に作成しなければいけない点、そして、静的なシーンでなければ使えない点です。

壁や建物を破壊できるようならそれらが破壊されたことにより視界が変わってきます。

そうするとPVSの情報は使えなくなってしまい、何らかの方法で更新しなければ無意味になってしまいます。

そして、オープンフィールドで見晴らしのいい場所ではあまり効果が得られません。

まばらに建物が建っている状態ではポータルの設定が難しいでしょう。

今回の手法はそのような事前の計算を不要とする技術です。

そのために、タイトルにあるように縮小深度バッファを利用します。

今回使用した縮小深度バッファは縦横それぞれ1/8、1/64の解像度のバッファです。

縮小した場合の深度値は最も奥、つまり、最も大きな値を採用するようにしています。

この深度バッファをどうするのかというと、この深度バッファの値とオブジェクトのAABBを比較し、完全に遮蔽されているなら描画をしないようにするわけです。

図にすると下のような感じです。

適当な図で申し訳ないですが、理屈は簡単なので問題ないでしょう。

図のグリッドが縮小深度バッファです。

赤で表示されたオブジェクトAのAABBは深度の最小値(つまり手前側のZ値)が0.7です。

深度バッファにはこれより大きな値、つまり奥にあるピクセルが存在しません。

つまり、このAABB内にはオブジェクトAより奥にあるピクセルが存在せず、オブジェクトAは完全に隠れている状態になります。

そのため、オブジェクトAは描画する必要がなくなり、棄却されます。

逆にオブジェクトBはこれよりも奥にあるピクセルがいくつか存在します。

もちろん、フル解像度のシーンでは完全に消えてしまう可能性も否定は出来ませんが、少なくとも縮小深度バッファ上では完全に遮蔽されていません。

そのため、オブジェクトBは描画されます。

今回はこの手法を Compute Shader にやらせました。

CPUを介さずに処理をする方が速いからです。

まあ、サンプルはこの処理をする方が遅いんですが、あとで言い訳もしておきます。

コードはここでは提示しませんが、サンプルコードを見れば何をやっているのか明らかでしょう。

さて、今回のサンプルではカリングするオブジェクトとしてポイントライトを選択しました。

サンプルでは512個のポイントライトが画面上に飛び回っています。

適当にランダム配置されたポイントライトは頂点データとして座標、カラー、減衰等の係数をそれぞれ1つずつ持っています。

これを Geometry Shader を使って一気に描画しています。

オクルージョンカリングを使用しない場合は全てを描画していますが、オクルージョンカリングを使用すると不要なポイントライトをジオメトリシェーダで棄却するようにしています。

しかし、残念なことに少々棄却した程度では高速にはならないようです。

理由はいくつかあるのですが、やはり Compute Shader での計算、とくにテクスチャのフェッチが重いようです。

ただ、共有メモリを利用することでテクスチャフェッチは高速化できるかもしれません。

DirectX11では共有メモリを32KB持てるはずなので、16bit × 100 × 75 は乗せることが出来るはずです。

共有メモリを使った方が速いのは以前にやったSSAOでも実証済みです。

今回のサンプルではやってませんが、そのうち試してみたいと思います。

最後に個人的な感想として、この技術は使い物になるのかどうかという話。

まず、今回の実装方法ではあまり効果が得られないだろうと感じました。

ピクセルシェーダが相当重いのであればまだしも、今回のライト計算はそれほど重くないので効果が薄いです。

やはり、動的なオブジェクト、例えば人間や箱、岩などのオブジェクトに対してなら効果が出るかもしれません。

特にDeferred Lighting を行う場合、ジオメトリを最低2回描画しなければなりません。

描画セットアップのコストも考えると馬鹿に出来ないレベルでしょう。

相当量の動的オブジェクトを描画し、しかも半分程度をこのカリングで棄却できるのであればやる価値はあると思います。

実を言えば、この技術を実装したゲームはすでにあります。

Crytekの『Crysis2』とDICEの『Battlefield3』です。

それぞれ微妙に異なるアプローチで、今回の実装とは別物なのですが、基本的な考え方は同じです。

『Crysis2』では縮小深度バッファとして前回のバッファを使用しています。

多分ですが、静的な不透明オブジェクトを描画した状態の深度バッファを縮小して保持しておき、次のフレームでそれを使用しているものと思われます。

もちろん、前回の状態からカメラは動いているので、Temporal Coherence の要領で現在フレームの座標を求める形になるかと思います。

『Battlefield3』では簡易ポリゴンモデルを非常に小さなバッファに対して、CPUで描画を行っているそうです。

建物などのオブジェクトの簡易モデルを用意しておき、現在の高速なCPUをぶん回して深度だけ埋めていくわけです。

カラーの補間もテクスチャマップも不要なので、意外と何とかなるんでしょう。

特にPS3の場合はCellを利用することでかなり高速に処理することが出来るんじゃないでしょうか?

どちらの手法も悪くないと思うのですが、簡易モデルを用意しなくて済む分、『Crysis2』の方法が便利なんじゃないかと思います。

ではサンプルです。

いつも通り、下から取得してください。

前回のサンプルに上乗せする形で実装しているので、Bilateral Upsampling も楽しめます。

しかも前回は縮小バッファに描画した方が遅かったライト計算も、さすがに512個もライトがあると縮小バッファを利用した方が速くなりました。

まだちょっとうまくいってるか不安なのですが、おかしなところがあったらご連絡ください。

追記:

共有メモリを使って予め縮小深度バッファを全て読み込む手法に変更してみました。

以前のものと比べると、ちょっと速くなったかな、っていう程度。というか、ほぼ変わらず。

一定以上の大きさのAABBは必ず描画に回すように工夫もしてみたのですが、そもそも全部描画した方が速いんだから工夫もへったくれもありゃしない。

とりあえずサンプルを差し替えてみたので、興味がある方はチェックしてみてください。