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



- [レイトレーシング(1): バージョン1の定義、ベクトル演算](http://qiita.com/eijian/items/fd30ed4c59bda658ebff)
- [レイトレーシング(2): `Algebra`モジュールをいじる](http://qiita.com/eijian/items/44b3ca8511702da811a1)
- [レイトレーシング(3): フォトンマップ生成の大枠を考える（ついでに光源も）](http://qiita.com/eijian/items/cb31e663c27584a9d064)
- [レイトレーシング(4): フォトンの生成](http://qiita.com/eijian/items/bc871c2ced1e9fa55b26)
- [レイトレーシング(5): 物体の定義](http://qiita.com/eijian/items/cb768bd137d0c4aa5012)
- [レイトレーシング(6): やっとフォトン追跡](http://qiita.com/eijian/items/f7faec4dcdc4ea69b96d)
- [レイトレーシング(7): 光線追跡処理の大枠](http://qiita.com/eijian/items/486fa66b0e156d11d395)
- [レイトレーシング(8): 輝度推定による画像生成](http://qiita.com/eijian/items/4d0cc3f67aa0f1cca801)
- [レイトレーシング(9): フィルタ、乱数などで抗ってみる](http://qiita.com/eijian/items/33e9b42d9ff8a33f72e7)

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

## 「物体」定義

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

```haskell
photoncaches <- tracePhotons objs photons
```

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

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

```haskell
data Object = Object Shape Material
```

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

```Material.hs
data Material = Material Double Double Double
```

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

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

```Geometry.hs
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数学 (Fletcher Dunn,Ian Parberry　著、オライリージャパン)

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

## 「形」に必要な関数

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

```Geometry.hs
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$ のリストとする。

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

```Geometry.hs
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`型にしている。

## まとめ

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

[ソース v.1.0.0.1](https://github.com/eijian/raytracer/tree/version-1.0.0.1)