レイキャスティング

Powered by MathJax

最も単純な3次元図形「球」を対象として, レイキャスティング法によるレンダリングプログラムを作成する. シェーディング(陰付け)とシャドウイング(影付け)を実装し, Fig.1 のように(ある程度)リアルな画像を生成することを目標とする.

要するに,POV-Ray の簡易版を作るんだ.
教科書の該当範囲:第7章

なお,Fig.1 では, 拡散反射率や鏡面反射率の調整によって, 質感の異なる球体が表現されている.

Fig.1. レイキャスティング法による球体のレンダリング例
Fig.1 では,手前がプラスチック的,奥が金属的な物質のつもり...

光線と物体の交差計算

レイキャスティング法(ray-casting)では, ある点から発射された光線(照明光線,視線)を追跡し, どれかの物体との衝突を検出する. この衝突検出は,次の2種類の処理で共通に利用される:

なお,POV-Ray などの実用的な CG ソフトウェアで採用されている レイトレーシング法(ray-tracing)では, 現実の光学現象をより忠実に考慮し, 衝突後にさらに反射・屈折した光線をも再帰的に追跡する. が,その基本の処理はレイキャスティングである. (レイトレーシング≒再帰的レイキャスティング)

ここでは,簡単のため物体の形状を球に限定し, レイキャスティングをベクトル的に解析する. Fig.2 のような配置を考えよう.

Fig.2. 球体に対するレイキャスティングの配置

各変数の意味は次の通り:

また,光線と交点,球と交点との関係は,それぞれ,次の2式で表される:

これらの2式から \( \V{P}(t) \) を消去すると, 次の二次方程式が得られる:

\[ \N{\V{D}}^2 t^2 + 2 \V{D}\.\P({\V{P}_1 - \V{P}_0}) t + \N{\V{P}_1 - \V{P}_0}^2 - R^2 = 0 \]
この方程式の導出には,ベクトル演算の公式 \( \V{V}\.\V{V} = \N{\V{V}}^2 \) を利用した.

この方程式を解いて \( t \) を求めれば, 交点 \( \V{P}(t) \) を算出できる. なお,交点の個数は1個とは限らないことに注意しよう.

二次方程式の解の個数は,2個または1個または0個だったなー.(遠い目)

以下,簡単のため方程式を次のように置き換えて解説する.

\[ a t^2 + b t + c = 0 \] ただし, \[ \begin{array}{rcl} a & = & \N{\V{D}}^2 \\ b & = & 2 \V{D}\.\P({\V{P}_1 - \V{P}_0}) \\ c & = & \N{\V{P}_1 - \V{P}_0}^2 - R^2 \\ \end{array} \]

シェーディング

シェーディング(shading)では, 光源から放射され,物体表面で反射され,視点へ到達した光の強度を求める. この処理でも,ベクトル演算が利用される. ベクトルの配置を Fig.3 に示す.

Fig.3. 光線等のベクトル配置

各変数の意味は次の通り:

ベクトル \( \V{L} \) と \( \V{V} \) の方向に注意. 光源・視点ではなく物体を基準とし, 光線・視線の進行方向とは逆向きに定義されている.
計算コスト削減のため, すべての方向ベクトルの大きさを1に正規化(normalize)しておく. 例えば,ベクトル \( \V{V} \) を定義する際, あらかじめ,大きさ \( \N{\V{V}} \) で割っておく.

古典的なシェーディングモデル:

以上の反射光強度の合計 \( I = I_\mathrm{ad} + I_\mathrm{d} + I_\mathrm{s} \) を 物体表面の色として画像化する.


デプスバッファ法による隠面処理

以前利用した BSP-tree 法では, ポリゴンモデルの隠面処理を高速に実現していた. しかし,この方法は曲面体に対してはうまく適用できない. そこで今回は,デプスバッファ法(depth buffering)を利用する.

デプスバッファは,画像の一種であり, 色情報の代わりに深度(視点から物体表面までの距離,D値) を画素値としている. 深度画像の例を Fig.5 に示しておく. 色が白の画素は深度が大(遠距離),黒は小(近)を表している.

ちなみに,通常のRGB画像は,フレームバッファと呼ばれる.
Fig.5. 深度画像の例(Fig.1 のデプスバッファ)

デプスバッファ法の基本アルゴリズムは次の通り:

上書きなので,奥側の物体に対するシェーディングの処理は無駄になってしまう. 改善策としては,シェーディングの手順を反復終了後に移動すれば良い. 今回の基本プログラムでは,この改善を採用している.

これで,二分木生成やソーティングなどの前処理を施さなくても, 隠面処理を実現できる. 教科書 pp.72-74 を参照せよ.

アルゴリズム的には,力ずく(非効率)な方法ではあるが, 単純なので,現代の GPU(CG 専用のプロセッサ)を使えば, ハードウェア的に(高速に)処理できる. ただし,今回は,GPU の機能は使わず,ソフトウェア的に処理してみよう.

基本プログラム

ダウンロードして実行してみよう:

動作確認環境:Linux,GCC,ImageMagick(convert, display, identifyコマンド)
(以前実施済み)環境設定も必要.

この基本プログラムでは, 環境光と拡散反射のシェーディングのみが実装されている. 鏡面反射およびシャドーイングについては実装されていない. (これが本日の課題.)

なお,ベクトル演算の注意点として, 計算しようとしている量が ベクトル or スカラのどちらであるのか? 見極めることが非常に重要である. たくさんの関数の中から,適切な関数を選び出すこと.


シャドーイング

基本プログラムでは, 照明光が物体を通過して他の物体を照らしている状況であった. しかし,現実の世界では, 光は最初に交差した物体によってさえぎられ, そこに影が生じるハズである. 余裕のある者は, この現象をプログラムに実装してみよう.

基本的なアルゴリズムを示す:

  1. 注視点(視線と物体の交点)の位置ベクトル \( \V{P}_\mathrm{lookat} \) を求める. (すでに算出済みのハズ.)
    これが交差計算における始点 \( \V{P}_1 \) に対応する. Fig.2 を参照せよ. なお,このベクトルの計算については, ビューイング処理の結果として既に求められているハズであり, 基本プログラム raycast.c でも実装済み.
  2. 照明光の方向ベクトル \( \V{L} \) を求める. (すでに設定済みのハズ.)
    これが交差計算における速度 \( \V{D} \) に対応. 光線の進行方向とは逆方向と定義されているので, 物体から光源の方向へ進む速度ベクトルとして好都合だなー.
  3. すべての物体(ただし,注視対象以外)に対して 交差計算を実行する. そして,交点数に応じて:

要するに,視点の代わりに注視点, 視線の代わりに照明光線を使って交差判定すればよい. 他の物体との間に交差があれば, その注視点は影領域内ということになる.

自分自身(注視対象物体)を検査対象外とする理由は, 注視点の計算結果に誤差が含まれているためだ. 算出された注視点は,ぴったり表面上にはなく, 約50%の確率で球のちょっとだけ内部に入り込んでいる. そして,自分自身によってさえぎられている,と誤解してしまうことになる. (本来ならば,このような自己隠蔽についても精密に検査対象とすべきだが, 今回の球体のような凸形状であれば,自分自身を検査対象外としても問題ない.)

本日の課題

基本プログラム raycast.c を元にして,鏡面反射を実装せよ. そして,球体・材料・照明などのパラメータを自由に変更し, 意味のあるオリジナル画像を生成すること.

ヒント: 基本的には,反射光のベクトル計算式をそのままコード化するだけ. ベクトル演算の関数については,ファイル vect.h 等を参照.
ありがちなまちがい: クドいが,光の強度は常に正値(プラス)だ.負値(マイナス)は,ありえない. そしてこれは,合計についてだけでなく,各反射成分についての話でもある.

また,余裕のある者はさらに,シャドーイングについても実装せよ. (この場合,もちろん,影が写っている画像を作ること.)

レポート提出

実行結果の画像を作るには,以前と同様,import を使えばよい.


上級者向け

さらにやる気のある者は...

Fig.6 は,球体と平面に対するレイトレーシングの実行例である.

Fig.6. レイトレーシング法による球体・平面のレンダリング例 (フレームバッファおよびデプスバッファ)
はい,POV-Ray の劣化版クローンを作ったんです. POV-Ray を使ってズルしてませんからねー, の証拠としてデプスバッファも掲げておきました. (まあ,POV-Ray でも工夫次第でデプスバッファを作れるけど.)