Superflyがいろん・2
2016年09月28日(Wed)
空間内のあらゆる光について、引き起こされるすべての現象をくまなく計算したい。それが可能ならば、得られる結果はまさしく現実世界そのものになるだろう。とはいうものの、実際にはそう上手くいかない。すべての現象が厳密に解明されていたとしても、すべての光をトレースするにはとんでもない量の計算が必要になる。光の大半は衝突した物質に吸収されるとしても、残りはまた反射して空間を進んでいく。増えることはなくても、ゼロになることもない。ただ限りなく小さくなって、一つの極限値に近づいていく。永遠に亀を追い続けるアキレスのように、その計算はいつまでも終わらないだろう。
じゃあ、どこかで端折らないとね。
光源からスタートして、光の経路を追跡しながら明るさを計算しようとする考え方を、ライトトレースという。それに対し、視点(カメラ)から出発して、視点に届く光を逆行するように追跡する考え方をレイトレースという。

ライトトレースに比べれば、レイトレースは圧倒的に計算が少ない。上下左右前後、全天方向へ進む光源からの光に比べて、視界にはレンダリングサイズという絶対的な制約があるからだ。レンダリングサイズが100×100ピクセルなら、1万ピクセル分の計算すればそれでレンダリングは終わる。
では、こんなシーンを考えてみよう。

ここで、カメラから視線を飛ばす。これをレイと呼ぶ。
スキャンラインでは、視線(レイ)はオブジェクトに衝突した時点で止まる。そこでライトとオブジェクトの面の向きを確認し、ランバートシェーディングで色をつけ、フォンシェーディングでハイライトを上乗せしたら終了だ。レイトレーシングでは、オブジェクトの表面に屈折や反射が設定されていた時だけレイを反射させる。最大で設定された「レイの反射回数(Raytrace Bounds)」の分だけ追跡を行い、その表面の状態を確認して計算に反映させていく。

影の描画が有効になっていた場合、レイトレースならオブジェクトの表面に衝突した時点で、そこに光が当たっているかどうか、つまり光源との間に遮蔽物がないかを調べる。スキャンラインの場合はそういった計算ができないので、不正確だが高速なシャドウマップを使う。
オブジェクトに衝突した時点で計算に入るので、スキャンラインやレイトレースは間接光を考慮しない。間接光を描画しようと思ったら、別の方法で計算して組み合わせるしかなかった。
そこで登場したのがパストレーシングだ。パストレーシングでは、レイはオブジェクトに衝突しても、まだ計算を行わない。その地点からさらにランダムな方角に二次レイを反射させるのだ。

反射角=入射角ではなくて、なぜランダムな方向なのだろう? それは、拡散反射光がどの方角から来るかわからないからだ。二次レイを飛ばして調べるのは、他所からその地点に向かって届いている拡散反射成分である。常に反射角の方角から来ているとは限らないのだ。
さらに次のオブジェクトに衝突したら、またランダムな方向に反射して……それを繰り返して、レイが光源にたどり着いたらゴール。ここでようやく、レイは光源から今までに通った経路を辿り、衝突したオブジェクトの明るさを計算する。明るさとはつまり「入射光に対してどれだけ反射するか」だ。だから光源の情報が手に入って、初めて計算できるようになる。

このようにして経路を追跡していくので、パストレーシングという。
パストレーシングがやっている計算は、ざっくり言えばこれだけである。影もIDLもSSSも分けて計算することはない。だからパストレーシングには、影やIDLやSSSの設定項目がない。全部一緒に計算しているのだ。
ここで、いくつかの疑問が浮かぶ。
なぜ、ランダムな方向に一本のレイを反射させただけで、正しい色が分かるのか。拡散反射はいろんな方角から届いているはずである。本来ならレイがオブジェクトに衝突した時点で、あらゆる方向にレイを反射させ、そのすべてを合算するべきじゃなのいか。だいたい、レイがいつまでも光源にぶつからなかったらどうするのだろう。
もっともである。しかし、オブシェクトに衝突するたびに無数の二次レイ、三次レイを飛ばしていたら、計算量がとてつもなく膨れ上がるのは目に見えている。とはいえ一本だけしか飛ばさなければ、それが本当に正しい「その地点に届く光」なのかはわからない。
だからこそ、ランダムな方向に二次レイを飛ばすのだ。

これは品質を一番落とした状態のレンダリング結果。ピクセルサンプリングを1にした状態だ。これはこれで何か利用できそうな気もしなくはないが、およそリアルとは言い難い。真っ暗な点もあれば、明るすぎる点もある。だけど、目を離してぼんやり眺めてみれば、二次反射光も表現できてるし、明暗もなんとなく掴めてる気がする。「理論は合ってるけど精度が悪いよね」という状態だ。

そこでパストレーシングではピクセルサンプルを増やす。ピクセルサンプルはFireflyにも存在するパラメータで、1ピクセルを計算するのに何回サンプリングを行うかを決定する。ピクセルサンプルが3なら、1ピクセルを決定するために3×3=9回計算を行う。なのでピクセルサンプルの設定を上げると、レンダリング時間は指数関数的に増大する。

オブジェクトに衝突したとき、飛ばされる二次レイの方角はランダムである。なのでこの結果が合っているかどうかは確率次第だ。だからこそ、結果は試行回数を増やすほど正解に収束していく。この考え方を確率の分野ではモンテカルロ法と呼んだりする。
では、レイがいつまでも光源にぶつからなかったらどうするのだろう。オブジェクトの存在しない背景へ飛んでいくこともあるだろう。光源がとても小さかったり、部屋の中のように狭く入り組んだ形状だったら、レイはいつまでも反射を繰り返してしまう。

背景に飛んでしまったらレイはもう帰ってこない。そんな時はそのレイを計算から除外する。さらに各レイに寿命を設ける。オブジェクトに衝突するたびに一定確率で死ぬようにして、毎回生死判定を行うのだ。これをロシアンルーレットと呼ぶ。いやホントに。他にもレンダラによっては最大反射回数を設定できるものもある。
このように増大する計算回数を制限する一方で、ライトからもレイを飛ばしライトトレースとレイトレースを合流させる双方向パストレーシングや、衝突時に一定確率で光源に直接ショートカットさせる手法、反射の方向をある程度限定する手法などもある。色々駆使してばらつきを抑え、計算結果を収束しやすくするのだ。

現実世界で起こっている無限の衝突現象を計算する代わりに、確率を定め、有限回の計算を行う。試行回数を上げればいつかは現実に近づいていく。時間はかかるが、無限ではない。そういう考え方がパストレーシングの根本だ。
だから宿命的に、パストレーシングには粒子状のノイズがつきまとう。つぶつぶノイズは不正な計算結果ではない。収束しなかった確率の一部であり、まっとうな計算結果だ。なのでノイズを軽減するには、根本的には計算回数を増やすしか方法はない。まあ、パストレーシングが苦手なシーンを避けるとか、そういうテクニックはあるんだけど。
長くなってしまったので、今回はここまで。次回はマテリアルの概念に触れて、Superflyの設定項目はその次かな。