Computer Graphics/ShaderPixel

ShaderPixel - 8. mandelbox 추가

surkim 2024. 11. 21. 17:43

과제의 A mandelbox, with lights (shadows are optionals). 항목에 해당하는 Mandelbox를 구현하였다.


Vertex Shader

Vertex Shader는 이전 작업들과 마찬가지로 뷰 공간 변환법선 변환에 초점을 맞추었다.

Mandelbox의 렌더링에 필요한 역행렬 값과 법선을 프래그먼트 셰이더로 전달하도록 작성하였다.

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

uniform mat4 uView;
uniform mat4 uProjection;
uniform mat4 uModel;
uniform mat4 uTransform;

out mat4 inverseView;
out mat4 inverseProjection;
out vec3 vNormal;
out vec3 vPosition;

void main() {
    inverseProjection = inverse(uProjection);
    inverseView = inverse(uView);
    vNormal = (transpose(inverse(uModel)) * vec4(aNormal, 0.0)).xyz;
    vPosition = (uModel * vec4(aPos, 1.0)).xyz;
    gl_Position = uTransform * vec4(aPos, 1.0);
}

Mandelbox 거리 함수

Mandelbox의 핵심인 거리 함수 이다.

Mandelbox는 Box Folding과 Sphere Folding을 반복하여 독특한 모양을 생성한다.

// Box Folding
void boxFold(inout vec3 z) {
    z = clamp(z, -1.0, 1.0) * 2.0 - z;
}

// Sphere Folding
void sphereFold(inout vec3 z, inout float dz) {
    float r2 = dot(z, z);
    if (r2 < min_radius2) {
        float temp = fixed_radius2 / min_radius2;
        z *= temp;
        dz *= temp;
    } else if (r2 < fixed_radius2) {
        float temp = fixed_radius2 / r2;
        z *= temp;
        dz *= temp;
    }
}

// Mandelbox 거리 함수
float mandelboxDistance(vec3 pos) {
    vec3 z = pos - uCenter;
    float dr = 1.0;
    for (int n = 0; n < ITERATIONS; ++n) {
        boxFold(z);
        sphereFold(z, dr);
        z = scale * z + (pos - uCenter);
        dr = abs(scale) * dr + 1.0;
    }
    return length(z) / abs(dr);
}
  • Box Folding: Mandelbox의 중심에서 좌표를 대칭적으로 변환하여 복잡한 구조를 만든다.
  • Sphere Folding: 거리 값을 변형하여 Mandelbox의 세부 구조를 만든다.

참고 사이트:


Ray Marching

Mandelbox를 렌더링하기 위한 Ray Marching 방식이다. 앞에서 주구장창 썼으니 설명은 생략

float rayMarch(vec3 ro, vec3 rd) {
    float t = 0.01;
    for (int i = 0; i < MAX_MARCHING_STEPS; ++i) {
        vec3 p = ro + t * rd;
        float dist = mandelboxDistance(p);
        if (dist < EPSILON) {
            return t; // 표면에 도달
        }
        if (t > MAX_DIST) {
            break; // 거리 초과
        }
        t += dist;
    }
    return -1.0; // Hit하지 않음
}

Lighting 계산

Mandelbox에 빛을 적용하기 위해 법선 계산Diffuse Lighting + ambient을 구현하였다.

// 법선 계산
vec3 calculateNormal(vec3 p) {
    float epsilon = 0.01;
    return normalize(vec3(
        mandelboxDistance(p + vec3(epsilon, 0.0, 0.0)) - mandelboxDistance(p - vec3(epsilon, 0.0, 0.0)),
        mandelboxDistance(p + vec3(0.0, epsilon, 0.0)) - mandelboxDistance(p - vec3(0.0, epsilon, 0.0)),
        mandelboxDistance(p + vec3(0.0, 0.0, epsilon)) - mandelboxDistance(p - vec3(0.0, 0.0, epsilon))
    ));
}

// Diffuse Lighting 계산
vec3 calculateDiffuseLighting(vec3 p, vec3 n, vec3 lightPos) {
    vec3 lightDir = normalize(lightPos - p);
    float diffuse = max(dot(n, lightDir), 0.0);
    diffuse = clamp(diffuse, 0.1, 1.0);
    return diffuse * vec3(1.0, 0.8, 0.6);
}

Fragment Shader 메인 함수

메인 함수에서 Ray Marching을 수행하고, 법선과 조명을 계산한 뒤 최종 색상을 결정하였다.

최적화 때문에 메쉬가 박스이므로 뒤에 닿는 면을 쏘는 레이는 걸렀다.

void main() {

	// 카메라가 구 바깥에 있을때는 레이를 두번 쏘기 때문에 걸러준다    
    vec3 viewToSurface = normalize(vPosition - uViewPos);
    float alignment = dot(viewToSurface, vNormal);    
    bool outside = (sdBox(uViewPos - uCenter, vec3(4.0, 4.0, 4.0)) > 0.00);
    if (outside) {
        if (alignment > 0.01) {
            discard;
        }
    }
    ////
    
    vec3 rayDir = calculateRayDirection(gl_FragCoord.xy);
    vec3 rayPos = uViewPos;

    float t = rayMarch(rayPos, rayDir);
    if (t > 0.0) {
        vec3 p = rayPos + t * rayDir;
        vec3 n = calculateNormal(p);
        vec3 color = calculateDiffuseLighting(p, n, uLightPos);

        fragColor = vec4(color, 1.0);
    } else {
        fragColor = vec4(0.0, 0.0, 0.0, 1.0); // 배경색
    }
}

Mandelbox 파라미터

Mandelbox의 최종 모양은 다음과 같은 파라미터를 사용하여 설정하였다:

// Mandelbox 파라미터
float fixed_radius2 = 1.5;
float min_radius2 = 0.5;
float folding_limit = 1.0;
float scale = -2.0;

MandelBox Wiki

이 파라미터들은 Mandelbox의 구조와 복잡도를 조정한다. 적절히 값을 변경하며 가장 이쁜 모양을 골랐다.


결과

아직까지 프레임방어는 잘 된다..