相当昔にフォトンマッピングを動かしたことがあるが、放射するフォトン数が少ないと斑点ノイズが発生してしまっていた。 これを低減させるファイナルギャザリングを実装してみる。
フォトンマッピングとは?
光源からフォトンを飛ばして交点を記録したフォトンマップ(以降PM)を作成し、画像生成時の各ピクセルの色を求める際に利用する。
(双方向)パストレーサーに比べての利点は、集光模様(コースティクス)を綺麗に描画できることでしょう。
単純なフォトンマッピングの問題点
ピクセルの色を求める際にカメラからの交点位置に対してPMから輝度推定すると、推定には一定量のフォトンを近傍から探すためフォトン数が少ないと斑点ノイズが発生してしまう:

この問題に対してファイナルギャザリングで対応する。
ファイナルギャザリングとは?
正直「ファイナルギャザリング」というのがなにを指すのかいまいちはっきりしないけど言葉的には「最後にかき集める」ということで、 輝度計算をPMだけで行うのではなく直接照明+間接照明+集光模様に分けて計算することにして、 間接照明を周りからかき集めることから命名してるのだと思う。
書籍「フォトンマッピング」での解説
フォトンマッピングの開祖たるHenrik Wann Jensen氏の書かれた書籍の和訳本「フォトンマッピング」を読み返してみた (本にはファイナルギャザリング(以降FG)という単語自体は出てこないようで、索引にもない)。
9章「実践的な2段階アルゴリズム」でレンダリング方程式を、
- 直接照明
- 鏡面反射
- 集光模様
- 間接照明
の4つに分けて計算することが提示される。
9.3 第1段階:フォトンの追跡
フォトンの記録を集光模様PMと大域PMに分ける。
- 集光模様PMには光源から放射され鏡面反射または透過してから拡散面に到達したフォトンを格納 (光伝達の表記法:
LS+D) - 大域PMには拡散面にぶつかるすべてのフォトンを格納、ということで集光模様PMに格納されるフォトンも格納する (
L(S|D)*D)
9.4 第2段階:描画
4つに分けた各計算法が記述されている(が説明がいまいちわかりづらい…)。
- 直接照明:影光線を使って直接計算
- 鏡面反射:反射先で計算
- 集光模様:集光模様PMで推定
- 拡散反射:何本もサンプル光線を利用して間接照明を正確に計算、サンプル光線での近似計算には大域PMから推定
この拡散反射を正確に計算するための分散光線追跡法(Distributed Ray Tracing、半球状を多数サンプリング)がFGということになるだろう。
すべての経路が追跡されるかの確認
この方法で正しいかどうか、あらゆる光伝達経路が網羅されるかを確認する必要がある。
拡散面での計算は直接照明+間接照明+集光模様PMで、直接照明はLD、集光模様PMはLS+Dとなる。
間接照明はFGでDS*D、FG先のPM推定でL(S|D)*Dということで、つなげるとL(S|D)*DS*Dになる(PM推定最後のDとFG最初のDは同じもののため1つにまとめる)。
これらを視点から辿る逆向きの経路DS*Eとつなげると、
| タイプ | 光経路 | 備考 |
|---|---|---|
| 直接照明 | LDS*E |
拡散面1回 |
| 集光模様 | LS+DS*E |
鏡面反射後に拡散面1回 |
| 間接照明 | L(S|D)*DS*DS*E |
拡散面2回以上 |
という具合で、任意の経路L(S|D)*Eが必ずどれか1つだけに当てはまるようになっている。
- 今回は点光源のみのため視点からのトレースで光源にはぶつからないので、光源を直接見る経路
LS*Eは存在しないので扱ってない- 光源が大きさを持ち拡散面を経ずに到達する場合、光源の色を返すようにする必要がある
- 同じくFGで
S経由で光源にぶつかる場合は黒を返す必要がある(そのような経路は集光模様PMに含まれるため)
実装
てなわけでみっちり実装:
int estimate=10, estimate_caustic=5; |
- 前回との共通部分は以前のコードから持ってくること
- FGのメイン部分:
trace関数内、拡散反射面の箇所:- カメラパスでレイのタイプを
EYE_GATHERとEYE_ESTIMATEで判別できるようにしておき、 GATHERの場合は直接照明+間接照明+集光模様PM- 間接照明に
ESTIMATEレイを撃つ
- 間接照明に
ESTIMATEの場合は大域PMから求める
- カメラパスでレイのタイプを
- 直接照明
directではLightPowに対してBRDFである1.0/PIとコサイン項と、距離減衰1.0/(4*PI*dist2)を乗ずる - PMから放射輝度を推定する際には放射したフォトン数で除算する必要がある
- 以前は最後に一括して除算していたが、今回の計算では直接照明の結果には適用してはいけないので、あらかじめ光源が放射するフォトンの放射輝度を除算してしまっている
- 間接照明以外にもアンチエイリアス・Glossy光沢反射・半影なども扱うことも考えて、 間接照明計算でループするのではなくメイン側でループさせてみる
- 元々smallppmを元にしていたので乱数は使わずに
halハルトン数列を利用している- フォトンの放射時にはフォトン番号によって偏りなくバラけてくれるが、今回カメラパスで単純にピクセル位置+FG回数だとパターンが見えてしまった → 2D座標に対する適当なハッシュ関数で回避
- そのため正の値となるよう
hal関数の引数jをunsigned intにする
- その他、改善項目:
- KDTree構築時に完全にソートする必要はないため
nth_elementが使える(O(n)だとか) log_2の計算に__builtin_clzが使える(GCC拡張。C++にはcountl_zeroがあるがC++20が必要なのとunsignedじゃないと怒られる)
- KDTree構築時に完全にソートする必要はないため
- 集光模様PMのみを可視化した結果:ガラス球の屈折だけじゃなく鏡面球による散らばりでフォトンが散乱したものがマップされるため、推定に用いる個数を少なくすると壁や天井にFG適用前と同様の斑点ノイズが発生するのが盲点だった (ただし直接光に比べれば影響は少ないので合成すれば目立たない)
リンク
- 書籍:フォトンマッピング: 実写に迫るコンピュ-タグラフィックス とてもいい本(でも説明は難しい)
- Final gatheringについて - teastat
- フォトンマッピングのメモリ的な制約がprogressive photon mappingで解決できる、とのことで現在ではあまりFGは使われてないのかもしれない
- Photon Mappingの実装の記録
- 青い本(Jensen本)に書かれている「集光PMに格納したら追跡を打ち切る」ことを実践しているが、光の経路を想像するに追跡を続けて大域PMに記録した方がいいような?
- githole/simple-photonmap: 日本語コメントつきフォトンマップ法。 FGのブランチがある
- 集光模様PMを使用せずにFGしてコースティクスがちゃんと出てるのは
REFRACTIONで光源にヒットした場合にemissionを返しているからっぽい、 光源はそんなに大きくないのによく綺麗に出るなと思う
- 集光模様PMを使用せずにFGしてコースティクスがちゃんと出てるのは
- 過去記事: