5.3 정방위 렌더링

정방위 렌더링은 360도 파노라마 이미지를 표시하는 데 널리 사용되는 기법입니다. 이 방법은 인테리어 디자인 시각화 및 게임에서 먼 배경이나 하늘을 렌더링하는 데 특히 인기가 있습니다. 360도 카메라의 등장으로 이 기술은 더욱 관련성이 높아졌습니다.

플레이그라운드 실행 - 5_03_equalrectangle_rendering

정방위 텍스처는 구형 표면을 평평하게 표현한 것으로, 지구본이 2D 지도로 투영되는 방식과 유사합니다. 이 투영에 익숙하다면 경도와 위도가 지도의 특정 지점을 정의하는 직사각형 형식으로 지구본을 매핑한다는 것을 알 수 있습니다. 이것이 구면 좌표를 통해 정방위 텍스처 맵에 접근하는 기본 원리입니다.

Equirectangular Projection of the Globe
지구본의 정방위 투영[SOURCE]

정방위 렌더링의 개념은 간단합니다. 일반적으로 텍스처 매핑은 UV 좌표를 사용하여 수행됩니다. 그러나 정방위 렌더링에서는 카메라를 둘러싼 가상의 구를 상상합니다. 렌더링되는 프레임버퍼는 카메라 앞에 있는 평면 역할을 합니다. 이 이미지 평면의 각 프래그먼트에 대해 카메라 위치에서 프래그먼트를 통해 가상의 구를 교차하는 광선이 투사됩니다. 이 교차점은 구면 좌표, 특히 세타와 파이로 정의됩니다.

Equirectangular Texture Access
정방위 텍스처 접근

이러한 구면 좌표를 사용하여 정방위 텍스처 맵을 샘플링하기 위한 UV 텍스처 좌표를 도출할 수 있습니다. 이 설정은 카메라가 자유롭게 회전하여 파노라마 이미지의 전체 360도 뷰를 제공할 수 있도록 합니다.

@group(0) @binding(0)
var modelView: mat4x4;
@group(0) @binding(1)
var projection: mat4x4;

struct VertexOutput {
    @builtin(position) clip_position: vec4,
    @location(0) worldPos: vec3
};

@vertex
fn vs_main(
    @location(0) inPos: vec3
) -> VertexOutput {
    var out: VertexOutput;
    out.worldPos = inPos;
    var wldLoc:vec4 = modelView * vec4(inPos, 1.0);
    out.clip_position = projection * wldLoc;
    return out;
}

// Fragment shader

const pi:f32 = 3.141592654;
@group(0) @binding(2)
var t_diffuse: texture_2d;
@group(0) @binding(3)
var s_diffuse: sampler;

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4 {
    var n:vec3 = normalize(in.worldPos);

    var len:f32 = sqrt (n.x *n.x + n.y*n.y);

    var s:f32 = acos( n.x / len);
    if (n.y < 0) {
        s = 2.0 * pi - s;
    }

    s = s / (2.0 * pi);
    var tex_coord:vec2 = vec2(s , ((asin(n.z) * -2.0 / pi ) + 1.0) * 0.5);
    return textureSampleLevel(t_diffuse, s_diffuse, tex_coord, 0);
}

이제 정방위 렌더링에 사용되는 셰이더 코드를 살펴보겠습니다. 정점 셰이더는 표준 모델-뷰 및 투영 변환을 수행하며, 월드 좌표를 프래그먼트 셰이더로 전달합니다. 이 월드 좌표는 정방위 텍스처 맵을 샘플링하는 데 사용되는 구면 좌표를 도출하는 데 필수적입니다.

프래그먼트 셰이더에서는 먼저 월드 좌표를 가져옵니다. 그런 다음 경도와 위도를 계산합니다. 경도는 다음에서 파생됩니다:

var n:vec3 = normalize(in.worldPos);

var len:f32 = sqrt (n.x *n.x + n.y*n.y);

var s:f32 = acos( n.x / len);

그리고 위도는 다음에서:

if (n.y < 0) {
    s = 2.0 * pi - s;
}

s = s / (2.0 * pi);

위도는 -π에서 π까지이므로, 이를 0에서 1까지의 범위로 스케일링합니다. 또한, 텍스처 좌표계와 구면 좌표계는 V 또는 위도를 따라 반대 방향을 가지므로 위도 또는 V 좌표를 뒤집습니다.

텍스처 샘플링은 이러한 계산 후에 평소와 같이 수행됩니다.

Example Image of Equirectangular Image
정방위 이미지 예시

정방위 렌더링은 금속, 거울, 유리와 같은 반사 표면을 렌더링하는 데에도 매우 효과적입니다. 이러한 표면은 주변 환경을 반사하며, 정방위 텍스처를 사용하여 이러한 반사를 캡처할 수 있습니다. 다음은 이를 구현하는 방법입니다:

let nn:vec3 = reflect(-viewDir, n);

var len:f32 = sqrt (nn.x *nn.x + nn.y*nn.y);
var s:f32 = acos( nn.x / len);
if (nn.y < 0) {
    s = 2.0 * pi - s;
}
s = s / (2.0 * pi);
var tex_coord:vec2 = vec2(s , ((asin(nn.z) * -2.0 / pi ) + 1.0) * 0.5);
var diffuseColor:vec4 =
        textureSampleLevel(t_diffuse, s_diffuse, tex_coord, 0);

이 코드 스니펫에서는 먼저 표면 노멀과 시야 방향을 기반으로 반사 방향을 계산합니다. 경도(s)와 위도는 이 반사 벡터에서 파생됩니다. 위도는 텍스처 좌표계에 맞게 스케일링됩니다. 그런 다음 이 좌표를 사용하여 정방위 텍스처에서 확산 색상을 샘플링합니다.

고정된 색상이 사용되었던 이전 섀도우 맵 데모와 달리, 여기서는 정방위 텍스처로 표현된 주변 환경에서 확산 색상을 샘플링합니다. 이 과정은 섀도우 맵 예제와 유사하므로 자세한 설명은 생략하겠습니다.

위 예시의 출력은 다음과 같습니다:

The Result of the Rendering
렌더링 결과

GitHub에 의견 남기기