- Sign In
- Sign Up
A Tiny Raymarcher
This is a tiny ray marcher that I wrote to explain Raymarching, rudimentary diffuse lighting, and the creation of basic 3D primitives using Signed Distance Fields (SDF), to a friend on a Discord call.
Created by marcogomez on Sun, 17 Oct 2021 11:48:34 GMT.
// ╔═════════════╦════════════════╗ // ║ Marco Gomez ║ https://mgz.me ║ // ╚═════════════╩════════════════╝ precision highp float; uniform vec2 resolution; uniform float time; #define marchSteps 128 #define maxDist 300.0 #define surfDist 0.01 const float PI = acos(-1.0); const float TAU = PI * 2.0; float disturbRadius(float radius, vec3 axis) { float minRes = min (resolution.x, resolution.y); vec2 fc = gl_FragCoord.xy / minRes; float wt = time * 2.0; vec2 distortOffset = vec2(sin(wt + fc.y * TAU), sin(wt + fc.x * TAU)) * vec2(0.5); axis.xy += distortOffset; float disturbed = ( radius * 1.0 + 0.03 * sin(4.0 * time + axis.x * 7.0) + 0.04 * sin(3.0 * time + axis.y * 6.0) + 0.05 * sin(2.0 * time + axis.z * 5.0) ); return disturbed; } float sphereSDF(vec3 p, float radius) { radius = disturbRadius(radius, p); return length(p) - radius; } float getDist(vec3 p) { vec3 spherePos = vec3(0.0, 1.5, 0.0); float sphereRadius = 1.0; float sphereDist = sphereSDF(p - spherePos, sphereRadius); float groundDist = p.y; float dist = min(sphereDist, groundDist); return dist; } float rayMarch(vec3 ro, vec3 rd) { float dist = 0.0; for (int i = 0; i < marchSteps; i++) { vec3 p = ro + rd * dist; float sceneDist = getDist(p); dist += sceneDist; if (dist > maxDist || sceneDist <= surfDist) { break; } } return dist; } vec3 getNormal(vec3 p) { float dist = getDist(p); vec2 closeSample = vec2(0.01, 0.0); vec3 closeSampleV3 = vec3( getDist(p - closeSample.xyy), getDist(p - closeSample.yxy), getDist(p - closeSample.yyx) ); vec3 normal = dist - closeSampleV3; return normalize(normal); } float getLight(vec3 p, inout vec3 normal) { vec3 lightPos = vec3(3.0, 5.0, -1.0); vec3 lightVec = normalize(lightPos - p); vec3 lightNor = getNormal(p); normal = lightNor; float diffuse = clamp(dot(lightNor, lightVec), 0.0, 1.0); float distToLight = rayMarch(p + lightNor * surfDist * 15.0, lightVec); if (distToLight < length(lightPos - p)) { diffuse *= 0.1; } return diffuse; } mat3 calcLookAtMatrix(vec3 origin, vec3 target, float roll) { vec3 rr = vec3(sin(roll), cos(roll), 0.0); vec3 ww = normalize(target - origin); vec3 uu = normalize(cross(ww, rr)); vec3 vv = normalize(cross(uu, ww)); return mat3(uu, vv, ww); } void main(void) { vec2 uv = ((gl_FragCoord.xy / resolution.xy) - 0.5) * vec2(resolution.x / resolution.y, 1.0); vec3 col = vec3(0.0); float camX = sin(time * 0.5) * 5.0; float camY = 1.0; float camZ = cos(time * 0.5) * 5.0; vec3 ro = vec3(camX, camY, camZ); vec3 camTarget = vec3(0.0, 1.0, 0.0); float camRoll = sin(time * 0.25) * 0.1 + 0.1; mat3 camMatrix = calcLookAtMatrix(ro, camTarget, camRoll); vec3 rd = normalize(camMatrix * vec3(uv.x, uv.y, 1.0)); float dist = rayMarch(ro, rd); vec3 p = ro + rd * dist; vec3 normal; float diffuse = getLight(p, normal); col = vec3(diffuse) * vec3(0.7, 0.6, 1.0) + normal * 0.07; gl_FragColor = vec4(col, 1.0); }
xxxxxxxxxx
// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;
uniform vec2 resolution;
uniform float time;
const float PI = acos(-1.0);
const float TAU = PI * 2.0;
float disturbRadius(float radius, vec3 axis) {
float minRes = min (resolution.x, resolution.y);
vec2 fc = gl_FragCoord.xy / minRes;
float wt = time * 2.0;
vec2 distortOffset = vec2(sin(wt + fc.y * TAU), sin(wt + fc.x * TAU)) * vec2(0.5);
axis.xy += distortOffset;
float disturbed = (
radius * 1.0 +
0.03 * sin(4.0 * time + axis.x * 7.0) +
0.04 * sin(3.0 * time + axis.y * 6.0) +
0.05 * sin(2.0 * time + axis.z * 5.0)
);
return disturbed;
}
float sphereSDF(vec3 p, float radius) {
radius = disturbRadius(radius, p);
return length(p) - radius;
}
float getDist(vec3 p) {
vec3 spherePos = vec3(0.0, 1.5, 0.0);
float sphereRadius = 1.0;
float sphereDist = sphereSDF(p - spherePos, sphereRadius);
float groundDist = p.y;
float dist = min(sphereDist, groundDist);
return dist;
}
float rayMarch(vec3 ro, vec3 rd) {
float dist = 0.0;
for (int i = 0; i < marchSteps; i++) {
vec3 p = ro + rd * dist;
float sceneDist = getDist(p);
dist += sceneDist;
if (dist > maxDist || sceneDist <= surfDist) { break; }
}
return dist;
}
vec3 getNormal(vec3 p) {
float dist = getDist(p);
vec2 closeSample = vec2(0.01, 0.0);
vec3 closeSampleV3 = vec3(
getDist(p - closeSample.xyy),
getDist(p - closeSample.yxy),
getDist(p - closeSample.yyx)
);
vec3 normal = dist - closeSampleV3;
return normalize(normal);
}
float getLight(vec3 p, inout vec3 normal) {
vec3 lightPos = vec3(3.0, 5.0, -1.0);
vec3 lightVec = normalize(lightPos - p);
vec3 lightNor = getNormal(p);
normal = lightNor;
float diffuse = clamp(dot(lightNor, lightVec), 0.0, 1.0);
float distToLight = rayMarch(p + lightNor * surfDist * 15.0, lightVec);
if (distToLight < length(lightPos - p)) { diffuse *= 0.1; }
return diffuse;
}
mat3 calcLookAtMatrix(vec3 origin, vec3 target, float roll) {
vec3 rr = vec3(sin(roll), cos(roll), 0.0);
vec3 ww = normalize(target - origin);
vec3 uu = normalize(cross(ww, rr));
vec3 vv = normalize(cross(uu, ww));
return mat3(uu, vv, ww);
}
void main(void) {
vec2 uv = ((gl_FragCoord.xy / resolution.xy) - 0.5) * vec2(resolution.x / resolution.y, 1.0);
vec3 col = vec3(0.0);
float camX = sin(time * 0.5) * 5.0;
float camY = 1.0;
float camZ = cos(time * 0.5) * 5.0;
vec3 ro = vec3(camX, camY, camZ);
vec3 camTarget = vec3(0.0, 1.0, 0.0);
float camRoll = sin(time * 0.25) * 0.1 + 0.1;
mat3 camMatrix = calcLookAtMatrix(ro, camTarget, camRoll);
vec3 rd = normalize(camMatrix * vec3(uv.x, uv.y, 1.0));
float dist = rayMarch(ro, rd);
vec3 p = ro + rd * dist;
vec3 normal;
float diffuse = getLight(p, normal);
col = vec3(diffuse) * vec3(0.7, 0.6, 1.0) + normal * 0.07;
gl_FragColor = vec4(col, 1.0);
}
102 fps 17ms
00:00:00.33
0.00