생성한 원을 여러개 구성하고 땅도 추가해본다.
각 물체의 색도 각각 적용해보자.
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 |