Computer Graphics/ShaderPixel

ShaderPixel - 9. mandelbulb 추가

surkim 2024. 11. 21. 22:49

과제의 A 3D Fractal of your choice (other than the mandelbox), with light, surrounding occlusion and shadows. 항목에 해당하는 Mandelbulb를 구현하였다.

가장 잘 알려져있는 3D fractal이라 구현하기 쉬울 거 같아서 선택했다.


Vertex Shader

Vertex Shader는 이전의 mandelbox와 동일하다.


Mandelbulb 거리 함수

3D 공간 내에서 특정 점이 Mandelbulb 표면에서 얼마나 떨어져 있는지를 계산한다.

// Mandelbulb distance function
float Mandelbulb(vec3 pos) {
    vec3 z = pos;
    float dr = 1.0;
    float r = 0.0;

    for (int i = 0; i < iter; ++i) {
        r = length(z);
        if (r > bailOut) break;

        float theta = acos(z.z / r);
        float phi = atan(z.y, z.x);
        float zr = pow(r, power - 1.0);
        dr = zr * power * dr + 1.0;
        zr *= r;
        theta *= power;
        phi *= power;

        z = zr * vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
        z += pos;
    }

    return 0.25 * log(r) * r / dr;
}

// Scene distance function
float SceneSDF(vec3 p) {
    vec3 relativePos = p - uCenter;
    return Mandelbulb(relativePos);
}
  • 반복 횟수: Mandelbulb는 iter 변수로 설정된 반복 횟수를 통해 정교한 구조를 만든다.
  • Spherical Coordinates 변환: 구좌표계를 활용하여 3D 공간의 좌표를 변환한다.
  • 탈출 조건: bailOut 값보다 거리가 크면 탈출하며, Mandelbulb의 경계를 벗어났음을 의미한다.

Ray Marching

// Ray marching function
vec4 rayMarch(vec3 rayOrigin, vec3 rayDir) {
    float depth = 0.0;
    for (int i = 0; i < MAX_MARCHING_STEPS; ++i) {
        vec3 p = rayOrigin + depth * rayDir;
        float dist = SceneSDF(p);
        if (dist < EPSILON) return vec4(p, 1.0); // Hit point
        if (depth > MAX_DIST) break;            // 최대 거리 초과
        depth += dist;
    }
    return vec4(0.0); // 표면에 닿지 않음
}
  • 이제 익숙한 항상 쓰는 함수

그림자 및 Ambient Occlusion

과제의 요구에 맞춰 shadow및 ambient occlusion을 구현했다.

// 그림자 계산
bool isInShadow(vec3 point, vec3 lightDir) {
    float distToLight = length(uLightPos - point);
    float shadowDepth = 0.01; // 그림자 시작 깊이 보정

    for (int i = 0; i < MAX_MARCHING_STEPS; ++i) {
        vec3 samplePoint = point + shadowDepth * lightDir;
        float dist = SceneSDF(samplePoint);
        if (dist < EPSILON) return true; // 빛 차단됨
        shadowDepth += dist;
        if (shadowDepth >= distToLight) return false; // 그림자 없음
    }
    return false;
}

// Ambient Occlusion 계산
float calculateAO(vec3 point, vec3 normal) {
    float ao = 0.0;
    float aoScale = 0.1; // AO 반경
    for (int i = 0; i < 8; ++i) {
        vec3 samplePoint = point + normal * aoScale * float(i);
        float dist = SceneSDF(samplePoint);
        ao += clamp(dist - float(i) * aoScale, 0.0, 1.0);
    }
    return clamp(1.0 - ao * 0.1, 0.0, 1.0);
}

 

  • 그림자는 맞은 표면으로 부터 광원까지 레이를 쏴 맞는 부분이 있으면 그림자를 지게했다.
  • ambient occlusion은 주면에 표면이 있으면 그만큼 차폐시켜주었다.

Phong 조명 모델

Phong 조명 모델을 사용하여 Mandelbulb에 조명을 적용하였다.

vec3 phongShading(vec3 p, vec3 normal, vec3 lightPos, vec3 viewPos) {
    vec3 ambient = 0.2 * vec3(1.0, 1.0, 1.0);
    vec3 lightDir = normalize(lightPos - p);
    vec3 viewDir = normalize(viewPos - p);
    vec3 reflectDir = reflect(-lightDir, normal);

    // Diffuse
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * vec3(1.0, 0.8, 0.6);

    // Specular
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 16.0);
    vec3 specular = spec * vec3(1.0);

    return ambient + diffuse + specular;
}

Fragment Shader 메인 함수

최종적으로 Ray Marching, 그림자, Ambient Occlusion을 적용하여 Mandelbulb를 렌더링하였다.

void main() {

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

    vec3 rayDir = calculateRayDirection(gl_FragCoord.xy);
    vec4 hit = rayMarch(uViewPos, rayDir);
    if (hit.w == 0.0) {
        discard;
    }

    vec3 hitPos = hit.xyz;
    vec3 normal = calculateNormal(hitPos);
    vec3 lightDir = normalize(uLightPos - hitPos);
    
    // Shadows
    bool inShadow = isInShadow(hitPos + normal * EPSILON, lightDir);
    
    // Ambient Occlusion
    float ao = calculateAO(hitPos, normal);

    // Phong Shading
    vec3 color = phongShading(hitPos, normal, uLightPos, uViewPos);

    // Apply shadows and AO
    if (inShadow) {
        color *= 0.5; // Shadow intensity
    }
    color *= ao; // AO intensity

    fragColor = vec4(color, 1.0);
}
  • Shadow: 빛의 경로를 따라 Mandelbulb가 빛을 차단하는지 계산하여 적용한다.
  • Ambient Occlusion: 주변의 지형 구조에 따라 음영을 더한다.

참고 자료

 


결과

이건 프레임 방어가 힘들다. 그냥 이거 그릴때 연산량 자체가 많다