과제의 "A 3D (or 4D) shader of your choice." 항목을 구현하기 위해 물 블록 효과를 추가하였다.
이번 구현은 구름에서 사용되었던 Signed Distance Field(SDF)를 활용하여, 물결치는 블록과 같은 동적 효과를 구현하였다. 특히, 2D 텍스처를 활용한 굴절 효과와 큐브맵을 활용한 반사 효과가 핵심이다.
Vertex Shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat4 uTransform;
out mat4 inverseView;
out mat4 inverseProjection;
out vec2 texCoord;
void main() {
inverseProjection = inverse(uProjection);
inverseView = inverse(uView);
texCoord = aTexCoord;
vec4 clipPos = uTransform * vec4(aPos, 1.0);
gl_Position = clipPos;
}
버텍스 셰이더는 cloud의 버텍스 셰이더를 가져왔다.
Fragment Shader
1. 광선 방향 계산
vec3 calculateRayDirection(vec2 fragCoord) {
vec4 clipSpacePos = vec4((fragCoord / uResolution) * 2.0 - 1.0, -1.0, 1.0);
vec4 viewSpacePos = inverseProjection * clipSpacePos;
viewSpacePos = vec4(viewSpacePos.xy, -1.0, 0.0);
vec3 worldSpaceDir = normalize((inverseView * viewSpacePos).xyz);
return worldSpaceDir;
}
2. 노이즈와 FBM(Fractal Brownian Motion)
mat3 m = mat3( 0.00, 0.80, 0.60,
-0.80, 0.36, -0.48,
-0.60, -0.48, 0.64);
float hash(float n)
{
return fract(sin(n) * 43758.5453);
}
float noise(in vec3 x)
{
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
float n = p.x + p.y * 57.0 + 113.0 * p.z;
float res = mix(mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y),
mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y), f.z);
return res;
}
float fbm(vec3 p) {
float f;
p += vec3(0.0, uTime * 0.3, uTime * 0.4); // 시간에 따라 위치를 변화
f = 0.5000 * noise(p); p = m * p * 2.02;
f += 0.2500 * noise(p); p = m * p * 2.03;
f += 0.1250 * noise(p);
return f;
}
- 물체 표면에 자연스러운 물결 효과를 생성하기 위해 FBM(프랙탈 브라운 운동)을 사용했다. 시간이 흐름에 따라 동적으로 변형된다.
3. SDF(Sign Distance Function): 정육면체
float sdBox(vec3 p, vec3 b) {
p -= uCenter;
vec3 d = abs(p) - b; // 점과 박스 표면 간의 거리
float dist = length(max(d, 0.0)) + min(max(d.x, max(d.y, d.z)), 0.0);
float noise = fbm(p);
return dist + 0.1 * noise;
}
- FBM 노이즈를 추가하여 표면이 꿈틀대는 정육면체의 서명 거리 함수를 정의했다. 점
p
와 박스의 경계면 사이의 거리를 계산한다.
4. Ray Marching
bool raymarch(vec3 rayOri, vec3 rayDir, out vec3 hitPos) {
const float MAX_DISTANCE = 100.0;
const float MIN_HIT_DISTANCE = 0.001;
const int MAX_STEPS = 100;
float totalDistance = 0.0;
for (int i = 0; i < MAX_STEPS; ++i) {
vec3 currentPos = rayOri + totalDistance * rayDir;
float dist = sdBox(currentPos, vec3(1.0));
if (dist < MIN_HIT_DISTANCE) {
hitPos = currentPos;
return true;
}
totalDistance += dist;
if (totalDistance > MAX_DISTANCE) {
break;
}
}
hitPos = vec3(0.0);
return false;
}
- 이제 익숙한 항상 쓰는 그것
5. 법선 계산
vec3 calculateNormal(vec3 p) {
const vec3 epsilon = vec3(0.001, 0.0, 0.0);
float dx = sdBox(p + epsilon.xyy, vec3(1.0)) - sdBox(p - epsilon.xyy, vec3(1.0));
float dy = sdBox(p + epsilon.yxy, vec3(1.0)) - sdBox(p - epsilon.yxy, vec3(1.0));
float dz = sdBox(p + epsilon.yyx, vec3(1.0)) - sdBox(p - epsilon.yyx, vec3(1.0));
return normalize(vec3(dx, dy, dz));
}
- 설명: 충돌 지점에서의 표면 법선을 계산한다. 이 값은 반사와 굴절 효과에 사용된다.
6. 반사와 굴절 효과
void main() {
vec4 pixel = texture(tex, texCoord);
if (pixel.a < 0.01) discard;
vec3 hitPos;
vec3 rayDir = calculateRayDirection(gl_FragCoord.xy);
vec3 rayPos = uViewPos;
if (raymarch(rayPos, rayDir, hitPos)) {
vec3 normal = calculateNormal(hitPos);
vec3 reflectedDir = reflect(rayDir, normal);
vec3 reflectionColor = texture(cubeTex, reflectedDir).rgb;
float refractionIndex = 1.0 / 1.33;
vec3 refractedDir = refract(rayDir, normal, refractionIndex);
vec3 refractedColor = texture(tex, texCoord + refractedDir.xy * 0.1).rgb;
vec3 finalColor = mix(refractedColor, reflectionColor, 0.5);
fragColor = vec4(finalColor, 1.0);
} else {
fragColor = pixel;
}
}
- 반사:
reflect
를 사용해 큐브맵에서 반사 색상을 가져온다. - 굴절:
refract
를 사용해 굴절된 텍스처 색상을 샘플링한다. - 최종 혼합: 반사와 굴절 색상을 혼합하여 자연스러운 물 효과를 구현한다.
결과
이로서 과제에서 요구하는 8개의 모든 오브젝트를 전부 구현하였다.
• A mandelbox, with lights (shadows are optionals).
• A 3D Fractal of your choice (other than the mandelbox), with light, surrounding occlusion and shadows.
• An IFS.
• A transluscent object (using volumetric ray-marching) with non-volumetric diffuse and specular lighting on its surface (like colored glass marble). Think to implement a mode to diplay one the specular/diffuse term of the lighting to verify that your lighting works.
• A volumetric cloud with a volumetric light, plus an object (volumetric too) inside with a higher density. The denser object must cast (volumetric) shadows on the cloud (i.e you must manage the light absorption inside the participating media). Example for the cloud, without the object.
• A shader applied on a surface, evoking "another world" in 3D, quite like a portal or a window. Pay attention to the perspective, the rendering must like a window in real life.
• A 2D shader of your choice that uses a renderbuffer.
• A 3D (or 4D) shader of your choice.
이제 할 일은 코드 리펙토링과 오브젝트 그리는 순서를 조정해주는 것!
드디어 끝이 보인다..
'Computer Graphics > ShaderPixel' 카테고리의 다른 글
ShaderPixel - 15. 마치며 (1) | 2024.11.24 |
---|---|
ShaderPixel - 14. 카메라 기준으로 오브젝트 렌더링 순서 정리하기 (2) | 2024.11.24 |
ShaderPixel - 12. 2d shader (Kaleidoscope) 추가 (0) | 2024.11.22 |
ShaderPixel - 11. another world 추가 (0) | 2024.11.22 |
ShaderPixel - 10. menger sponge 추가 (0) | 2024.11.22 |