과제의 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의 구조와 복잡도를 조정한다. 적절히 값을 변경하며 가장 이쁜 모양을 골랐다.
결과
'Computer Graphics > ShaderPixel' 카테고리의 다른 글
ShaderPixel - 10. menger sponge 추가 (0) | 2024.11.22 |
---|---|
ShaderPixel - 9. mandelbulb 추가 (0) | 2024.11.21 |
ShaderPixel - 7. 구름 안 장애물 추가 (0) | 2024.11.18 |
ShaderPixel - 6. 구름 구현 (Volumetric Cloud) (0) | 2024.11.18 |
ShaderPixel - 5. 색 구슬 구현 (0) | 2024.11.17 |