레이 마칭 파트 1: 레이 트레이싱의 인정받지 못한 사촌

21232 단어 graphicstutorial

광선 추적



우리는 모두 ray tracing 에 대해 알고 있습니다. 여기에서 각 픽셀에 대한 광선을 쏘고 멋진 수학을 사용하여 반복적으로 광선을 반사하고 픽셀을 색칠합니다. 모든 픽셀에 대해 이 프로세스를 반복하면 재능 있는 아티스트가 만든 장면을 실제와 거의 구별할 수 없을 정도로 멋지게 렌더링할 수 있습니다.



불행히도 광선 추적에는 몇 가지 단점이 있습니다. 즉, 엄청나게 느릴 수 있다는 것입니다. 단일 프레임을 렌더링하는 데 몇 시간이 걸릴 수 있으며 제작자가 렌더링 전용으로 매우 강력한 컴퓨터를 가지고 있을 가능성이 높기 때문에 애니메이션 영화에 적합할 수 있지만 일반적으로 거대한 렌더팜을 가지고 있지 않기 때문에 애호가에게는 상당히 골칫거리입니다.

그러나 다른 것이 있습니다.

레이 행진



레이 트레이싱의 진가를 인정받지 못하는 사촌인 레이 마칭에 들어가십시오.
레이 마칭은 레이 트레이싱과 매우 유사하지만 실시간 그래픽 데모 및 게임을 위한 흥미로운 옵션이 되는 몇 가지 흥미로운 단점이 있습니다.
먼저 레이 마칭이 래스터화나 레이 트레이싱을 대체하는 것이 아니라 멋진 대안이라는 점에 주목해야 합니다.
괜찮은! 시작하자.

작동 방식



레이 마칭은 앞서 말했듯이 레이 트레이싱과 매우 유사합니다. 차이점은 광선 행진은 벡터를 따라 광선을 "행진"한 다음 거리 방정식을 사용하여 광선을 행진하는 거리 또는 물체와 교차했는지 여부를 결정한다는 것입니다. 그런 다음 행진 함수에서 교차한 위치, 행진 횟수 또는 행진 거리와 같은 정보를 반환할 수 있습니다.

구현 방법



이제 어떻게 작동하는지 대충 알았으니 실제로 광선 행진기를 만들어 봅시다. 우리는 GLSL을 사용할 것이므로 OpenGL을 설정하거나 Shadertoy 과 같은 것을 사용해야 합니다. 따라하고 싶지 않다면 여기code on Shadertoy가 있습니다.

우선 셰이더 상단에 몇 가지 항목이 필요합니다.

#version 420
// you can change #version if you want to
// the resolution, set to the window's dimensions.
uniform vec2 resolution;
// uncomment if you are going to implement a movement system
//uniform vec3 camPos;

가장 간단한 부분인 거리 추정기부터 시작하겠습니다. 특히 구체의 DE:

float sphereDE(vec3 pos, vec3 spherePos, float size) {
    return length(pos - spherePos) - size;
}

이것은 매우 간단하고 매우 자명하므로 실제로 설명하지는 않겠습니다. 그러나 다른 DE는 이것보다 더 복잡하다는 점을 명심하십시오. 다른 모양의 here's a big list보다 구가 마음에 들지 않는 경우.

이제 실제 행진을 수행하는 행진 함수를 작성할 수 있습니다!

vec3 march(vec3 origin, vec3 direction) {
    float rayDist = 0.0;
    const int NUM_STEPS = 32; // doesn't matter much right now
    const float MIN_DIST = 0.001; // threshold for intersection
    const float MAX_DIST = 1000.0; // oops we went into space

    for(int i = 0; i < NUM_STEPS; i++) {
        vec3 current_pos = origin + rayDist*direction;

        // Use our distance estimator to find the distance
        float _distance = sphereDE(current_pos, vec3(0.0), 1.0);

        if(_distance < MIN_DIST) {
            // We hit an object! This just adds a subtle shading effect.
            return vec3(rayDist/float(NUM_STEPS)*4.0);
        }

        if(rayDist > MAX_DIST) {
            // We have gone too far
            break;
        }

        // Add the marched distance to total
        rayDist += _distance;
    }
    // The ray didn't hit anything so return a color (black, for now)
    return vec3(0.0);
}

이제 우리는 주요 기능을 제외하고 raymarch에 필요한 모든 것을 가지고 있습니다.
작성해 봅시다:

void main( out vec4 fragColor, in vec2 fragCoord ) {
    // normalized coordinates, resolution should be the window/screens pixel dimensions.
    vec2 uv = fragCoord / resolution.xy * 2.0 - 1.0;
    uv = uv / vec2(0.6, 1.0);

    // camPos can be a uniform if you want to move around
    vec3 camPos = vec3(0.0, 0.0, -3.0);
    vec3 rayDir = vec3(uv, 1.0);

    // march!
    vec3 shaded_color = march(camPos, rayDir);

    // set the color!
    fragColor = vec4(shaded_color, 1.0);
}

그리고 끝났습니다! 이것들을 합치면 가장자리로 갈수록 미묘하게 밝아지는 원을 볼 수 있을 것입니다.

개량



우리가 방금 만든 것이 멋지기는 하지만 그다지 흥미롭지는 않습니다. 단색이고 칙칙하며 구체 외에는 별 것이 없습니다.
우리는 광선에 대한 구조체를 냉정하게 만들고 모든 종류의 유용한 값을 반환할 수 있습니다.

struct Ray {float distance; vec3 endPos; float minDist; bool hit;};

여기 내 최종 버전이 있습니다.

const int NUM_STEPS = 32; // doesn't matter much right now
const float MIN_DIST = 0.001; // threshold for intersection
const float MAX_DIST = 1000.0; // oops we went into space

struct Ray {float totalDist; float minDist; vec3 endPos; bool hit;};

// The distance estimator for a sphere
float sphereDE(vec3 pos, vec3 spherePos, float size) {
    return length(pos - spherePos) - size;
}

Ray march(vec3 origin, vec3 direction) {
    float rayDist = 0.0;
    float minDist = MAX_DIST;

    for(int i = 0; i < NUM_STEPS; i++) {
        vec3 current_pos = origin + rayDist * direction;

        // Use our distance estimator to find the distance
        float _distance = sphereDE(current_pos, vec3(0.0), 1.0);

        minDist = _distance < minDist ? _distance : minDist;

        if(_distance < MIN_DIST) {
            // We hit an object!
            return Ray(rayDist, minDist, current_pos, true);
        }

        if(rayDist > MAX_DIST) {
            // We have gone too far
            break;
        }

        // Add the marched distance to total
        rayDist += _distance;
    }
    return Ray(MAX_DIST, minDist, origin + rayDist * direction, false);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord / iResolution.xy * 2.0 - 1.0;

    // scale the ra
    uv = uv / vec2(0.6, 1.0);

    vec3 camPos = vec3(0.0, 0.0, -3.0);
    vec3 rayDir = vec3(uv, 1.0);

    Ray marched = march(camPos, rayDir);

    vec3 color = marched.hit ? 
        vec3(marched.totalDist/pow(float(NUM_STEPS), 0.8)*4.0) : // shading
        vec3(0.0) + vec3(pow(clamp(-1.0 * marched.minDist + 1.0, 0.0, 1.0), 4.0) / 2.0); // glow

    fragColor = vec4(color,1.0);
}

Here's how it looks!
색상, CSG(DE 간의 부울 연산) 또는 더 많은 모양과 같이 만들 수 있는 무한한 다른 추가 기능이 있습니다. 하지만 손가락이 너무 피곤해서 여기서 멈추겠습니다.

광선 행진과 프리티 프랙탈로 만든 그의 게임도 시청하는 것이 좋습니다.

좋은 웹페이지 즐겨찾기