title: "レイマーチング入門" description: "レイマーチングの基本原理を学びます。レイの定義とスフィアトレーシングのアルゴリズムで、フラグメントシェーダーだけで3Dオブジェクトを描画します。" chapter: "09-advanced" order: 2 challenge: description: "複数の球体を配置してみましょう。異なる位置に3つ以上の球体を配置し、min()で最も近い球体との距離を返す距離関数を作ってください。" hints:
- "球体のSDFは length(p - center) - radius で計算できます。関数として sdSphere(vec3 p, vec3 center, float radius) を定義しましょう。"
- "複数のオブジェクトは min() で結合します。例: float d = min(sdSphere(p, c1, r1), sdSphere(p, c2, r2))"
- "3つの球体を配置するには、min を入れ子にします。各球体に異なる center と radius を指定しましょう。"
レイマーチング入門
フラグメントシェーダーで3D?
これまで私たちは2D平面上でパターンを描いてきました。しかし、フラグメントシェーダーだけで3Dシーンを描画することもできます。それがレイマーチングです。
レイマーチングは、頂点データやメッシュを一切使わずに、数学だけで3Dオブジェクトを描画する手法です。
レイとは
3Dシーンを画面に描画するには、カメラから各ピクセルに向けて**レイ(光線)**を飛ばします。
レイは2つの要素で定義されます:
- origin(原点) — レイの出発点(カメラ位置)
- direction(方向) — レイの進む方向
vec3 ro = vec3(0.0, 0.0, -3.0); // カメラ位置
vec3 rd = normalize(vec3(uv, 1.0)); // レイの方向
各ピクセルのUV座標からレイの方向を計算し、シーン内の何かにぶつかるまで進めます。
SDF(Signed Distance Function)
レイマーチングの核となる概念がSDF(符号付き距離関数)です。空間内の任意の点から、最も近いオブジェクトの表面までの距離を返す関数です。
- 正の値 → オブジェクトの外側
- 0 → 表面上
- 負の値 → オブジェクトの内側
球体のSDFは最もシンプルです:
float sdSphere(vec3 p, float radius) {
return length(p) - radius;
}
点 p から原点(球の中心)までの距離から半径を引く。直感的です。
スフィアトレーシング
スフィアトレーシングは、SDFを使ったレイマーチングの手法です:
- レイの出発点でSDFを評価し、最も近い表面までの距離
dを得る - レイを
dだけ進める(この距離以内にはオブジェクトがないことが保証される) - 新しい位置でまたSDFを評価
dが十分小さくなったら「ヒット」(表面に到達)- 一定回数繰り返してもヒットしなければ「ミス」(背景)
float raymarch(vec3 ro, vec3 rd) {
float t = 0.0;
for (int i = 0; i < 100; i++) {
vec3 p = ro + rd * t;
float d = sdSphere(p, 0.5);
if (d < 0.001) return t; // ヒット
if (t > 100.0) break; // 遠すぎる
t += d; // SDFの距離だけ安全に進める
}
return -1.0; // ミス
}
このアルゴリズムの美しい点は、表面に近づくほどステップが小さくなり、精度が上がることです。
基本的な構造
レイマーチングシェーダーの基本構造:
void main() {
vec2 uv = (gl_FragCoord.xy - u_resolution * 0.5) / u_resolution.y;
vec3 ro = vec3(0.0, 0.0, -3.0); // カメラ位置
vec3 rd = normalize(vec3(uv, 1.0)); // レイ方向
float t = raymarch(ro, rd);
vec3 color = vec3(0.0); // 背景色
if (t > 0.0) {
color = vec3(1.0); // ヒット時の色
}
fragColor = vec4(color, 1.0);
}
UV座標は中心を原点にし、アスペクト比を補正します。
複数のオブジェクト
SDFを min で組み合わせると、複数のオブジェクトを配置できます:
float scene(vec3 p) {
float d1 = sdSphere(p, 0.5);
float d2 = sdSphere(p - vec3(1.5, 0.0, 0.0), 0.3);
return min(d1, d2);
}
min は和集合(Union)を表します。最も近い表面までの距離を返すので、どちらのオブジェクトの表面にもヒットします。
右のエディタでは、1つの球体をレイマーチングで描画しています。
まとめ
- レイマーチングはフラグメントシェーダーだけで3Dを描画する手法
- レイ = origin(出発点)+ direction(方向)
- SDF(符号付き距離関数)で表面までの距離を計算
- スフィアトレーシングでSDFを繰り返し評価してレイを進める
minで複数オブジェクトを組み合わせる