레이마칭으로 굴절 표현

18229 단어 레이마칭GLSLWebGL
비어 있었으므로, WebGL Advent Calendar 2018 5일째의 기사로 했습니다(WebGL이라고 할까 GLSL입니다만...).

12월 1,2일에 행해진 도쿄도 페스트 2018의 GLSL Compo에 「Refaction」이라고 하는 작품을 제출했습니다.
실제로 움직이는 것은 아래에서 볼 수 있습니다 (조금 무겁기 때문에주의하십시오).
h tp // glsl 씨 d보 x. 이 m/E #50703.0



이 작품에서는 중간의 유리 같은 직육면체를 레이마칭으로 작성하고 있습니다.
여기에서 사용하는 굴절 표현에 대해 설명합니다.

먼저 카메라에서 일반 레이마칭과 같이 물체의 표면에 도달할 때까지 레이를 날립니다. 표면에 도달하면 GLSL의 내장 함수 refectrefract을 사용하여 반사 방향과 굴절 방향을 찾습니다.



반사 방향은 스페큘러 성분을 구하는데 사용하고, 굴절 방향은 배경을 샘플링하는데 사용합니다.

GLSL로 구현하면 다음과 같습니다.

float schlickFresnel(float ri, float cosine) {
    float r0 = (1.0 - ri) / (1.0 + ri);
    r0 = r0 * r0;
    return r0 + (1.0 - r0) * pow(1.0 - cosine, 5.0);
}

float refractionIndex = 1.5;
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
vec3 lightColor = vec3(2.0);
vec3 substanceColor = vec3(0.90, 0.95, 1.0);

vec3 raymarch(vec3 ro, vec3 rd) {
    vec3 p = ro;
    for (int i = 0; i < 32; i++) {
        float d  = scene(p);
        p += rd * d;
        if (d < 0.01) {
            vec3 n = normal(p);
            vec3 reflected = reflect(rd, n);
            float f = schlickFresnel(refractionIndex, max(0.0, dot(-rd, n)));
            vec3 spec = f * lightColor * pow(max(0.0, dot(reflected, lightDir)), 4.0);
            vec3 refracted = refract(rd, n, 1.0 / refractionIndex);
            return spec + substanceColor * background(p, refracted);
        }
    }
    return background(ro, rd);
}

스페큘러 성분은 Phong 쉐이딩으로 계산합니다. Schlick의 근사식에서 구한 프레넬 계수를 곱하면 얕은 각도에서 볼 때 빛이 더 반사됩니다.

객체와 직육면체를 그리면 각각 다음과 같이 됩니다.





여기까지도 나름대로 분위기가 나옵니다만, 레이의 사출시의 재굴절을 한층 더 고려해 보고 싶습니다.

재굴절을 고려할 때, 굴절된 광선이 뒷면의 어디에서 사출되는지 알아야 합니다. 이것은 굴절된 광선 앞에 있는 충분히 멀리 떨어진 지점에서 광선의 반대 방향으로 광선을 날려서 실현됩니다.



재굴절할 위치를 알았으므로 여기에서 다시 refract 함수를 사용하여 사출 방향을 계산합니다. 사출 방향이 요구되면 거기에서 배경을 샘플링 할 수 있습니다. 주의해야 할 점은 입사각이 특정 값을 초과하면 전반사가 일어나 굴절하지 않을 수 있다는 것입니다. 이 경우 refract 함수는 vec3(0.0)를 반환합니다. 여기에서 레이가 객체에서 사출할 때까지 같은 처리를 반복해도 좋지만, 성능적인 것도 생각하고 이번에는 반사 방향으로 배경을 샘플하기로 합니다.



이것을 GLSL로 구현하면 다음과 같이 되어, 2회 레이마칭을 실시하게 됩니다.
vec3 raymarch(vec3 ro, vec3 rd) {
    vec3 p = ro;
    for (int i = 0; i < 32; i++) {
        float d  = scene(p);
        p += rd * d;
        if (d < 0.01) {
            vec3 n = normal(p);
            vec3 reflected = reflect(rd, n);
            float f = schlickFresnel(refractionIndex, max(0.0, dot(-rd, n)));
            vec3 spec = f * lightColor * pow(max(0.0, dot(reflected, lightDir)), 4.0);
            vec3 refracted = refract(rd, n, 1.0 / refractionIndex);         
            vec3 op = p + refracted * 100.0;
            for (int j = 0; j < 32; j++) {
                float d = scene(op);
                op += -refracted * d;
                if (d < 0.01) {
                    vec3 n2 = normal(op);
                    vec3 refracted2 = refract(refracted, -n2, refractionIndex);
                    if (length(refracted2) > 0.01) {
                        return spec + substanceColor * background(op, refracted2);
                    } else {
                        vec3 reflected2 = reflect(refracted, -n2);
                        return spec + substanceColor * background(op, reflected2);
                    }
                }
            }
        }
    }
    return background(ro, rd);
}

이렇게 했을 때의 구와 직육면체는 다음과 같이 됩니다. 입사 위치에서의 굴절 방향으로 배경을 샘플링했을 때와 비교하면, 특히 직육면체에서의 후면의 모퉁이의 부분이 리얼하게 되어 있는 느낌이 듭니다.





덧붙여 이 방법은 볼록면체라면 (아마) 능숙합니다만, 오목면체에서는 능숙하지 않습니다.
예를 들어 토러스에서 같은 알고리즘을 적용하면 다음과 같이 되어 정확하게 재현할 수 없습니다.





이번 샘플은 아래에서 확인할 수 있습니다. scene 함수에서 그리는 객체의 모양을 변경할 수 있습니다.
또한 #define MULTI_REFRACTION 를 주석 처리하여 입사 위치에서 굴절 방향으로 배경을 샘플링합니다.
h tp // glsl 씨 d보 x. 이 m/E #50748.0

좋은 웹페이지 즐겨찾기