ライティング


title: "ライティング" description: "レイマーチングでのライティングを学びます。SDFの勾配から法線を計算し、ランバートとフォンの照明モデルを実装します。" chapter: "09-advanced" order: 3 challenge: description: "球体に加えて床(無限平面)を追加し、球体の影を床に落としましょう。平面のSDFを追加し、影はライトに向かってレイマーチングして判定します。" hints:

  • "平面のSDF: sdPlane関数でp.yからheightを引いた値を返すことで、y=heightの水平な床を定義できます。"
  • "影の判定: ヒット点からライト方向にレイマーチングし、途中で何かにぶつかったら影です。"
  • "影の実装: ヒット点を法線方向に少しオフセットしてからライト方向にraymarchし、何かにぶつかったらshadowを0.3にすることで簡易的なハードシャドウが実装できます。"

ライティング

なぜライティングが必要か

前回のレイマーチングでは球体を描画できましたが、見た目は平面的でした。ライティングを加えると、3Dオブジェクトに奥行きと立体感が生まれます。

ライティングには法線ベクトル(表面の向き)が必要です。レイマーチングでは、SDFの勾配から法線を求められます。

SDFの勾配から法線を計算

SDFの勾配(gradient)は表面に垂直なベクトル、つまり法線です。数値微分で近似します:

vec3 calcNormal(vec3 p) {
    vec2 e = vec2(0.001, 0.0);
    return normalize(vec3(
        scene(p + e.xyy) - scene(p - e.xyy),
        scene(p + e.yxy) - scene(p - e.yxy),
        scene(p + e.yyx) - scene(p - e.yyx)
    ));
}

各軸方向にほんの少しだけずらした点でのSDF値の差を取ることで、その方向の変化率(偏微分)を近似しています。6回のSDF評価が必要ですが、これで任意のSDF形状の法線が得られます。

ディフューズ照明(ランバート反射)

ランバート反射は最も基本的な照明モデルです。表面がライトに正対しているほど明るく、横を向くほど暗くなります:

vec3 lightDir = normalize(vec3(1.0, 1.0, -1.0));
float diffuse = max(dot(normal, lightDir), 0.0);

dot(normal, lightDir) は法線とライト方向のなす角のコサインです。0度(正面)で最大、90度以上で0になります。

スペキュラー照明(フォン反射)

フォン反射は鏡面反射のハイライトを表現します。ライトが反射する方向と視線が一致したときに明るくなります:

vec3 viewDir = normalize(-rd);
vec3 reflectDir = reflect(-lightDir, normal);
float specular = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);

pow の指数(32.0)は光沢度を表します:

  • 小さい値(4〜8) → 広くぼんやりしたハイライト(マット)
  • 大きい値(32〜128) → 鋭く集中したハイライト(光沢)

アンビエント照明

影になっている部分も完全な黒にならないよう、アンビエント(環境光) を加えます:

float ambient = 0.1;

単純な定数ですが、これがないと影の部分が真っ黒になってしまいます。

照明の合成

3つの要素を合成して最終的な色を決めます:

vec3 objectColor = vec3(0.8, 0.2, 0.2);
vec3 lightColor = vec3(1.0);

vec3 color = objectColor * (ambient + diffuse) + lightColor * specular;

シャドウ

ヒット点からライトの方向にレイマーチングし、途中で何かにぶつかったら影です:

float calcShadow(vec3 p, vec3 lightDir) {
    float t = 0.02; // 表面から少しオフセット
    for (int i = 0; i < 50; i++) {
        float d = scene(p + lightDir * t);
        if (d < 0.001) return 0.3; // 影
        t += d;
        if (t > 20.0) break;
    }
    return 1.0; // 影なし
}

右のエディタでは、ランバート+フォンの照明モデルで球体をライティングしています。

まとめ

  • SDFの数値微分で法線ベクトルを計算
  • ディフューズ(ランバート)— 表面とライトの角度に応じた明るさ
  • スペキュラー(フォン)— 鏡面反射のハイライト
  • アンビエント — 影部分を完全な黒にしないための環境光
  • シャドウ — ライト方向へのレイマーチングで判定