과제의 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: 주변의 지형 구조에 따라 음영을 더한다.
참고 자료
결과

'Computer Graphics > ShaderPixel' 카테고리의 다른 글
ShaderPixel - 11. another world 추가 (0) | 2024.11.22 |
---|---|
ShaderPixel - 10. menger sponge 추가 (0) | 2024.11.22 |
ShaderPixel - 8. mandelbox 추가 (0) | 2024.11.21 |
ShaderPixel - 7. 구름 안 장애물 추가 (0) | 2024.11.18 |
ShaderPixel - 6. 구름 구현 (Volumetric Cloud) (0) | 2024.11.18 |