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