GLSL

レイマーチングでアンビエントオクルージョン

レイマーチングでアンビエントオクルージョンを計算するには、レイマーチのステップ数をもとに求める方法と、レイとオブジェクトの交点の法線方向の近似点での距離をサンプリングして求める方法の2通りがあります。以下、サンプルコードはGLSLで書いています。

ステップ数をもとに計算する方法

ステップ数を利用する方法では、レイとオブジェクトの交点を求めるのに必要としたレイマーチのステップ数を最大ステップ数で割った値をアンビエントオクルージョンの値とします。
この方法は、「ステップ数が多い」 => 「レイの近くにオブジェクトが多い」 => 「遮蔽されている」 という理屈です。

以下、サンプルコードです。

#define TMAX 50.0
#define ITERATION 128
float raymarch(in vec3 origin, in vec3 direction, inout int steps) {
    float t = 0.0;
    for (int i = 0; i < ITERATION; i++) {
        steps = i;
        vec3 p = origin + t * direction;
        float d = map(p);
        if (d < 0.02 || t > TMAX) break;
        t += d;
    }
    return t;
}

vec3 render(in vec3 origin, in vec3 direction) {
    int steps;
    float t = raymarch(origin, direction, steps);

    vec3 c = vec3(0.0);
    if (t < TMAX) {
        float ao = 1.0 - float(steps) / float(ITERATION);
        c = vec3(ao);
    }   
    return c;
}

アンビエントオクルージョンだけで色付けしたものは以下のようになります。

ao_steps.png

動いているものは以下から確認できます。
http://glslsandbox.com/e#44291.0

法線方向の近似点での距離をサンプリングする方法

この方法では、レイとオブジェクトの交点の法線方向の近似点で距離をサンプリングすることで、アンビエントオクルージョンを計算します。
近似点での距離を求めている箇所はambientoculustion関数内の以下の箇所になります。

ao += amp * clamp(map(position) / distance, 0.0, 1.0);

もし、近くに交差したオブジェクトしかなければ、map(position) = distanceになります。しかし、サンプリングしている点の近くに別のオブジェクトがある場合はmap(position) < distanceになり、このオブジェクトに交差点が遮蔽されていると考えることができます。

#define TMAX 50.0
#define ITERATION 128
float raymarch(in vec3 origin, in vec3 direction, inout int steps) {
    float t = 0.0;
    for (int i = 0; i < ITERATION; i++) {
        steps = i;
        vec3 p = origin + t * direction;
        float d = map(p);
        if (d < 0.02 || t > TMAX) break;
        t += d;
    }
    return t;
}

float ambientoculusion(in vec3 position, in vec3 normal) {
    float ao = 0.0;
    float amp = 0.5;
    float distance = 0.02;
    for (int i = 0; i < 10; i++) {
        position = position + distance * normal;
        ao += amp * clamp(map(position) / distance, 0.0, 1.0);
        amp *= 0.5;
        distance += 0.02;
    }
    return ao;
}

vec3 render(in vec3 origin, in vec3 direction) {
    int steps;
    float t = raymarch(origin, direction, steps);

    vec3 c = vec3(0.0);
    if (t < TMAX) {
        vec3 p = origin + t * direction;
        vec3 n = normal(p);
        float ao = ambientoculusion(p + 0.01 * n, n);
        c = vec3(ao);
    }

    return c;
}

この方法で求めたアンビエントオクルージョンでカラーリングした場合は次のようになります。

ao_normal.png

動いているものは以下から確認できます。
http://glslsandbox.com/e#44290.0

2つの方法の違い

ステップ数をもとに計算する方法は精度はよくないけど計算は軽く、逆に法線方向の近似点での距離をサンプリングする方法は精度はいいけど計算は重いです。

[余談] 光ってるっぽいエフェクト

ステップ数を利用すると光ってるっぽいエフェクトも作ることができます。

grow.png

vec3 render(in vec3 origin, in vec3 direction) {
    int steps;
    float t = raymarch(origin, direction, steps);

    vec3 c = vec3(0.0);
    if (t < TMAX) {
        float grow =  float(steps) / float(ITERATION);
        c += vec3(1.5, 1.0, 2.0) * grow;
    }   
    return c;
}

 参考