レイマーチング入門


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を使ったレイマーチングの手法です:

  1. レイの出発点でSDFを評価し、最も近い表面までの距離 d を得る
  2. レイを d だけ進める(この距離以内にはオブジェクトがないことが保証される)
  3. 新しい位置でまたSDFを評価
  4. d が十分小さくなったら「ヒット」(表面に到達)
  5. 一定回数繰り返してもヒットしなければ「ミス」(背景)
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 で複数オブジェクトを組み合わせる