ノイズの削減と間接照明

約一ヶ月休んでましたが、色々蹴つまづきながらも開発は進んでます。いまは人生初めての就職活動に挑むにあたって、身辺がばたばたして心が落ち着きません。ああ、早く定職に就きたい...

§

ここ1週間は、レンダリング処理に様々な問題が見つかり、その解決に手間取りました。

ノイズ

サンプル数を増やしてもなかなかノイズが減少する気配がないので、疑問に思い詳しく調べたところ、シャドウレイによる光源の可視判定で予期しない失敗をしていることが分かりました。レイとポリゴンとの交差点から光源に向かってシャドウレイを追跡する時、始点を含むオブジェクトを誤って最近傍のオブジェクトとして判定してしまうパターンが多々発生し、これがオブジェクト表面に黒い斑点を生む原因となっていたようです。
そこで、レイの始点から交差点までの距離を互いに比較する際に、境界値を調整する補正値を加えたところ、期待通りにノイズが一気に減少しました。また、新しく層化サンプリングを実装したところ、今までオブジェクト表面に縞模様となって浮き上がっていたノイズも見事に消えて無くなりました。
以下は、修正後のプログラムでコーネルボックスレンダリングした結果です。左が10パス/ピクセルで、右が100パス/ピクセルです。以前は100パス/ピクセルでも黒いノイズが派手に目立っていたのですが、修正後はそのようなノイズは見当たりません。

今回は「アルゴリズムは間違っていないのに、なぜ?」という思い込みが問題解決の邪魔をしてしまって、余計に時間がかかってしまいました。浮動小数点演算は慎重に行わないと怖いですね。以後の反省にします。

間接照明

これまで間接照明は、通常の光線追跡によって再帰的に求めた放射輝度BRDFをかけて計算していました。しかし、BRDFの値が大変小さくなる傾向があり、間接照明の効果がほとんど得られていませんでした。実はこの間、試しに直接照明と間接照明を分けてレンダリングしてみたところ、間接照明だけの場合、シーン全体が真っ暗闇になってしまって「駄目だこりゃ」(いかりや長助調)となったしだい。
そこで、手元にある「 Realistic Ray Tracing 」(以下、RRT)の『12. Path Tracing』をじっくり読んでみると、放射輝度の計算式は、確率密度関数p(x)を
p(\vec{k_i}) = \frac{\rho(\vec{k_i}, \vec{k_o})cos\theta}{R(\vec{k_o})}
と定義すると、次の式に近似できるから単純に反射率Rをかけたらいいと書いてあります(最初に読んだ時は、意味が分からなくて完全にスルーしてました)。
L_s(\vec{k_o}) = R(\vec{k_o})L_f(\vec{k_i})
ここでいう反射率Rとは、あるオブジェクト表面において、全方向に出射される光量と一定方向から入射してくる光量の比であり、完全な拡散反射(ランバート面)だけに限定した場合、
R(\vec{k_o}) = \pi \rho(\vec{k_i}, \vec{k_o})
と定義されます。RRTでは、このRを「Directional Hemispherical Reflectance」と読んでいますが、正確な訳が分からないので、とりあえず反射率としておきます。
さて、これにしたがってBRDFに変えて反射率をかけるようプログラムを変更したところ、以前よりずっと柔らかい陰影をもつ画像が得られるようになりました(上記画像)。モンテカルロ積分において、確率密度関数がp(x)である関数F(x)の期待値は、
g(x) = f(x)p(x)
とおくと
E(f(x)) = \frac{1}{N}\sum^{N}_{i = 0}\frac{g(x_i)}{p(x_i)}
なので、放射輝度をp(x)で割る必要があったのですね。計算式の項を一つ入れ替えただけでここまで結果が変わるなんて、どうしたらいいのか分からず悩んでた時間はなんだったんだ!と叫びたい思いです。
ただ、ここで一つ疑問があります。確率密度関数p(x)は、どのような方法で決定されるのでしょうか。RRTでは、直接照明の計算において、p(x)を光源(面光源)の面積の逆数を指定するのが良いとしていますが、それ以上の突っ込んだ説明はなされていません。つまるところ、直接照明と間接照明ごとに、ある一定条件を満たす関数を適当に自分で見つけろということでしょうか。