Haskellでいってみよう

日曜プログラマにも満たないレベルでもHaskellで何かソフトウェアを作りたい!

レイトレーシング(5): 物体の定義

前回までで、光源から放射されるフォトンが生成できたので、次はそれを 追跡してフォトンマップを作ることになるが、そのためには描かれる「物体」を 準備しないといけない。

「物体」定義

次に考える処理は、メインルーチン中の次の部分だ。

photoncaches <- tracePhotons objs photons

objsはシーン中の物体のリスト、photonsは前回生成したフォトンのリストである。 だから、tracePhotonsの処理を考える前にobjsがどういうものかを定義する必要がある。

本プログラムでは物体をObject型で定義しよう。物体に必要な情報は何だろうと考えると 「形」と「材質」だろう、と考えてみた。今後他にも出てくるかもしれないが、今は その二つにしておく。

data Object = Object Shape Material

材質は木とかガラスとかで、情報としては物体の色や反射率、透明度などなどだ。 考え出すととても複雑な構造になるが、まず作ろうとしている"バージョン1"では 物体は「表面が拡散反射のみ」で「フォトンの追跡は反射を無視」としたのだった。 だからフォトンの追跡は「物体にぶつかったらその位置を記録して終わり」という ことになる。実はこれだとフォトンマップを作るためには材質として何の情報も要らない のだが、あとあとレイトレーシングで画像を作る段になったらさすがに色の情報が 必要なのでその分だけ定義しておこう。「拡散反射率」だ。

data Material = Material Double Double Double

3つのDoubleは赤緑青それぞれの波長での拡散反射率を表す。0から1の間の数値を 設定する。色や輝度をどのような型で表すかまだ決めていないが、その検討次第では 型コンストラクタの引数は型が変わるだろう。

さて、今大事なのは「形」の方だ。レイトレーシングでは、無限平面や球面、二次曲面、 ポリゴンなど様々な「形」が使われる。光源と同じだ。光源の定義では失敗したので、 形の定義では最初から多相性を意識して定義しよう。ただしひとまず無限平面と球面のみ 扱うことにする。(ちょっと先のことも考え、大きさのない"点"も入れておくが)

data Shape = Point Position3
           | Plain Direction3 Double
           | Sphere Position3 Double

ここで、無限平面と球面は次の方程式を満たす三次元空間中の点\boldsymbol x (位置ベクトル)の集合である。

 {
  無限平面: \boldsymbol n \cdot \boldsymbol x = d
}

 {
  球面: ||\boldsymbol c - \boldsymbol x|| = r
}

ここで、 \boldsymbol nは平面の法線ベクトル、 dは平面の位置に 関係するパラメータ、 \boldsymbol cは球の中心座標、 rは球の 半径だ。このあたりの詳しいところはその筋の文献などを参照のこと。たとえば、

実例で学ぶゲーム3D数学

実例で学ぶゲーム3D数学

などに記載がある。上記のPlainSphereの型コンストラクタの引数は、それぞれ  \boldsymbol n, d \boldsymbol c, rである。

「形」に必要な関数

フォトンの追跡は、まず物体と衝突する場所(交点)を求めることから始まる。 バージョン1では反射は考えないので交点計算がやることのすべてと言っていい。 Shape型は上記の通り方程式で記述できるので、交点を求めるのは容易だ。光線(Ray) との連立方程式を解けばよい。交点を  \boldsymbol x = \boldsymbol p + t \cdot \boldsymbol dとすると、 連立させて tを求めれば、位置 \boldsymbol xも解るわけだ。 よって、 tを計算する関数distanceを用意しよう。

distance :: Ray -> Shape -> [Double]
-- Point
distance r (Point p)  = []
-- Plain
distance (pos, dir) (Plain n d)
  | cos == 0  = []
  | otherwise = [(d + n <.> pos) / (-cos)]
  where
    cos = n <.> dir
-- Sphere
distance (pos, dir) (Sphere c r)
  | t1 <= 0.0 = []
  | t2 == 0.0 = [t0]
  | t1 >  0.0 = [t0 - t2, t0 + t2]
  where
    o  = c - pos
    t0 = o <.> dir
    t1 = r * r - (square o - (t0 * t0))
    t2 = sqrt t1

交点がない場合、複数の場合があるので、結果は tのリストとする。

今後反射や屈折を考えたり、輝度計算をするときには交点での法線ベクトルを求める 必要が出てくる。今はいらないが簡単なので定義しておこう。

getNormal :: Position3 -> Shape -> Maybe Direction3
-- Point
getNormal p (Point p') = Nothing
-- Plain
getNormal p (Plain n d) = Just n
-- Sphere
getNormal p (Sphere c r) = normalize (p - c)

「点」の場合法線ベクトルがないので、関数getNormalの結果をMaybe型に している。

まとめ

今回はフォトン追跡の下準備として、「物体」を定義した。次回はフォトンを追跡 することにしよう。