Skip to content

第06回 レイマーチのライティング

前回: 第05回 3D SDF とレイマーチ

今回の授業内容

  • SDF の微小な差から法線を推定する
  • Lambert 拡散反射と鏡面反射を追加する
  • 光方向へレイを進めてソフトシャドウを作る
  • 近傍の距離から Ambient Occlusion を近似する
  • Smooth Min で形状を有機的に接続する

SDF から法線を推定する

表面の周囲で SDF を少しずらして評価し、距離が大きくなる方向を法線とします。

hlsl
float3 EstimateNormal(float3 p)
{
    const float e = 0.001;
    float3 offsetX = float3(e, 0.0, 0.0);
    float3 offsetY = float3(0.0, e, 0.0);
    float3 offsetZ = float3(0.0, 0.0, e);

    float3 gradient = float3(
        MapScene(p + offsetX) - MapScene(p - offsetX),
        MapScene(p + offsetY) - MapScene(p - offsetY),
        MapScene(p + offsetZ) - MapScene(p - offsetZ)
    );
    return normalize(gradient);
}

1ピクセルの法線に複数回の MapScene が必要です。精度を上げるほど、計算回数と数値誤差の両方を考える必要があります。

光と視線を使って明るさを求める

hlsl
float3 n = EstimateNormal(hitPosition);
float3 l = normalize(lightPosition - hitPosition);
float3 v = normalize(cameraPosition - hitPosition);
float3 h = normalize(l + v);

float diffuse = saturate(dot(n, l));
float specular = pow(saturate(dot(n, h)), smoothnessPower);
float fresnel = pow(1.0 - saturate(dot(n, v)), fresnelPower);

前期で Shader Graph のノードとして扱った内積、Normalize、Power が、HLSL の短い式に対応します。

ソフトシャドウを近似する

命中点からライトへ向けて、もう一度レイマーチします。レイ上で形状に近づいた程度を記録すると、影の輪郭をなめらかにできます。

hlsl
float SoftShadow(float3 origin, float3 direction, float maxDistance)
{
    float visibility = 1.0;
    float traveled = 0.02;

    for (int i = 0; i < 48; i++)
    {
        float distanceToScene = MapScene(origin + direction * traveled);
        visibility = min(visibility, 12.0 * distanceToScene / traveled);
        traveled += clamp(distanceToScene, 0.01, 0.5);
        if (distanceToScene < 0.001 || traveled > maxDistance) break;
    }
    return saturate(visibility);
}

自己干渉を避けるため、レイの始点を表面から法線方向へ少しずらします。

Smooth Min で形状をつなぐ

hlsl
float SmoothMin(float a, float b, float k)
{
    float h = saturate(0.5 + 0.5 * (b - a) / k);
    return lerp(b, a, h) - k * h * (1.0 - h);
}

min(a, b) では形状の境界が鋭くつながります。Smooth Min は境界に幅を持たせ、液体や粘土のように滑らかに合体させます。

実習:粘性のある SDF オブジェクトを作る

  1. 前回の球とボックスに法線を追加する
  2. Lambert で光源の向きを表す
  3. 鏡面反射とフレネルを追加する
  4. 光源の位置をプロパティで変更する
  5. ハードシャドウとソフトシャドウを比較する
  6. 球2個を Smooth Min で接続する
  7. 時間で球を移動し、接続部の変化を確認する

授業内課題:接地感を調整する

  • ソフトシャドウの硬さを3段階で比較する
  • Ambient Occlusion を有効・無効にして接地部を比較する
  • 表面が暗くなりすぎない設定を決める
  • 採用値と理由を記録する

完成条件

  • SDF から法線を推定できる
  • 拡散反射、鏡面反射、影を表示できる
  • Smooth Min で複数形状を接続できる
  • ライティングと影で MapScene が複数回実行されることを説明できる

今回のまとめ

SDF は形状の交差だけでなく、法線、影、近傍の閉塞、形状合成に使えます。複数の追加レイは見た目を豊かにしますが、それぞれが反復計算を増やすことも意識します。

次回: 第07回 ボリュームレイマーチ