グローバルイルミネーションのためのフォトンマップ実装①

 グローバルイルミネーション(Global Illumination: GI)とは、現実世界における間接光を表現するためのアプローチである。(あるいは、間接光そのものを指す)

 本来、現実のライティングというものは、無限にも等しい膨大な量のフォトンと、制限のないバウンスという膨大な物理的現象の結果起きている事象であるが、それをコンピュータで再現するというのは非常に難しい。リアルタイムレンダリングであるならばなおさらだ。
 ゆえに、古きよきレンダリングエンジンというものは、頂点の座標と法線、そして光源の距離と方向から高速に計算することができる「直接光(Direct Lighting)」と、間接光をすべて均一に入射する光として大幅に近似する「環境光(Ambient Lighting)」の二つでライティングは実装されていることが多かった。

 しかし、近年は、オフラインライティングとリアルタイムライティングを組み合わせた近似的GI表現が流行っている。その一つがフォトンマッピングだ。これは、広義的にはレイトレーシングの一種で、狭義のレイトレーシングは視線からレイを飛ばし色を追跡するのに対し、フォトンマッピングは光源からフォトンを放ちその軌道をシミュレーション、キャッシュするアルゴリズムである。
 そして、テクセルベースで特定数のキャッシュされたフォトンを収集、そしてその収集したフォトンの分布から密度を推定、放射照度を計算する。
 この手法は、放射するフォトンの量と、テクセルあたりのフォトン量にしか計算量は依存せず、古典的GI実装手法であったラジオシティ法よりも計算量の見積もりが容易いという利点がある。また、フォトンの放射量及び密度を、関心の高いオブジェクト(例えば、コースティクスを形成する透過材質のオブジェクトなど)に対し調整を加えることで、ほかの手法では難しかったシーンの品質の調整が可能になっている。特に、透過材質に対するコースティクスの生成は、フォトンマッピングの優秀なところを語る際には欠かすことができない。これは最も一般的なラスタライズ法とは根本的に異なるアプローチではあるが、リアルタイムレンダリングでは組み合わせて使われることも多いようだ。

 実際に、GPUレンダラ―における実装を行ってみよう。手順は以下のようなものが考えられる。

①光源からフォトンを照射する
②非完全鏡面に当たるまでバウンス、拡散面に当たった場合はメタリック値、RGB反射能から確率を計算し、ロシアンルーレット法でバウンスするかどうかを決定する
③全てのフォトンがmiss(サーフェイスに吸収されないままどっかいった)、吸収、あるいは数値計算的限界(あらかじめ定めたバウンスオーダーのオーバー)等により計算結果が確定したら、吸収が起きたフォトンの座標の空間分布よりKD-Treeを形成する(平衡条件を満たすように構築するとよい)
④次に、LightMapを生成するために、GIの影響下にあるオブジェクトをテクスチャにUV展開し、テクセルごとにワールド座標やワールドタンジェント等の情報を書き込んでおく。
⑤LightMapをテクセル単位で走査し、各テクセルについてKD-Treeを使ってフォトンのN近傍探索を行い、放射照度を推定する。
⑥シェーダーでLightMapをフェッチしてライティングに使用する。

 結構泥臭い。とくに、LightMapの生成に関しては、ラスタライザーはもちろん、リソースの肥大化を防ぐためにもアトラステクスチャを作って大きいライトマップに複数のオブジェクトベイクするべきであるので、ある種の平面ナップザック問題を解かなくてはならない。メッシュのUV展開アルゴリズムも必要である。
 アルゴリズムそのもの複雑さの割に、なかなかに面倒な代物だ。一方で、CPUで計算する場合はKd-treeのフォトン分布をそのまま放射輝度推定に使えるのでLightMapを作る必要はなく、レンダラは割とシンプルに作れる。

 
 ざっくりレンダリングしてみると以下のような結果が得られる。放射フォトン数は10000、放射輝度推定フォトン数は10、フォトン探索半径は3(このコーネルボックス自体のサイズが30くらい)


f:id:riyaaaaasan:20180519230732p:plain

 にじみがすさまじい。まあ、フォトンの数的にこんなものだろうか。

f:id:riyaaaaasan:20180519230727p:plain

 ボールの右側が、近くの青い壁から反射してくる間接光によりうっすら青くなっている様子も確認できる。

 
 ちなみに、これは直接光も間接光も両方フォトンマップから放射輝度を推定しているが、前述の通り直接光は入射ベクトルと法線ベクトルから高速に計算できる。しかし、その場合の光の物理量の辻褄をどう合わせるのか分かっていない。
 今回の実装では、光のIntensity(強度)をポイントライトのパラメータにもち、それを放射束に変換して、フォトンの密度とその放射束の和から放射輝度を計算している。
 一方で、古典的なポイントライティングのライティング手法では、「減衰係数ζ1、ζ2、ζ3」をパラメータとし、そして入射ベクトルと法線ベクトルの内積から放射輝度を推定する。
 
 まったく異なるアプローチのため、直接光と間接光を二つのパスに分けるとしても、同じポイントライトの光の強さから、物理量としての整合性を保つ方法が分からない。うーむ。減衰係数という概念をライティングに持ち出すことをやめたほうがいいのかもしれない。
 あまり実用レベルにならない上に、ちょっと資料の不足により改善の目途が立っていないので、いったんこのGI機能はお蔵入りにしようか。敗北した気分だが。

 どうでもいいが、放射輝度推定に使うフォトン数を1個にすると、ボロノイ図のようなキモイものが浮かび上がる。

 f:id:riyaaaaasan:20180519232408p:plain