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.
과제를 잘 읽어 봤는데 밑줄친 부분도 있어 추가한다. 역시 과제를 꼼꼼하게 읽어야한다..
장애물 추가와 기본 구현
먼저 장애물(Obstacle)을 추가했다. 장애물은 작은 구 형태로 표현했고, Ray Marching 단계에서 구름 안에 장애물이 있는지를 확인했다.
if (uObstacleOn) {
hit = false;
vec3 tmpRo = rayPos;
for (int i = 0; i < 5; i++) { // 최대 5번만 확인
float dist = sdSphere(tmpRo - uObstaclePos, 0.1);
if (dist < 0.01) { // 장애물과의 충돌
hit = true;
break;
}
tmpRo += rayDir * dist;
}
} else {
hit = false;
}
if (hit)
color = vec3(1.0, 0.0, 0.0); // 빨간색으로 장애물 표시
else
color = pixel.rgb;
장애물과의 충돌을 확인한 뒤, 장애물 여부에 따라 색을 다르게 설정한다.
그림자 계산
장애물에 의해 구름에 그림자가 드리워지도록 구현했다.
그림자는 장애물이 빛의 경로를 차단할 경우 구름의 색에 반영되도록 계산한다.
이 부분은 Ray Marching 내부에서 처리했다.
if (uObstacleOn) {
float isShadow = 0.0;
vec3 shadowPos = p;
vec3 shadowSunDirection = sunDirection;
for (int j = 0; j < 80; j++) { // Shadow ray steps
isShadow = sdSphere(shadowPos - uObstaclePos, 0.1);
if (isShadow < 0.01) {
shadow = mix(1.0, 0.0, exp(-j * 0.1));
break;
}
shadowPos += shadowSunDirection * 0.03;
shadowSunDirection = normalize(SUN_POSITION - shadowPos);
}
}
- Ray Marching을 통해 구름의 밀도가 0보다 큰 영역을 확인한다.
- 해당 위치에서 장애물의 그림자가 드리워지는지 계산하기 위해 빛의 경로를 따라 레이를 전진시킨다.
- 장애물과의 거리와 반복 횟수에 따라 그림자의 강도를 점차 감소시킨다.
그림자 적용
그림자는 최종적으로 다음과 같이 적용된다:
color.rgb *= shadow; // 그림자 적용
res += color * (1.0 - res.a); // 최종 색상 계산
Ray Marching 함수
최종 raymarch
함수는 다음과 같다:
vec4 raymarch(vec3 rayOrigin, vec3 rayDirection) {
float depth = 0.0;
vec3 p = rayOrigin + depth * rayDirection;
vec3 sunDirection = normalize(SUN_POSITION - uCenter);
vec4 res = vec4(0.0);
float shadow = 1.0;
for (int i = 0; i < MAX_STEPS; i++) {
float density = scene(p - uCenter);
if (density > 0.0) {
if (uObstacleOn) {
float isShadow = 0.0;
vec3 shadowPos = p;
vec3 shadowSunDirection = sunDirection;
for (int j = 0; j < 80; j++) { // Shadow ray steps
isShadow = sdSphere(shadowPos - uObstaclePos, 0.1);
if (isShadow < 0.01) {
shadow = mix(1.0, 0.0, exp(-j * 0.1));
break;
}
shadowPos += shadowSunDirection * 0.03;
shadowSunDirection = normalize(SUN_POSITION - shadowPos);
}
}
// 조명과 산란 계산
float diffuse = clamp((scene(p - uCenter) - scene((p - uCenter) + 0.3 * sunDirection)) / 0.3, 0.0, 1.0 );
vec3 lin = vec3(0.60, 0.65, 0.75) * 1.1 + 0.8 * vec3(1.0,0.6,0.3) * diffuse;
// Rayleigh 산란
float phase = 0.75 * (1.0 + pow(dot(normalize(rayDirection), sunDirection), 2.0));
phase *= 0.5;
vec3 scatterColor = lin * phase;
vec4 color = vec4(mix(vec3(1.0,1.0,1.0), scatterColor, density), density);
color.rgb *= lin;
color.rgb *= color.a;
color.rgb *= shadow; // 그림자 적용
res += color * (1.0 - res.a);
}
depth += MARCH_SIZE;
p = rayOrigin + depth * rayDirection;
}
return res;
}
결과
장애물 없음
장애물 추가
그림자 계산할 때 raymarch step마다 광원으로 그림자 계산을 해야해서 매우 연산량이 많아진다.
때문에 on/off를 통해 보여줘야 할 때만 연산을 할 수있게 구현을 했다.
'Computer Graphics > ShaderPixel' 카테고리의 다른 글
ShaderPixel - 9. mandelbulb 추가 (0) | 2024.11.21 |
---|---|
ShaderPixel - 8. mandelbox 추가 (0) | 2024.11.21 |
ShaderPixel - 6. 구름 구현 (Volumetric Cloud) (0) | 2024.11.18 |
ShaderPixel - 5. 색 구슬 구현 (0) | 2024.11.17 |
ShaderPixel - 4. 버텍스 셰이더에서 계산한 노말 값과 월드 좌표는 실제로 프래그먼트에 그려지는 픽셀의 월드 좌표와는 다르다 (2) | 2024.11.15 |