第5章・先取り編 レイトレーシングとレイマーチングの基礎
目的:画素からレイを飛ばす方法、球への解析的交差判定、SDFに基づくsphere tracing、法線と拡散反射を理解する。 左右比較の実験 / OpenGLとWebGL 本編第3章(ラスタライズ・Z-buffer)は未公開。ここはレイによる描画を先に知るための先取り編。
1. 画素から3D空間へ
ラスタライズでは3Dの三角形を画面へ投影して、覆われる画素を探す。レイを使う方式では反対に、画素から3Dへレイを飛ばして最初に当たる面を探す。
左のデモは、球と平面について数学的に交点を求める解析的な一次レイのray casting。反射・屈折や経路を繰り返し追跡するレイトレーシング、パストレーシングとは区別する。
右のデモは、距離関数の値を使って進むsphere tracing。レイマーチングはもっと広い総称であり、たとえばボリュームを固定間隔で積分する方法も含む。レイマーチング全般がSDFを必要とするわけではない。
カメラの原点を \(\mathbf{o}\)、単位方向を \(\mathbf{d}\) とすると、レイ上の位置は
方向を正規化すれば \(t\) は実距離。画素 \((x,y)\)、幅 \(W\)、高さ \(H\)、垂直画角 \(\theta\) のとき、
この教材はカメラが+Zを見る座標系(第1章と同じ)。OpenGLで慣例的な−Z方向とは混同しない。
2. レイトレーシング:球の交点を解く
球の中心 \(\mathbf{c}\)、半径 \(r\) なら表面は
レイの式を代入し、\(\mathbf{q}=\mathbf{o}-\mathbf{c}\) と置く。方向は単位ベクトルなので、
判別式が負なら交点はない。実数解があれば正の最小の解が最初の表面(球の内部からなら出る側)。実装は次の考え方。
const q = subtract(origin, center);
const b = dot(q, direction); // directionの長さは1
const k = dot(q,q) - radius*radius;
const delta = b*b-k;
if (delta < 0) return Infinity;
const t0 = -b-Math.sqrt(delta), t1 = -b+Math.sqrt(delta);
return t0>1e-4 ? t0 : t1>1e-4 ? t1 : Infinity;
床を水平面 \(y=h\) とすると、方向のy成分が0でない場合、
で求まる。複数の物体について交点を求め、最も手前の正の \(t\) を選ぶ。左のデモでは球2個+床で各画素3回の交差テストを行う。
複雑な三角形メッシュは全三角形を総当たりせず、BVHなどの加速構造で交差候補を絞ることが多い。本章はそこを省略している。
3. レイマーチング:SDFで安全に前進
SDF(Signed Distance Function)は表面までの距離を返し、形状の外側で正、内側で負。球と水平面は以下のように書ける。
複数の形状の和集合なら、外側にいるとき
は表面まで進んでよい距離の下限になる。その距離だけ進めば表面を飛び越えないというのがsphere tracingの基本。
let t = 0;
for (let i=0; i<maxSteps && t<=maxDist; i++) {
const p = addScaled(origin,direction,t);
const distance = sceneDistance(p);
if (distance<epsilon) return {hit:true,t,steps:i+1};
t += distance;
}
return {hit:false};
重要な終了条件は3つ。
- ε(epsilon):距離が閾値未満なら到達と見なす。大きすぎると手前で停止して境界がずれ、小さすぎると計算が増える。
- 最大ステップ数:到達しなくても打ち切る。低く設定すると画面に欠けができる。
- 最大距離:シーンの探索範囲外なら打ち切る。
ここでの t += distance は、距離の下限が保証される関数を使うことが前提。sin(x)+sin(y)+sin(z)、一般のmetaball、gyroidなどの暗黙関数を、根拠なくSDFとしてそのまま加算してはいけない。安全係数、固定刻み、符号変化検出、二分法などの別設計が必要になる。
また本章は形状の外にカメラを配置している。内部から出るレイや、交差・変形させた距離場では符号や終了条件に注意。
4. 表面法線と簡単な光
球の交点 \(\mathbf{p}\) での法線は
SDFでは勾配から \(\mathbf{n}\approx\operatorname{normalize}(\nabla d)\) と求めることもできる。本章では交点を探す方法だけを比較するため、両方式とも球の解析的法線を共用し、同じLambert型の簡易照明を適用する。
\(\mathbf{l}\) は単位光方向。影レイ、反射、屈折、間接照明は未実装であり、物理ベースの光輸送ではない。
5. 左右の画像を使った実験
デモを開く。CPUのJavaScriptとCanvas 2Dで同じ2球+床を描き、WebGL・WebGPUには依存しない。
- 左(解析法)と右(SDF)で同じ形が出ることを見る。
- 最大ステップ数を8にする。右だけ到達前に打ち切られ、欠ける領域が増える。
- ステップ数を96に戻し、εを0.04から0.004へ変える。必要ステップ数と境界の違いを調べる。
- 左右に視線を回転する。斜めに接近するレイは、進み方が変わる。
- 左右いずれかの画像をクリックする。選択画素の方向、解析的交差距離、SDFの
tと距離の履歴が下に表示される。
表示する処理時間は、小さいCPU実装の参考値。「3回の交差テスト」と「N回のSDF評価」は演算の重さが異なるため、単純に数字だけで高速性を比較できない。
6. どこが得意で、何がまだない?
- 解析的レイ交差:球・平面・三角形などは明確な式が使える。多数の形状を扱うにはBVHなどが重要。
- SDF sphere tracing:組み合わせ形状や滑らかな形状を表現しやすい。距離場の評価費用、精度、細い形状、非SDFの誤用には注意。
- 固定幅レイマーチング:密度場の積分や、符号変化を探す用途など。SDFで距離を稼ぐ方法とは区別する。
- パストレーシング:光源や面での反射・散乱を多数回サンプリングする。ノイズとサンプル数が重要。本章では未実装。
いずれもCPU/GPUに実装できる。レイトレーシング=WebGPU、レイマーチング=WebGLという1対1の対応はない。
7. 演習と参考資料
- 原点 \((0,0,-3)\)、方向 \((0,0,1)\) と、原点を中心とする半径1の球の交点を求める(答え:\(t=2\))。
- 接線方向のレイ(判別式0)と、外れるレイ(判別式が負)を考える。
- ステップ上限8と128で同じ画素の履歴を比べる。
scene.spheresに球を1個追加して、左右とも同じ追加物体を描けることを確認する。- SDFから法線を数値微分で求め、解析的な法線と比較する。
ソース:ray-math.js(方程式・SDF・照明)、app.js(左右描画と操作)、index.html(UI)。
参考文献:John C. Hart, Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces (1996)。Inigo Quilez, Distance functions。
次はSDFの合成、ソフトシャドウ、反射や屈折、BVH、GPUでの並列化へ進める。