2014年3月1日土曜日

レイトレーシングの拡散面の輝度計算

3次元グラフィックの描画方法の一つであるレイトレーシング。光の反射・屈折などが正確に計算できる手法で結構キレイに画像生成ができます。ただし、間接光が計算できないので、暗めの画像になってしまいます。本ブログでも、Scalaで実装したレイトレーシングで描画した画像を載せていましたが、実は今までレイトレーシングよって生成される画像の各ピクセルの色の計算を結構適当に計算していました。今回は、この色の計算について考えてみたいと思います。

レイトレーシングの復習

簡単にレイトレーシングの仕組みについて復習してみましょう。現実の世界で、光源から発せられた光が目に届くまでには、いろんな物体に何回も衝突し、その一部が目に届きます。写真のような画像を作りたければ、光源が発せられた全ての光の道筋を計算すればよいことになります。でも、光源からの光の道筋を全て計算しても、最終的に目に届く光というのは、ごく一部です。ほとんど無駄な計算になります。コンピュータの使えるリソースは、限られているのに無駄な計算を何日もかけて計算する気にはなりません。もっと効率的に計算できる方法はないか。ちょっとぐらい写真画質じゃなくてもいいよ。と思った人が、たぶんレイトレーシングという効率的な方法を考えだしたんでしょう。

光源から光を追跡したら、膨大な計算量を必要とする上に、目に届くのは一部。じゃあ、目に届いた光だけを逆に追跡すればいいんじゃね。そう考えたんですね。目に届いた光というのは、スクリーン上のある点と目のある点を結ぶ半直線です。つまり、光源からの光を追跡するんじゃなくて、目がある位置からスクリーン上のある点を通過する半直線(光線=レイ)を追跡(トレース)しようってわけです。これがレイトレーシング(光線追跡)です。光源からの光は無数にありますが、目からの光線は、目がある場所からスクリーン上の各ピクセルとを結ぶ半直線のみなので、ピクセル数の光線を追跡するだけで済みます。

これで、目に届く光だけを計算することができます。目から出た光線は、シーンの中にあるいろんな物体にぶつかって、材質によって反射したり屈折したりして、ある光線は、光源にぶつかり、ある光線は、どこにもぶつからずに闇に消えていきます。光源にぶつかった光線は、その光線が通過したスクリーン上の点を明るくするでしょう。どこにもぶつからなかった光線は、その光線が通過したスクリーン上の点を明るくすることはありません。このようにして、各ピクセルの色が決められていきます。なので、物体表面の反射や屈折を計算して、どの光線が光源とぶつかるかを計算するのが重要になってきます。光線の反射や屈折の計算は、鏡や透明なガラスなどの材質だったら簡単です。では、ざらざらな表面の場合は、どうでしょう?ざらざらした表面は、光が乱反射します。ちなみに、このような性質の表面を拡散面と呼ぶようです。拡散面では、光線は、いろんな方向に反射します。鏡みたいに一方向に反射しません。ん?ちょっと待てよ。色んな方向に反射する?ということは、無数に分散する光線を追跡していかなきゃならんのですね。これじゃ、光源から発せられた光線を追跡するのと変わらんやん!だめやん。振り出しに戻ったやん。と思ってしまうかもしれませんが、レイトレーシングでは、拡散面にぶつかったら、そこで光線の追跡終えます。これがレイトレーシングが正確にピクセルの色を計算できないとこです。では、どうするのかというと、拡散面にぶつかった光線のピクセルの色は、拡散面の位置と光源の位置から近似計算します。間接光の計算はしません。間接光を計算するには、拡散面で分散した光線を追跡しなければならないからです。ちなみに、この追跡を統計的に行うのがパストレーシングです。ということで、レイトレーシングは、拡散面は近似計算で鏡やガラスなどの材質は、正確に計算するレンダリング手法です。

拡散面に届く光の量と目に届く光の量

先程も、拡散面では近似計算して、ピクセルの色を計算すると言いました。ここでは、具体的にその計算方法を考えてみます。以下のピクセル値の計算は、僕が勝手に近似計算しているだけなので、よりよい方法があるかもしれませんし、間違っているかもしれません。あしからず。

拡散面から来る光の量を計算を簡単にするために、光の量の種類を2つに分類します。1つは、光源から拡散面に届く光の量で、2つめは、拡散面から目に届く光の量です。

拡散面に届く光の量の近似計算

光源から拡散面上のある点に入射してくる光の量を正確に計算するには、その点を中心にした単位半球面を考えます。半球面は、その点での法線方向側にあります。つまり、拡散面の表に半球面があります。その単位半球面を横切る光線の数が、この点に入射してくる光の量です。これは、入射光線ベクトルを半球面に沿って積分することと同じことです。半球面に入射してくる光線ベクトルは、光源から直接入射してくるのもあれば、何回も反射してきて、入射してくるのもあります。前者の光線は直接光、後者の光線は間接光です。先程も言及したとおり、この入射ベクトルを全て計算するのは、大変なのでレイトレーシングでは、この積分計算を正確に計算せずに近似計算します。
まず、間接光は計算しません。間接光を計算するには、何回も反射した光線を考慮しなければならないので、非常に計算量を要するからです。直接光だけを考えましょう。半球面上を横切る直接光の数は、半球面上に投影される光源の面積に比例するはずです。半球面に投影される光源の面積は、図にあるように、魚眼レンズで見た時の光源が形作る図形の面積のようになると考えるとイメージしやすいでしょう。それでは、この面積を計算しましょう、と言いたいところですが、この積分計算もややこしそうなので、思い切って端折って、この直接校の計算も近似することにします。
では、どのようにして近似しましょう。たぶん、半球面上に投影される光源の面積は、図のように、拡散面上の点から光源を見た時の角度にだいたい比例するんだと思います。きっと。

以下にその角度を計算するコードを示します。
  val v0 = p -> center // 拡散面から光源の中心までのベクトル
  val d = radius // 光源を囲む円の半径
  val nt = normal * (v0 * normal) // v0の光源の法線方向成分
  val vr = v0 + (nt -> v0).normalize * d // 拡散面から光源の端までのベクトル
  val dt = vr angle v0 // 拡散面から光源を見た時の角度の半分

目に届く光の量の近似計算

次に、拡散面から目に届く光の量を考えます。拡散面に入射した光の量の一部が目に届きます。拡散面なので、この面に入射した光が全て目に届くはずがありません。そのごく一部の光が目にとどきます。正確には目に届く光は、ちょうど目のある位置の方向に偶然反射した光だけです。したがって、拡散面で反射する光が、どの方向にも一様に反射すると仮定すると、目に届く光の量は、拡散面に入射した光の量を半球面の面積で割った量だけになるはずです。ここで半球面の半径は、拡散面上の点から目の位置までの距離です。

それに対して、鏡で反射して目に入ってくる光は、鏡のその点に入射した光が全て同じ方向(目がある方向)に反射した光です。なので、この場合は、半球面の面積で割る必要がありません。

描画結果

レイトレーシングで描画した結果です。この画像をみる限り、光の量の計算は、よい近似になってるのではないでしょうか。

0 件のコメント:

コメントを投稿