본문 바로가기
Shader CG

03. RayMarhcing - Rendering Many Object

by SimonLee 2024. 8. 2.

 

생성한 원을 여러개 구성하고 땅도 추가해본다.

각 물체의 색도 각각 적용해보자.

 

 

sdSphere 함수의 리턴되는 값은 point와 원형의 표면까지 거리였다.

구를 그릴때는 원점 기준으로 반지름 만큼 떨어진 지점이 표면임을 알수 있었다.

그 표면의 지점과 카메라의 거리만큼을 ray에서 더해 주었는데,

 

여러개 원을 구성할때는 표면까지 거리 중 가장 최소의 값만 취하면 된다.

우리 눈에 더 앞에있는 물체가 보이는 것과 같은 의미이다.

 

uv 값의 범위는 가로세로 동일 비율을 적용하고 원점 기준으로 좌표계를 옮겨서 다음과 같다.

-0.5 < y < 0.5 , -1 < x < 1

 

sdFloor가 음수가 되면 더 이상 ray를 증가시키지 않기 때문에, 구를 찾을수가 없다.

그렇기 때문에 y 값을 + 1 증가시켜 것 같은데,

구의표면과 카메라의 거리대상으로 한 sdSphere 와 달리,

floor의 경우 y 좌표 값을 리턴함으로써 비교대상이 맞지 않는 다는 생각을 ;;;  계속 하게된다..

 

그래서 length 기준으로 다시 생각을 해 보았다.

length로 바꾸어 생각을 해보면, 픽셀의 좌표가 p.x = 0, p.z = 0 이면

floor 영역과 카메라 렌즈 딱 붙어 있고, +1 해주면

좌표의 범위가 -0.5 < y < 0.5 --> 0.5 < y < 1.5 변경이 된다.

 

거리 자체도 +1 로 인하여 0보다 항상 크기 때문에,

광선이 물체와 맞았을때 훨씬 0과 가깝기 때문에 floor가 물체를 가리지도 않는다.

 

//s.dist = p.y + 1.;
s.dist = length(vec3(0., p.y + 1., 0));

 

코드 >> 

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

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

float sdScene(vec3 p) {
  vec3 offsetA = vec3(2, 0, 0);
  vec3 offsetB = vec3(-2, 0, 0);
  float sphereLeft = sdSphere(p, 1., offsetA);
  float sphereRight = sdSphere(p, 1., offsetB);
  float sdfloor = sdFloor(p);
  return min(min(sphereRight, sphereLeft), sdfloor);
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    float r = 1.; // radius of sphere
    return normalize(
      e.xyy * sdScene(p + e.xyy) +
      e.yyx * sdScene(p + e.yyx) +
      e.yxy * sdScene(p + e.yxy) +
      e.xxx * sdScene(p + e.xxx));
}

float rayMarch(vec3 ro, vec3 rd, float s, float e) {
    int step = 255;
    float depth = 0.;
    
    for (int i=0; i<step; i++) {
        vec3 p = ro + rd * depth;
        float l = sdScene(p);
        depth += l;
        if (depth < 0. || depth > 100.) break;
    }
    return depth;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
    vec3 backgroundCol = vec3(0.15);
    vec3 sphereCol = vec3(0.8, 0.8, 0.1);
 
    vec3 lightPos = vec3(0., 100., 0.);
    vec3 ro = vec3(0, 0, 5);
    vec3 rd = normalize(vec3(uv, -1));
    
    vec3 col = backgroundCol;
    float depth = rayMarch(ro, rd, .0, 100.);
    if (depth < 100.) {
        vec3 p = ro + rd * depth;
        vec3 normal = calcNormal(p);
        vec3 lightDir = normalize(lightPos - p);
        
        vec3 diffuse = clamp(dot(normal, lightDir), 0.15, 1.) * sphereCol * 0.8;
        diffuse += backgroundCol * 0.3;
        col = diffuse;
    } 
    fragColor = vec4(col,1.0);
}

 

 

각 물체마다 다른 색을 적용해보자.

Surface 구조체를 하나 만든다.

- sd는 카메라와 물체의 픽셀간 거리

- col 는 물체의 컬러 값이다.

 

전 버전은 물체와 카메라가 가장 가까운 거리만 판별하고 거리만 리턴했다면,

현재 버전은 거리와 색상이 들어있는 Surface 구조체를 리턴한다.

 

또 하나의 특징이 floor의 색상을 체크무늬로 바꾸었다.

floor 함수와 mod를 통해 반복되는 형태를 만드는 것은 방법은 이전 챕터 bookshader 편을 참고하자.

vec3 floorColor = vec3(1. + 0.7*mod(floor(p.x) + floor(p.z), 2.0));

floor 함수는 소수는 무시하고 정수만 리턴한다.

floor(p.x) 는 1번 영역은 -1, 2번 영역 0 , 3번 영역 -1, 4번 영역 0 이다.

floor(p.y) 는 1번 영역은 0,  2번 영역 0 , 3번영역  -1, 4번 영역 -1 이다.

 

그 둘을 더하게 되면 1번 영역 -1, 2번 영역 0, 3번 영역 -2, 4번 영역 -1 이다.

나머지 연산 mod 2 를 하게 되면 다음과 같다.

1번 영역 1, 2번 영역 0, 3번 영역 0, 4번 영역 1 이다.

 

모듈러 음수 연산은 양수처럼 생각하고 계산하구 + M을 해주면 된다.

다음과 같이 체크 무늬가 나오게 된다.

체크 무늬를 만들기 위하여 floor 함수와 mod 함수를 사용하는 방법을 알아두자.

 

 

전체코드 >>

#define MAX_MARCHING_STEP 255
#define MIN_DIST 0.
#define MAX_DIST 100.
#define PRECISION 0.001

struct Surface {
    float sd;
    vec3 col;
};

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

Surface sdFloor(vec3 p, vec3 color) {
    Surface s;
    s.sd = p.y + 1.;
    s.col = color;
    return s;
}

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

Surface sdScene(vec3 p) {
    vec3 offsetA = vec3(2, 0, 0);
    vec3 offsetB = vec3(-2, 0, 0);
    vec3 leftSphereColor = vec3(1, 0, 0);
    vec3 rightSphereColor = vec3(0, 1, 0);
    vec3 floorColor = vec3(1. + 0.7*mod(floor(p.x) + floor(p.z), 2.0));
    
    Surface sphereLeft = sdSphere(p, 1., offsetA, leftSphereColor);
    Surface sphereRight = sdSphere(p, 1., offsetB, rightSphereColor);
    Surface sdfloor = sdFloor(p, floorColor);
    
    Surface l;
    l = minWidthColor(sphereLeft, sphereRight);
    l = minWidthColor(l, sdfloor);
    return l;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    float r = 1.; // radius of sphere
    return normalize(
      e.xyy * sdScene(p + e.xyy).sd +
      e.yyx * sdScene(p + e.yyx).sd +
      e.yxy * sdScene(p + e.yxy).sd +
      e.xxx * sdScene(p + e.xxx).sd);
}

Surface rayMarch(vec3 ro, vec3 rd, float s, float e) {
    float depth = s;
    Surface sur; // closest object
    
    for (int i=0; i < MAX_MARCHING_STEP; i++) {
        vec3 p = ro + rd * depth;
        sur = sdScene(p);
        depth += sur.sd;
        if (depth < MIN_DIST || depth > MAX_DIST) break;
    }
    sur.sd = depth;
    return sur;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
    vec3 backgroundCol = vec3(0.15);
    vec3 sphereCol = vec3(0.8, 0.8, 0.1);
 
    vec3 lightPos = vec3(0., 100., 0.);
    vec3 ro = vec3(0, 0, 5);
    vec3 rd = normalize(vec3(uv, -1));
    
    vec3 col = backgroundCol;
    Surface s = rayMarch(ro, rd, MIN_DIST, MAX_DIST);
    float depth = s.sd;
    
    if (depth < 100.) {
        vec3 p = ro + rd * depth;
        vec3 normal = calcNormal(p);
        vec3 lightDir = normalize(lightPos - p);
        
        vec3 diffuse = clamp(dot(normal, lightDir), 0.15, 1.) 
            * s.col * 0.8;
        diffuse += backgroundCol * 0.3;
        col = diffuse;
    } 
    fragColor = vec4(col,1.0);
}

 

함수마다 리턴하는 방식 말고 각 아이템의 ID를 정해서 리턴하는 방식도 셰이더 토이에서 많이 

사용한다고 한다.

vec2 [ 카메라와 물체거리 , id ] 로 만들어서, 씬을 구성할때 id 값만 넣어주고

최종 컬러 출력할때, id 값으로 판단하여 색을 블렌딩한다.

이 방법이 더 좋아 보인다.  

 

전체코드 >>

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

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

vec2 opu(vec2 a, vec2 b) {
    if (a.x < b.x) return a;
    return b;
}

vec2 map(vec3 p) {
  vec3 offsetA = vec3(2, 0, 0);
  vec3 offsetB = vec3(-2, 0, 0);
  vec2 sphereLeft = vec2(sdSphere(p, 1., offsetA), 1.);
  vec2 sphereRight = vec2(sdSphere(p, 1., offsetB), 2.);
  vec2 sdfloor = vec2(sdFloor(p), 3.);
  vec2 rst;
  rst = opu(sphereLeft, sphereRight);
  rst = opu(rst, sdfloor);
  return rst;
}

vec3 calcNormal(in vec3 p) {
    vec2 e = vec2(1.0, -1.0) * 0.0005; // epsilon
    float r = 1.; // radius of sphere
    return normalize(
      e.xyy * map(p + e.xyy).x +
      e.yyx * map(p + e.yyx).x +
      e.yxy * map(p + e.yxy).x +
      e.xxx * map(p + e.xxx).x);
}

vec2 rayMarch(vec3 ro, vec3 rd, float s, float e) {
    int step = 255;
    float depth = 0.;
    vec2 closeObj;
    
    for (int i=0; i<step; i++) {
        vec3 p = ro + rd * depth;
        closeObj = map(p);
        depth += closeObj.x;
        if (depth < 0. || depth > 100.) break;
    }
    closeObj.x = depth;
    return closeObj;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
    vec3 backgroundCol = vec3(0.15);
    vec3 objColor = vec3(0);
 
    vec3 lightPos = vec3(0., 100., 0.);
    vec3 ro = vec3(0, 0, 5);
    vec3 rd = normalize(vec3(uv, -1));
    vec3 col = backgroundCol;
    
    vec2 closeObj = rayMarch(ro, rd, .0, 100.);
    float depth = closeObj.x;
    if (depth < 100.) {
        vec3 p = ro + rd * depth;
        vec3 normal = calcNormal(p);
        vec3 lightDir = normalize(lightPos - p);
        
        vec3 lsphereColor = vec3(1, 0, 0);
        vec3 rsphereColor = vec3(0, 1, 0);
        vec3 floorColor = vec3(1. + 0.7*mod(floor(p.x) + floor(p.z), 2.0));
        
        if (closeObj.y <= 1.) objColor = lsphereColor;
        else if (closeObj.y <= 2.) objColor = rsphereColor;
        else if (closeObj.y <= 3.) objColor = floorColor;
        
        vec3 diffuse = clamp(dot(normal, lightDir), 0.15, 1.) * objColor * 0.8;
        diffuse += backgroundCol * 0.3;
        col = diffuse;
    } 
    fragColor = vec4(col,1.0);
}

 

 

Reference

 

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

05. RayMarching - Camera move  (0) 2024.08.05
04. RayMarching - Rotation  (0) 2024.08.04
02. RayMarhcing - Shading Sphere  (0) 2024.08.01
01. RayMarhcing - Rendering Sphere  (0) 2024.07.31
3D Scenes With Ray Marching  (0) 2024.06.27