본문 바로가기
Shader CG

11. RayMarching - Snow man

by SimonLee 2024. 8. 14.

지금까지 공부했던 내용 바탕으로 눈사람 만들어보자.

 

머리와 몸통은 구 렌더링이다.

머리와 몸통을 경계를 스무스하게 만드는 opSmoothUnion() 을 사용한다.

눈과 입 전부 구 렌더링이라 특별한 것은 없다.

 

코의 경우 콘 형태이며, z 축방향으로 삐죽하게 만들기 위해 y,z 축을 교환했다.

콘을 만드는 코드는 아래 사이트를 참고

https://iquilezles.org/articles/distfunctions/

 

Inigo Quilez

Articles on computer graphics, math and art

iquilezles.org

 

눈사람 내부 컴포넌트를 전부 union 후, 컬러를 적용한다.

옆에서 바라보는 효과를 위해 아래 코드를 사용했다.

p.x -= 0.75; // move entire scene slightly to the left
p.xz *= rotate2d(0.5); // start scene at an angle

 

눈사람 위로 둠칫 두둠칫 움직임을 표현하기 위해 y 값을 변화 시킨다.

p.y *= mix(1., 1.03, sin(iTime * SPEED)); // bounce snowman up and down a bit

 

 

특별한 것이 바닥 렌더링인데 가까이서 보면 눈이 온것처럼 울퉁 불퉁하다.

이를 구현하기 위해서는 랜덤 텍스처가 필요하다.

아래 텍스처 중에 " Gray Noise Small "텍스처를 채널 0번 버퍼로 입력받는다.

노이저 텍스처 값을 아주 작은 범위로 샘플링 하여 칼러 값을 사용한다.

어둡고 밝은 픽셀값으로 인하여 울퉁불퉁한 표면이 표현된다.

노말맵 형태 비슷하다.

 

Image 전체 코드 >>

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;
const float PI = 3.14159265359;

// Color
const vec3 COLOR_BACKGROUND = vec3(.741, .675, .82); // night
//const vec3 COLOR_AMBIENT = vec3(0.42, 0.20, 0.1); // day
const vec3 COLOR_AMBIENT = vec3(0.0, 0.20, 0.8) * 0.3;
const vec3 COLOR_BODY = vec3(1.15);
const vec3 COLOR_EYE = vec3(0);
const vec3 COLOR_NOSE = vec3(0.8, 0.3, 0.1);
const vec3 COLOR_ARM = vec3(0.2);
const vec3 COLOR_HAT = vec3(0.4);

struct Surface {
  float sd; // signed distance
  vec3 col; // diffuse color
};

mat3 rotateZ(float theta) {
  float c = cos(theta);
  float s = sin(theta);
  return mat3(
    vec3(c, -s, 0),
    vec3(s, c, 0),
    vec3(0, 0, 1)
  );
}

mat3 wiggle() {
  float SPEED = 0.5;
  return rotateZ(mix(-0.01, 0.01, cos(iTime * SPEED)));
}


Surface opRep(vec3 p, vec3 c);
mat2 rotate2d(float theta);

Surface opUnion(Surface a, Surface b) {
  if (a.sd < b.sd) return a;
  return b;
}

Surface opSmoothUnion(Surface a, Surface b, float k) {
  Surface s;
  float h = clamp( 0.5 + 0.5*(b.sd-a.sd)/k, 0.0, 1.0 );
  s.sd = mix( b.sd, a.sd, h ) - k*h*(1.0-h);
  s.col = mix( b.col, a.col, h ) - k*h*(1.0-h);
  return s;
}

Surface sdSphere(vec3 p, float r, vec3 col, vec3 offset) {
  p -= offset;
  return Surface(length(p) - r, col);
}

Surface sdCone( vec3 p, vec2 c, float h , vec3 col, vec3 offset)
{
  Surface s;
  //float tmp = p.y; p.y = p.z; p.z = p.y;
  p -= offset;
  float q = length(p.xy);
  s.sd = max(dot(c.xy,vec2(q,p.z)),-h-p.z);
  s.col = col;
  return s;
}

Surface sdCapsule( vec3 p, vec3 a, vec3 b, float r, vec3 col, vec3 offset )
{
  Surface s;
  p -= offset;
  vec3 pa = p - a, ba = b - a;
  float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
  s.sd = length( pa - ba*h ) - r;
  s.col = col;
  return s;
}

vec3 opFlipX(vec3 p)
{
  return vec3(-p.x, p.y, p.z);
}

Surface sdSnowman(vec3 p) {
  Surface ret;
  Surface head = sdSphere(p, 0.5, COLOR_BODY, vec3(0,1,0));
  Surface body = sdSphere(p, 0.8, COLOR_BODY, vec3(0,-0.2,0));
  Surface lefteye = sdSphere(p, 0.1, COLOR_EYE, vec3(-0.15,1.,0.4));
  Surface righteye = sdSphere(opFlipX(p), 0.1, COLOR_EYE, vec3(-0.15,1.,.4)); 
  float noseAngle = radians(75.);
  Surface nose = sdCone(
    p, vec2(sin(noseAngle), cos(noseAngle)), 0.5, 
    COLOR_NOSE, vec3(0, 0.85, .8));
  Surface mouse = sdSphere(p, 0.05, COLOR_EYE, vec3(0,0.65,.5));
 
 Surface leftHand = sdCapsule(
    p, vec3(0, 0, 0), vec3(-1.2,.5, 0), 0.1, COLOR_ARM, vec3(0, 0, 0));
  Surface rightHand = sdCapsule(
    opFlipX(p), vec3(0, 0, 0), vec3(-1.2,.5, 0), 0.1, COLOR_ARM, vec3(0, 0, 0));
  
  ret = opSmoothUnion(head, body, 0.25);
  ret = opUnion(ret, lefteye);
  ret = opUnion(ret, righteye);
  ret = opUnion(ret, nose);
  ret = opUnion(ret, mouse);
  ret = opUnion(ret, leftHand);
  ret = opUnion(ret, rightHand);
  return ret;
}

Surface sdFloor(vec3 p) {
  Surface s;
  float snowFloor = p.y + 1. + texture(iChannel0, p.xz).x * 0.03;
  vec3 snowFloorCol = 0.85 * mix(vec3(1.5), vec3(1), texture(iChannel0, p.xz/100.).x);
  s.sd = snowFloor;
  s.col = snowFloorCol;
  return s;
}

Surface scene(vec3 p) {
  float SPEED = 0.5;
  p.x -= 0.75; // move entire scene slightly to the left
  p.xz *= rotate2d(0.5); // start scene at an angle
  p.y *= mix(1., 1.03, sin(iTime * SPEED)); // bounce snowman up and down a bit

  Surface a = opRep(p - vec3(0, 0, -2), vec3(5, 0, 5));
  
  
  Surface b = sdFloor(p);
  Surface ret = opUnion(a, b);
  return ret;
}

Surface opRep(vec3 p, vec3 c)
{
  vec3 q = mod(p+0.5*c,c)-0.5*c;
  return sdSnowman(q * wiggle());
}

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

mat2 rotate2d(float theta) {
  float s = sin(theta), c = cos(theta);
  return mat2(c, -s, s, c);
}

mat3 camera(vec3 cameraPos, vec3 lookat) {
  vec3 cd = normalize(lookat - cameraPos);
  vec3 cr = normalize(cross(vec3(0,1,0), cd));
  vec3 cu = normalize(cross(cd, cr));
  return mat3(-cr, cu, -cd);
}

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



void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
  vec2 mouseUV = iMouse.xy / iResolution.xy;
  if (mouseUV == vec2(0)) mouseUV = vec2(0.5, 0.4);
  
  vec3 col = vec3(0);
  vec3 ro = vec3(0, 0, 2);
  vec3 lookat = vec3(0,0,-2);
  vec3 rd = normalize(vec3(uv, -1)) * camera(ro, lookat);
  
  float cameraRadius = 2.;
  ro.yz = ro.yz * cameraRadius * rotate2d(mix(-PI/2., PI/2., mouseUV.y));
  ro.xz = ro.xz * rotate2d(mix(-PI, PI, mouseUV.x)) + lookat.xz;

  Surface s = rayMarch(ro, rd);
  if (s.sd < MAX_DIST) {
    vec3 p = ro + rd * s.sd;
    vec3 normal = calcNormal(p);
    vec3 lightPos = vec3(0,2,0);
    vec3 lightDi = normalize(lightPos - p) * 0.65;
    float diffuse = clamp(dot(lightDi, normal), 0., 1.) * 0.5 + 0.5;
    
    col = s.col * diffuse;
    col = col * 0.7 + COLOR_AMBIENT * 0.3;
    col = mix(col, COLOR_BACKGROUND, 1.0 - exp(-0.00005 * s.sd * s.sd * s.sd)); // fog
 
  }
  else {
    col = COLOR_BACKGROUND;
  }
  
  col += texture(iChannel1, fragCoord/iResolution.xy).rgb;
  fragColor = vec4(col,1.0);
}

 

배경에 눈이 내리는 씬을 구현한다.

아래 구현된 코드를 참조하자.

https://www.shadertoy.com/view/3ld3zX

 

위에서 구현 된 컬러값을 채널 1번 버퍼에 넣어서,

Image 코드에서 샘플링된 컬러값을 배경에 적용하자.

 

이미지 코드에서 눈 버퍼에서 샘플링 컬러값을 더한다.

col += texture(iChannel1, fragCoord/iResolution.xy).rgb;

 

눈 버퍼 1 코드 >>

/*
** Buffer A
** Credit: This buffer contains code forked from "snow snow" by changjiu: https://www.shadertoy.com/view/3ld3zX
*/

float SIZE_RATE = 0.1;
float XSPEED = 0.5;
float YSPEED = 0.75;
float LAYERS = 10.;

float Hash11(float p)
{
  vec3 p3 = fract(vec3(p) * 0.1);
  p3 += dot(p3, p3.yzx + 19.19);
  return fract((p3.x + p3.y) * p3.z);
}

vec2 Hash22(vec2 p)
{
  vec3 p3 = fract(vec3(p.xyx) * 0.3);
  p3 += dot(p3, p3.yzx+19.19);
  return fract((p3.xx+p3.yz)*p3.zy);
}

vec2 Rand22(vec2 co)
{
  float x = fract(sin(dot(co.xy ,vec2(122.9898,783.233))) * 43758.5453);
  float y = fract(sin(dot(co.xy ,vec2(457.6537,537.2793))) * 37573.5913);
  return vec2(x,y);
}

vec3 SnowSingleLayer(vec2 uv,float layer){
  vec3 acc = vec3(0.0,0.0,0.0);
  uv = uv * (2.0 + layer);
  float xOffset = uv.y * (((Hash11(layer)*2.-1.)*0.5+1.)*XSPEED);
  float yOffset = YSPEED * iTime;
  uv += vec2(xOffset,yOffset);
  vec2 rgrid = Hash22(floor(uv)+(31.1759*layer));
  uv = fract(uv) - (rgrid*2.-1.0) * 0.35 - 0.5;
  float r = length(uv);
  float circleSize = 0.04*(1.5+0.3*sin(iTime*SIZE_RATE));
  float val = smoothstep(circleSize,-circleSize,r);
  vec3 col = vec3(val,val,val)* rgrid.x ;
  return col;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
  vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;

  vec3 acc = vec3(0,0,0);
  for (float i = 0.; i < LAYERS; i++) {
    acc += SnowSingleLayer(uv,i);
  }

  fragColor = vec4(acc,1.0);
}

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

12. RayMarching - rain and lights  (0) 2024.08.18
10. RayMarching - Cube map  (0) 2024.08.13
09. RayMarching - SD Operation  (0) 2024.08.12
08. RayMarching - Shadow  (0) 2024.08.10
07. RayMarching - Frenel effect  (0) 2024.08.10