본문 바로가기
Shader CG

08. RayMarching - Shadow

by SimonLee 2024. 8. 10.

그림자 이펙트 구현.

 

 

2 pass 알고리즘이라고도 불리는 그림자 이펙트는

구현하기 위해서는 픽셀마다 ray marching 2번 수행해야 한다.

 

카메라 원점에서 Ray Marching을 해서 물체와 충돌되는 지점 (p1) 를 찾았다면, --> 1

다시 p1 지점에서 p1 --> light position 방향으로 Ray Marching을 다시 진행한다. --> 2

이때 p1 지점과 light position 간 거리도 구해 놓는다. --> 3

 

2 번 ray marching 에서 얻은 뎁스 값과 3번에서 구한 거리 값을 비교해 본다.

뎁스 값이 더 작다면, 물체와 태양 사이 다른 오브젝트가 있는 것으로 이해할 수 있으며 

그림자가 생성 된다.

뎁스 값이 같으면 물체와 태양 사이 아무것도 없는 것이며, 

그림자가 생성되지 않는다.

 

위의 있는 예제는 floor 영역에 픽셀이 원의 대한 그림자 영역이 표시가 된다.

 

p1 지점에 " normal * EPSILON * 3 "을 더한 이유는 무엇일까 ?

ray marching 시작 지점이 카메라가 아니라 p1이며, 

p1도 물체이기 때문에  아주 작은 값이라도 더하지 않으면 rayMarching 함수에서 

리턴하기 때문.

 

그림자 1

 

그림자 코드 >>

// shadow
vec3 newRayOrigin = p + normal * EPSILON * 3.;
vec3 newRayDi = normalize(lightPos - newRayOrigin);

float shadowRayLength = rayMarch(newRayOrigin, newRayDi);
if (shadowRayLength < length(lightPos - newRayOrigin)) {
  col = vec3(0.);
}

 

 

전체 코드 >>

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;
const float EPSILON = 0.0005;

float sdSphere(vec3 p, float r) {
  return length(p) -r;
}

float sdFloor(vec3 p) {
  return p.y + 1.;
}

float scene(vec3 p) {
  float sphere = sdSphere(p, 1.);
  float floors = sdFloor(p);
  return min(sphere, floors);
}

float rayMarch(vec3 ro, vec3 rd) {
  float d = MIN_DIST;
  for (int i=0; i<MAX_MARCHING_STEPS; i++) { 
    vec3 p = ro + rd * d;
    float depth = scene(p);
    d += depth;
    if (d < PRECISION || d > MAX_DIST) break;
  }
  return d;
}

vec3 calcNormal(vec3 p) {
  vec2 e = vec2(1, -1) * EPSILON;
  return normalize(
      e.xyy * scene(p + e.xyy) +
      e.yyx * scene(p + e.yyx) +
      e.yxy * scene(p + e.yxy) +
      e.xxx * scene(p + e.xxx));
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-0.5*iResolution.xy) / iResolution.y;
  vec3 background = vec3(0.);
  vec3 col = background;
  vec3 ro = vec3(0, 0, 5);
  vec3 rd = normalize(vec3(uv, -1));
  
  
  float d = rayMarch(ro, rd);
  if (d < MAX_DIST) {
    vec3 p = ro + rd * d;
    vec3 lightPos = vec3(cos(iTime), 2, sin(iTime));
    vec3 lightDi = normalize(lightPos - p);
    
    vec3 diffuseCol = vec3(1.);
    vec3 normal = calcNormal(p);
    float diffuse = clamp(dot(normal, lightDi), 0., 1.);
    col = diffuseCol * diffuse;
   
    // shadow
    vec3 newRayOrigin = p + normal * EPSILON * 3.;
    vec3 newRayDi = normalize(lightPos - newRayOrigin);
   
    float shadowRayLength = rayMarch(newRayOrigin, newRayDi);
    if (shadowRayLength < length(lightPos - newRayOrigin)) {
      col = vec3(0.);
    }
  }
  
  fragColor = vec4(col,1.0);
}

 

< 그림자 1 > 을 보면 그림자의 경계선이 명확하여 어색해 보인다.

아래는 소프트 셰도우 적용 코드이다.

float softShadow = clamp(softShadow(p, ld, 0.02, 2.5), 0.1, 1.0); 
col = s.col * diffuse * softShadow;

 

소프트 셰도우도 rayMarching 기법을 동일하게 사용하지만,

그림자 영역 계산하는 부분이

길이비교 if 문으로 나누어 지는 방식이 아니고, t 값으로 나눈 값을 사용 하는 것이 차이점.

 

float softShadow(vec3 ro, vec3 rd, float mint, float tmax) {
  float res = 1.0;
  float t = mint;

  for(int i = 0; i < 16; i++) {
    float h = scene(ro + rd * t).d;
      res = min(res, 8.0*h/t);
      t += clamp(h, 0.02, 0.10);
      if(h < 0.001 || t > tmax) break;
  }
  return clamp( res, 0.0, 1.0 );
}

 

추가로 바닥의 경계면도 부드럽게 보이기 위하여 포그 이펙트를 적용해보자.

 col = mix(col, background, 1.0 - exp(-0.0002 * s.d * s.d * s.d));

 

포그 + 소프트 셰도우 적용

 

전체 코드 >

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float PRECISION = 0.001;
const float EPSILON = 0.0005;

struct Surface {
  float d;
  vec3 col;
};

Surface sdSphere(vec3 p, float r) {
  Surface s;
  s.d = length(p) - r;
  s.col = vec3(1, 0, 0);
  return s;
}

Surface sdFloor(vec3 p) {
  Surface s;
  s.d = p.y + 1.;
  s.col = vec3(mod(floor(p.x) + floor(p.z), 2.) * 0.7);
  return s;
}

Surface scene(vec3 p) {
  Surface a = sdSphere(p, 1.);
  Surface b = sdFloor(p);
  if (a.d < b.d) return a;
  return b;
}

Surface rayMarch(vec3 ro, vec3 rd) {
  float d = MIN_DIST;
  Surface s;
  
  for (int i=0; i<MAX_MARCHING_STEPS; i++) {
    vec3 p = ro + rd * d;
    s = scene(p);
    d += s.d;
    if (d < PRECISION || d > MAX_DIST) break;
  }
  s.d = d;
  return s;
}

vec3 calcNormal(vec3 p) {
  vec2 e = vec2(1, -1) * EPSILON;
  
  return normalize(
    e.xyy * scene(p + e.xyy).d +
    e.yxy * scene(p + e.yxy).d +
    e.yyx * scene(p + e.yyx).d +
    e.xxx * scene(p + e.xxx).d);
}

float softShadow(vec3 ro, vec3 rd, float mint, float tmax) {
  float res = 1.0;
  float t = mint;

  for(int i = 0; i < 16; i++) {
    float h = scene(ro + rd * t).d;
      res = min(res, 8.0*h/t);
      t += clamp(h, 0.02, 0.10);
      if(h < 0.001 || t > tmax) break;
  }

  return clamp( res, 0.0, 1.0 );
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-0.5*iResolution.xy) / iResolution.y;
  vec3 background = vec3(0.3);
  vec3 col = background;
  vec3 ro = vec3(0, 0, 5);
  vec3 rd = normalize(vec3(uv, -1));
  
  vec3 lightPos = vec3(cos(iTime), 2, sin(iTime));
  
  Surface s = rayMarch(ro, rd);
  if (s.d < MAX_DIST) {
    vec3 p = ro + rd * s.d;
    vec3 ld = normalize(lightPos - p);
    vec3 normal = calcNormal(p);
    float diffuse = clamp(dot(ld, normal), 0., 1.);
    float softShadow = clamp(softShadow(p, ld, 0.02, 2.5), 0.1, 1.0); 
    col = s.col * diffuse * softShadow;
    
    /*
    // Hard Shadow
    vec3 newRayOrigin = p + normal * PRECISION * 2.;  
    float sdepth = rayMarch(newRayOrigin, ld).d;
    if (sdepth < length(lightPos - newRayOrigin)) {
      col *= 0.1
    }
    */
  }
  col = mix(col, background, 1.0 - exp(-0.0002 * s.d * s.d * s.d)); // fog
  col = pow(col, vec3(1.0/2.2)); // Gamma correction
  fragColor = vec4(col,1.0);
}

 

Reference


https://inspirnathan.com/posts/59-shadertoy-tutorial-part-13

 

Shadertoy Tutorial Part 13 - Shadows

Add shadows, gamma correction, and fog to your 3D scene!

inspirnathan.com

'Shader CG' 카테고리의 다른 글

10. RayMarching - Cube map  (0) 2024.08.13
09. RayMarching - SD Operation  (0) 2024.08.12
07. RayMarching - Frenel effect  (0) 2024.08.10
06. RayMarching - 퐁 라이팅 모델  (0) 2024.08.08
05. RayMarching - Camera move  (0) 2024.08.05