Interactive Indirect Illumination Using Voxel-Based Cone Tracing(3)

前回に引き続き、Pacific Graphics 2011で発表された論文の後半を見ていきます。

This article is based on the following publication.
[Interactive Indirect Illumination Using Voxel Cone Tracing]
http://maverick.inria.fr/Publications/2011/CNSGE11b/
All of this publication's contents that are used in following this article are belong to the original authors.

7. Voxel Shading

間接照明を扱うためには、AOのみでなくvoxelのシェーディングも計算しなくてはならない。
そのためには、voxelに埋め込まれた、様々な方向情報とスカラー値だけでなく、累算しているconeの大きさも取り扱わなければならない。この演算は[Fou92, HSRG07]で示されたとおり、Gaussian lobeに分解された上記情報の累算で行うことができる。

この論文のケースではBRDF, NDF,coneの大きさを積分しなければならない。 BRDFは先に述べたとおりGaussian lobeで表すこととした。この論文ではPhong BRDFを考えてみる。これは(分散の大きい)diffuse lobeと(小さい)specular lobeで表すことが出来る。それ以外でもこの論文の手法は、lobeの組み合わせを用いることで、簡単に(シェーディングモデルを)拡張することが出来る。 NDFは[Tok05]で提案されたように、平均化した法線のベクトルの長さで分散を表したGaussian lobeでストアされる。

次に、(累算のための)Gaussian lobeの分布をview coneにあわせる。(上図参照)
これはconeの原点から対象のvoxelに向かうconeの分布は、対象のvoxelからconeの原点に向かうconeの分布(すなわち累算の窓関数の分布)と等しいと考える。つまり累算の際に使うGaussian lobeの標準偏差はconeの開度で表される。

8. Indirect Illumination

点光源の配置されたシーンでの間接照明の演算は、AOの演算よりもより複雑である。この論文では2つのステップを用いた。
まず、voxelに対する入力光(incoming radiance)を保存する。二次光(outgoing)ではなく入力光を保存することで、鏡面のような反射(glossy reflection)をサポートする。 次に、cone tracingを行うことで、ライトの伝播を計算する。入力光をoctreeに書き込むのは複雑だが、このプロセスを実行する前に、すでにoctreeにこの情報が存在するものとする。

8.1 Two-bounce indirect illumination

今回はPhong BRDFの説明を行ったが、この論文の手法は、低エネルギー/低周波数でも、高エネルギー/高周波数でも様々なBRDFでうまくいくはずである。間接照明の演算は、第6項で説明したAOの演算に似ている。deferred shadingを用いて、シェーディングに必要な情報を書き出し、数回のcone tracingを行うことでfinal gathering(間接照明演算)を行う。 Phongマテリアルの場合、いくつかの大きなcone(通常5)のtracingでdiffuse項の概算を行い、反射方向を尖ったconeでtracingすることでspecular項を計算する。このconeの開度はspecularのexponentに従って求められ、これによって、光沢のある反射が実現する。

8.2. Capturing Direct Illumination

この間接照明手法を完成させるために、直接光をどのように(voxel)に保存するかを説明する必要がある。
[DS05]のように、シーンをライトの方向からラスタライズし、world positionと共に出力する。このレンダリング結果の 各々のピクセルは、シーン内でのphotonを表す。この論文内ではこの結果をlight-view mapと呼ぶ。このphotonを後ほどoctree内にストアするものとする。正確には、これらを方向情報の分布と、ピクセル(photon)の持つ立体角に比例したエネルギー量としてストアしたい。
photonの格納にはpixel shaderを用いる。通常light-view-mapは、末端のvoxelグリッドより細かいので、 末端voxelに直接格納しても不連続性を誘発することはないはずである。 その上、photonはサーフェースのそばに格納されるので、末端の(存在するはずの)voxelに格納することが出来るはずである。 またこの論文のvoxel表現では、空の空間のみvoxelを欠落している(保持していない)。 いくつかのphotonは同じvoxelに到達するはずなので、atomic addを使用する。

このプロセスはシンプルに見えるが、実際には2つの克服する必要のある障害がある。
まずはじめに、atomic addは整数にしか用意されていないということがある。 これは16Bitのnormalize テクスチャフォーマットを用い、サンプリング時にdenormalizeすることで、簡単に対処できる。もうひとつの問題は、隣接するBrickに重複するvoxelである。第4項で説明したとおり、 ハードゥエアによるフィルタリングを利用するために、この重複は必要である。最初のphotonのストアではスレッドの衝突(同一voxelへの書き込み)はまれだが、そのままpotonを隣接するBrickにコピーする作業をおこなうと、多くのスレッドの衝突が発生し、パフォーマンスに重大な影響を与える。また、この並列化されたランダムアクセスは、大きな帯域の問題を招く。もっと効率的な手法が必要である。

Value Transfer to Neighboring Bricks

簡単に説明するため、octreeはすべて末端ノードまで保持していると仮定し、各末端ノードにひとつずつスレッドを起動できるとする。x,y,z各々2パスで計6パスで、この処理を実行する。
最初のx軸のパスでは、現在のノードに対応するBrickのvoxelの値を右側に隣接するBrickの対応するvoxelに加算する(上図1)。実際には各スレッドごとに、3つの値(9?)を右の隣接Brickに加算することを意味する。次のx軸のパスでは、 右のBrickの対応するvoxel(先ほど加算した対象のvoxel)から、値をコピーしてくる(上図2)。この処理により、 x軸上に関しては一貫性の保たれた状態となる。この処理をy,z軸に各々適用する。
隣接Brickのポインタを保持している上に、スレッドの衝突も発生しないので、この処理は非常に効率的に動作する。 atomic operationも必要ない。

Distribution over levels

ここまでで、一貫性のある情報が、最下位のoctreeに格納された。次の処理ではこれらの値をfilterし、 その値を上のレベルのvoxelに格納する。(MIP-map)
簡単な方法として、ひとつのスレッドを上位レベルのvoxelに割り当て、下位レベルvoxelのデータを取得する方法が考えられるが、これには性能的に問題がある。重複しているvoxelでは、最高8回の同じ計算が行われる。加えてスレッドの計算コストは、処理するvoxel(の数)に依存するので、スレッドのアンバランスなスケジューリングとなる。

この論文の手法では、これを3つのパスに分けて実行する。これらのスレッドは大体同じコストで実行される。 はじめに部分的にfilterの結果を計算し、先の転送処理(隣接Brickの一貫性をのための処理)と同じ方法で、最終結果を計算する。
最初のパスは(上位voxelでの)中心部にあたる、計27のvoxelを対象にの計算を行う。 次のパスでは、Brickの外面の中心にあたるvoxelの半分にあたる18voxelを対象に計算する。 最後のパスでは、Brickの角にあたる部分の1/8を計算する。 これらの計算の後、先の処理と同じ処理(隣接Brickの一貫性をのための処理と同様の処理)を行うことで、このfiltering作業を完成させる。

Sparse Octree

ここまで、octreeは末端ノードまですべて保持していると仮定してきたが、実際には疎な(sparse)構造にをしている。
ここまで説明してきた手法は多くのリソースを消費する。filteringのためにphotonを一切含まない場合も、すべての(末端)ノードのためにスレッドを起動し、ゼロの値にfilterを適用している。直接光がシーンのごく一部にしかあたらない場合など、このゼロ値のfilteringを避けることは重要である。ひとつの手法として、どのノードにfiteringを行うべきかを見つけるために、light-view-mapのピクセルごとにスレッドを起動することが考えられる。これは結果としては正しいが、複数のスレッドは同じノードにたどり着くことになり、(特に高いレベルのMip-mapの場合)何度も同じ動作を繰り返すことになる。

この論文では、スレッドの起動をそれぞれのfilteringパスで最適化するために、light-view-mapから導出された、2Dのnode mapを利用する。このnode-mapはMip-mapのピラミッド構造となっており、最低レベル(最高解像度)のマップには、photonが届いたoctreeの末端ノードのインデックスが記されている。その上位レベルのマップには、 先の末端ノード共通の親ノードのインデックスが記されている。
これでもなお、最低レベル(最高解像度)の1ピクセルごとにスレッドを起動しなければならないが、ひとたびtree構造をたどり、対象ノードにたどり着けば、それはそのスレッドで必ず計算しなければならない対象となる。また、現在のnode-mapを参照することで、同時に実行されているスレッドの中に、自分と親のノードを共有しているものが いるかどうかも確認することが出来る。 すべてのスレッドは、共通の親ノードを持つスレッドが、同時に起動しているかどうかを検出することができ、上図で(各2×2ブロックの)左上を担当しているスレッド以外を終了させ、親ノードに関連する残りのフィルター処理を左上のスレッドのみに担当させることができる。この手法は非常に効率的で、他の手法に比べて2倍以上早い結果を得ることができた。

9. Anisotropic voxels for improved cone-tracing

ここまで説明したこの論文のvoxel表現は、シーンのとても良い近似となったが、 まだいくらかの品質の問題が残っている。
一つめの問題は、上図の左側で示したように、赤と緑の壁の問題である。 2つの平面からなる、違う色のvoxelは、上位のMip-mapレベルで平均化され、まるで半透明のように、色は混ぜられてしまう。
不透明度にも同様の問題が発生する。(図の真ん中部分)2x2x2のvoxelの半分がopaqueで半分が完全に透明ならば、平均化されたvoxelは半透明になってしまう。この問題は、coneが大きいときにlight leaking(本来届かないはずの壁の裏に間接光が漏れる)の問題を発生する事は容易に想像が出来る。

そのため、この論文では、異方性のvoxel表現をMip-map生成時にに用いる。1つの方向性を持たない値をストアする代わりに、voxelには6つのそれぞれの主軸を向いた、方向性のある値をストアする。上図(おそらく図の真ん中の部分)の例で示すと、方向性の持った値は、それぞれの方向からみて、奥側向かって処理を行った後、4つの値の平均を取ることで計算される。
レンダリング時は、voxelの値は近い3軸分の値を線形補間した値が採用される。(図の右側部分)
この論文では、もっと複雑な手法の代わりに、このシンプルな表現を使用した。 完全に補間可能で、簡単にBrick内に格納できるためである。くわえて、この方向性をもった表現は、中間ノードのvoxelのみである。(最下層は持っていない) そのため、方向性をもったvoxel情報をストアしてもメモリ消費量は1.5倍程度である。

10. Results and Discussion

この論文の手法を NVIDIA GeForce GTX 480 を搭載したCore2DuoE6850を搭載したシステムで実装した。 結果、添付のビデオ(このポストの最上部リンク先参照)にあるように、様々な複雑なシーンでリアルタイムのフレームレートでレンダリングできた。

この表はは1フレーム内のそれぞれの処理の所要時間だが、Sponza シーン(280K triangles)で 5123の9レベルのoctreeeと512×512のスクリーン解像度で実行したものである。 これらのコストに加え、動的オブジェクト Wald’s hand(16K triangles)の更新処理におよそ5.5msかかっている。
この更新時間は、動的オブジェクトがサイズとパーツの数に依存する。 初期化時に、静的オブジェクトのoctreeの生成にかかる時間は約280msである。 直接光のinjectionとfilteringは、光源が動いた場合のみに動作し、およそ16msかかる。 Sponzaシーンでは、ひとつの動的オブジェクトと、ひとつの動的光源で、スクリーン解像度512×512で、specular cone tracingをしなければ30FPS、ひとつのspecular cone tracingを行った場合は20FPSとなった。1024×768のスクリーン解像度で3つのdiffuse cone tracingと1つのspecular cone tracingを行った場合は11.4FPSとなった。

上図はレンダリング品質の比較を、この手法と最も近いリアルタイム技法であるLPV(light propagation volumes)と、リファレンスであるMental-Rayのfinal gatheringと比較した。
LPVの実装は、NVIDIAのDirect3D SDKに付属する実装を使用した。すべての状況で、この論文の手法は、LPVと比較して高品質な(よりリファレンスに近い)結果となった。このケースでは、LPVが、ネストしたグリッド表現で遠くの間接光を拾うことが出来ないのに対して、この論文の技法は、リファレンスのように、より正確にに遠くの間接光を拾うことが可能であった。LPVと、この論文の技法は、共に離散化したジオメトリとirradianceによる、light leakingの問題を起こしている。 左側の壁や、アーチの側面で観測できる。leakingはLPV手法で特に見られる。この論文の手法では、高いvoxel解像度のため、いくらかましである。

この手法の一つの制限事項は、 鏡面反射の間接光をサポートしたことにより、sparse structureを用いていても、相対的に高いメモリ使用量である。実際には、おおよそ1024MBのメモリを使用した。だが、LPVと同等の品質を達成しようとした場合は、もっと少ないメモリ使用量になる。


AOの計算にかかる時間と、リファレンス(NVIDIA OptiXでpath tracingしたもの)と比較したものを示した。
違いは主に、AOのテストが3つのcone tracingで行われたことによるものである。開度の大きいconeは 精度の問題を引き起こし、AOを大きく概算(over darken)してしまう。

11. Conclusion

先のposterのIntroductionに似てるので割愛。

ここまでの日本語での記述は、残念ながら、この論文の日本語訳になっていません。表記のブレや、意訳、直訳、欠落、そして私の力量不足による誤記等が、存分に含まれていると思われます。
本気で取り組む方は、ぜひオリジナルを参照してください。

この手法に関する考察は、別で行いたいと思います。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中