双方向パストレーシングを理解したいんだけど全体を一度に理解するのは難しい。 ひとまず3点形式というものが必要っぽいのでナイーブなパストレのまま計算だけ3点形式でやってみる。
概要
双方向パストレーシングを理論的に解説した論文としてEric VeachのRobust Monte Carlo Methods for Light Transport Simulationがあるが、まあムズくてボリュームもデカいので理解ができてない。 でどうしようかと悩んでたんだけど3点形式として扱うことが必要っぽいのでまずはそれ単体を動かしてみることにした。
経路積分の公式化
件の論文の8章 光輸送の経路積分公式化に出てくる方程式を参考にしつつ進める。
パストレの計算内容
ナイーブなパストレでは視点からレイを飛ばして、交差する物体の表面で反射を追跡していく。 これが実のところレンダリング方程式の近似解を求めることになる:
$$ L_o(\bm{x}, \vec{\omega_o}) = L_e(\bm{x}, \vec{\omega_o}) + \int_{\Omega} f_r(\bm{x}, \vec{\omega_i}, \vec{\omega_o}) L_i(\bm{x}, \vec{\omega_i}) (\vec{\omega_i} \cdot \vec{n}) d\vec{\omega_i} $$
積分変数\(\vec{\omega_i}\)を積分範囲\(\Omega\)である半球状の方向をランダムに選ぶことでモンテカルロ積分が計算できる。
3点形式
上記のレンダリング方程式は方向ベクトルで扱っている。 双方向パストレでは光輸送問題を経路積分で扱いたくて、それには3点形式を用いる必要がある。 3点形式は方向ベクトルの代わりに反射先の交点座標を扱う:
$$ \begin{align*} L(\bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) =& L_e(\bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) + \\ & \int_{A} L(\bm{x} \rightarrow \bm{x^\prime}) f_r(\bm{x} \rightarrow \bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) G(\bm{x} \leftrightarrow \bm{x^\prime}) dA(\bm{x}) \\ & \tag{8.1} \end{align*} $$
積分変数が\(\bm{x}\)で、積分範囲\(A\)は理論的にはシーンに存在するすべての物体表面が対象になる。 「そんなことが可能なの?」と思うんだけど、項\(G\):
$$ G(\bm{x} \leftrightarrow \bm{x^\prime}) = V(\bm{x} \leftrightarrow \bm{x^\prime}) \frac{|\cos(\theta_o) \cos(\theta_i^\prime)|}{||\bm{x} - \bm{x^\prime}||^2}. \tag{8.3} $$
に含まれる\(V\)が\(\bm{x}\)と\(\bm{x^\prime}\)が遮られない場合のみ1・それ以外は0という可視判定で、それにより頂点\(\bm{x^\prime}\)から直接見える=一番近い頂点のみが有効で、結果的に元の式と同じ内容になる。
また\(G\)には注目点\(\bm{x^\prime}\)のコサイン項\(\cos(\theta_i^\prime)\)だけじゃなく、次のサンプル点\(\bm{x}\)のコサイン項\(\cos(\theta_o)\)も必要で、計算は多少煩雑になる。
頂点が選択される確率密度の算出方
対象となる頂点の選択にシーン内のすべての物体表面からランダムサンプリングするというのはまあ無駄だし意味ないので、ナイーブなパストレで行う方法と同様に半球状の方向ベクトルを選んでその方向にレイを飛ばして交差した点を対象に選ぶ。 ただモンテカルロ積分を適用する際にサンプル点が選ばれる確率密度が必要になる。 方向ベクトルによって頂点を選んだ場合、確率密度はどうなるのか?
方向\(\omega_o\)が選ばれる確率密度を使って:
$$ p(\bm{x^\prime}) = p(\omega_o)\left( \frac{|\cos(\theta_i^\prime)|}{||\bm{x} - \bm{x^\prime}||^2} \right) \tag{8.10} $$
- “括弧内の式は、\(\bm{x^\prime}\)における単位表面積あたり、\(\bm{x}\)において張られる立体角を表す”とのこと
- 導出は微正面を単位球に投影した立体角だと思うが完全に理解はしてない…
- 式(8.1)の3点形式では\(\bm{x^\prime}\)に対して\(\bm{x}\)が動く変数なので、記号が逆じゃない?
\(p(\omega_o)\)がその方向が生成される確率密度で、一様だったり拡散反射面の重点的サンプリングを用いるためのコサイン重み付きだったりで、使用した方向乱択アルゴリズムによって決まる。
3点形式のモンテカルロ積分
3点形式の積分項の近似解を求めるためにモンテカルロ積分を用いる。
一般に積分\(I = \int f(x) dx\)の近似解は、
$$ \hat{I} = \frac{1}{N} \sum_{i=1}^N \frac{f(x_i)}{p(x_i)} $$
で求められる。 \(N\)は試行回数、\(p(x)\)はサンプル点\(x\)が選択される確率密度を表す。
これを3点形式に適用し、また\(G\)(可視判定\(V\)は除く)と確率密度\(p\)(\(\bm{x^\prime}\)から\(\bm{x_i}\)をサンプルする確率密度)を代入すると、
$$ \begin{align*} \hat{I} &= \frac{1}{N} \sum_{i=1}^N L(\bm{x_i} \rightarrow \bm{x^\prime}) f_r(\bm{x_i} \rightarrow \bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) \cdot G(\bm{x_i} \leftrightarrow \bm{x^\prime}) \cdot \frac{1}{p(\bm{x^\prime} \rightarrow \bm{x_i})} \\ &= \frac{1}{N} \sum_{i=1}^N L(\bm{x_i} \rightarrow \bm{x^\prime}) f_r(\bm{x_i} \rightarrow \bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) \cdot \frac{|\cos(\theta_o) \cos(\theta_i^\prime)|}{||\bm{x_i} - \bm{x^\prime}||^2} \cdot \frac{||\bm{x^\prime} - \bm{x_i}||^2}{p(\omega_i^\prime) |\cos(\theta_o)|} \\ &= \frac{1}{N} \sum_{i=1}^N L(\bm{x_i} \rightarrow \bm{x^\prime}) f_r(\bm{x_i} \rightarrow \bm{x^\prime} \rightarrow \bm{x^{\prime\prime}}) \cdot \frac{|\cos(\theta_i^\prime)|}{p(\omega_i^\prime)} \end{align*} $$
と距離の2乗や選択先のコサイン項がちょうど消えて、元々のナイーブなパストレで行っている計算と同じになる。
- 積分変数は違うが同じ内容の計算を行っているので、当然計算結果が同じなる
実装
以上の3点形式を組み込んだパストレを作ってみる。 実装は例によってsmallptを元にする。
経路をいったんバッファに記録する
smallptではradiance関数で再帰して直接色を返していた。
それをいったんバッファに貯めてあとから関係を追えるようにする:
struct Vertex { |
traceSceneでシーン内をレイを追跡して、頂点列Vertexを記録するpdfにはロシアンルーレットでの確率と、反射特性によるBRDFの係数を格納(拡散反射面)- 鏡面反射や屈折はデルタ関数なので別扱いする必要がある
radiance関数(後述)から呼び出して、得られた頂点列から計算する- ナイーブにやるのであれば、Gと確率密度を求めて計算する
直接光計算組み込み
3点形式を組み込んだ利点としてシーン内の任意の頂点を選ぶことができることを利用して、直接光を組み込んでみる。
光源上のランダムな点を選び、現在注目している交点との間が遮られているかどうかを調べる。 またその光源への方向が交点の法線と逆方向だったり、光源側の点の法線が裏向きだったりする場合も遮られているのと同様に、直接光源は無効とする。
有効であれば影響を加えるが、そのまま足し合わせてしまっては重複してしまうので、多重重点的サンプリング(Multiple Importance Sampling, MIS)を使う。 光源を直接サンプリングした場合とレイを追跡した場合のそれぞれの確率密度を比べて、信頼度の高い手法に高いウェイトを与えるようにする。 ウェイトの合計が1.0になるようにすることでバイアスを与えずにすむ。
光源サンプリングで選ばれる確率密度は、一様サンプリングであれば表面積の逆数で計算できる。 レイのトレース(BRDFサンプリング)で選ばれる確率密度は上の式(8.10)通り。
それを組み込んだ、最終的なradiance関数:
Vec radiance(const Ray &r, unsigned short *Xi){ |
- 最初に光源上を1点ランダムサンプリングして、頂点列に対して使い回す
- 直接光を計算するのは拡散反射面のみ
mis_weightでウェイト計算(パワーヒューリスティック)- トレースした影響を「スループット」として掛け合わせていく(反射で徐々に減衰していく)
- Gと確率密度は実際のところ打ち消し合うのだけど、そもそも取り除いてしまうとなんの計算をしているのかわからなくなってしまうので残しておく
- ただ同じ値を掛けて割ってるだけなので単に精度が落ちるだけだが、いちおう理解の補助として…
- Gや確率密度の計算には次の頂点が必要
- 光源に辿り着いたらトレースは打ち切り、という前提
あとがき
- 計算内容的には普通のパストレと同じだけど、計算方法を変えることで多少は意味がわかるようになった
- 角度方式だと占める立体角を計算するのもサンプルを生成するのも難しいが、経路方式だと対象の面積を計算したりサンプル可能なので、その点が重要なんだと思う
- また角度と頂点の両方式で確率密度を変換できる