- Sign In
- Sign Up
A (less) Tiny Raymarcher
This is a tiny (but not so much anymore) ray marcher that I wrote to continue my studies on ray marching, this time exploring soft shadows and supersampling anti-aliasing.
Created by marcogomez on Sun, 31 Oct 2021 01:24:09 GMT.
#version 300 es // ╔═════════════╦════════════════╗ // ║ Marco Gomez ║ https://mgz.me ║ // ╚═════════════╩════════════════╝ precision highp float; uniform vec2 resolution; uniform vec2 mouselerp; uniform float time; out vec4 fragColor; #define marchSteps 128 const float PI = acos(-1.0); const float TAU = PI * 2.0; mat4 rotationMatrix(vec3 axis, float angle) { axis = normalize(axis); float s = sin(angle); float c = cos(angle); float oc = 1.0 - c; return mat4( oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0, oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0, 0.0, 0.0, 0.0, 1.0 ); } vec3 rotate(vec3 v, vec3 axis, float angle) { mat4 m = rotationMatrix(axis, angle); return (m * vec4(v, 1.0)).xyz; } vec2 rotate(vec2 v, float angle) { float co = cos(angle), si = sin(angle); return v * mat2(co, -si, si, co); } float disturbRadius(float radius, vec3 axis) { float mul = 4.0; float mag = 9.0; float minRes = min(resolution.x, resolution.y); vec2 fc = gl_FragCoord.xy / minRes; float waveTime = time * 2.0; vec2 distortOffset = vec2( sin(waveTime + fc.y * TAU), sin(waveTime + fc.x * TAU) ) * vec2(0.5); axis.xy += distortOffset; axis.yz += distortOffset * -0.5; float disturbed = ( radius * 1.0 + 0.03 * sin((mul - 1.0) * time + axis.x * (mag)) + 0.04 * sin((mul - 2.0) * time + axis.y * (mag - 1.0)) + 0.05 * sin((mul - 3.0) * time + axis.z * (mag - 2.0)) ); return disturbed; } float sphereSDF(vec3 p, float radius) { radius = disturbRadius(radius, p); return length(p) - radius; } float mapDistance(vec3 p) { vec3 spherePos = vec3(0.0, 1.75, 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 = mapDistance(p); dist += sceneDist; if (dist > 300.0 || sceneDist <= 0.01) { break; } } return dist; } vec3 getNormal(vec3 p) { float dist = mapDistance(p); vec2 closeSample = vec2(0.01, 0.0); vec3 closeSampleV3 = vec3( mapDistance(p - closeSample.xyy), mapDistance(p - closeSample.yxy), mapDistance(p - closeSample.yyx) ); vec3 normal = dist - closeSampleV3; return normalize(normal); } float getAmbientOcclusion(vec3 pos, vec3 nor) { float occ = 0.0; float sca = 1.0; for (int i = 0; i < 5; i++) { float h = 0.001 + 0.25 * float(i) / 4.0; float d = mapDistance(pos + h * nor); occ += (h - d) * sca; sca *= 0.98; } return clamp(1.0 - 1.6 * occ, 0.0, 1.0); } float getSoftShadow(vec3 ro, vec3 rd, float tmin, float tmax) { float res = 1.0; float t = tmin; float ph = 1e10; for (int i = 0; i < 32; i++) { float h = mapDistance(ro + rd * t); res = min(res, 10.0 * h / t); t += h; if (res < tmin || t > tmax) { break; } } return clamp(res, 0.0, 1.0); } vec3 getCameraPosition(void) { float acc = 0.0; const float camHeight = 3.5; const float camDistanceRadius = 6.0; float camPosX = sin(mouselerp.x * PI) * camDistanceRadius; float camPosY = camHeight; float camPosZ = cos(mouselerp.x * PI) * camDistanceRadius; vec3 ro = vec3(camPosX, camPosY, camPosZ); return ro; } 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); } vec3 getCameraTarget(vec2 uv, vec3 ro) { vec3 camTarget = vec3(0.0, 1.5, 0.0); float camRoll = 0.0; float fov = 1.25; mat3 camMatrix = calcLookAtMatrix(ro, camTarget, camRoll); vec3 rd = normalize(camMatrix * vec3(uv.x, uv.y, fov)); return rd; } vec4 render(vec2 uv) { float ambient = 0.2; float spec = 0.2; vec3 col = vec3(0.0); vec3 material = vec3(0.2, 0.5, 1.0); vec3 ro = getCameraPosition(); vec3 rd = getCameraTarget(uv, ro); float dist = rayMarch(ro, rd); vec3 p = ro + rd * dist; vec3 normal = getNormal(p); vec3 light = normalize(vec3(2.5, 2.5, 1.0)); float l = clamp(dot(light, normal), 0.0, 1.0); vec3 ref = normalize(reflect(rd, -normal)); float r = clamp(dot(ref, light), 0.0, 1.0); float occlusion = getAmbientOcclusion(p, normal); float shadow = getSoftShadow(p, light, 0.1, 5.0); col = ( min(vec3(max(shadow, ambient)), max(l, ambient)) * max(occlusion, ambient) * material + pow(abs(r), 64.0) * spec + normal * 0.02 ); return vec4(col, 1.0); } vec4 superSample(vec2 uv, float res, int steps, float offset) { vec4 rv = vec4(0.0); vec2 stepSize = vec2(offset) / res / float(steps); uv -= vec2(0.5) / res; for (int x = 0; x < steps; ++x) { for (int y = 0; y < steps; ++y) { vec2 off = vec2(float(x), float(y)) + vec2(0.5); rv += render(uv + off * stepSize); } } return rv / float(steps * steps); } float gaussian(float z, float u, float o) { return ( (1.0 / (o * sqrt(TAU))) * (exp(-(((z - u) * (z - u)) / (2.0 * (o * o))))) ); } vec3 gaussGrain(float t) { vec2 ps = vec2(1.0) / resolution.xy; vec2 uv = gl_FragCoord.xy * ps; float seed = dot(uv, vec2(12.9898, 78.233)); float noise = fract(sin(seed) * 43758.5453123 + t); noise = gaussian(noise, 0.0, 0.5); return vec3(noise); } void main(void) { vec2 uv = (gl_FragCoord.xy - resolution.xy * 0.5) / resolution.y; float minRes = min(resolution.x, resolution.y); vec4 color = superSample(uv, minRes, 2, 1.96); vec3 grain = gaussGrain(time) * 0.03; color.xyz -= grain; fragColor = color; }
xxxxxxxxxx
// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;
uniform vec2 resolution;
uniform vec2 mouselerp;
uniform float time;
out vec4 fragColor;
const float PI = acos(-1.0);
const float TAU = PI * 2.0;
mat4 rotationMatrix(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(
oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
vec3 rotate(vec3 v, vec3 axis, float angle) {
mat4 m = rotationMatrix(axis, angle);
return (m * vec4(v, 1.0)).xyz;
}
vec2 rotate(vec2 v, float angle) {
float co = cos(angle), si = sin(angle);
return v * mat2(co, -si, si, co);
}
float disturbRadius(float radius, vec3 axis) {
float mul = 4.0; float mag = 9.0;
float minRes = min(resolution.x, resolution.y);
vec2 fc = gl_FragCoord.xy / minRes;
float waveTime = time * 2.0;
vec2 distortOffset = vec2(
sin(waveTime + fc.y * TAU),
sin(waveTime + fc.x * TAU)
) * vec2(0.5);
axis.xy += distortOffset;
axis.yz += distortOffset * -0.5;
float disturbed = (
radius * 1.0 +
0.03 * sin((mul - 1.0) * time + axis.x * (mag)) +
0.04 * sin((mul - 2.0) * time + axis.y * (mag - 1.0)) +
0.05 * sin((mul - 3.0) * time + axis.z * (mag - 2.0))
);
return disturbed;
}
float sphereSDF(vec3 p, float radius) {
radius = disturbRadius(radius, p);
return length(p) - radius;
}
float mapDistance(vec3 p) {
vec3 spherePos = vec3(0.0, 1.75, 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 = mapDistance(p);
dist += sceneDist;
if (dist > 300.0 || sceneDist <= 0.01) { break; }
}
return dist;
}
vec3 getNormal(vec3 p) {
float dist = mapDistance(p);
vec2 closeSample = vec2(0.01, 0.0);
vec3 closeSampleV3 = vec3(
mapDistance(p - closeSample.xyy),
mapDistance(p - closeSample.yxy),
mapDistance(p - closeSample.yyx)
);
vec3 normal = dist - closeSampleV3;
return normalize(normal);
}
float getAmbientOcclusion(vec3 pos, vec3 nor) {
float occ = 0.0;
float sca = 1.0;
for (int i = 0; i < 5; i++) {
float h = 0.001 + 0.25 * float(i) / 4.0;
float d = mapDistance(pos + h * nor);
occ += (h - d) * sca;
sca *= 0.98;
}
return clamp(1.0 - 1.6 * occ, 0.0, 1.0);
}
float getSoftShadow(vec3 ro, vec3 rd, float tmin, float tmax) {
float res = 1.0;
float t = tmin;
float ph = 1e10;
for (int i = 0; i < 32; i++) {
float h = mapDistance(ro + rd * t);
res = min(res, 10.0 * h / t);
t += h;
if (res < tmin || t > tmax) { break; }
}
return clamp(res, 0.0, 1.0);
}
vec3 getCameraPosition(void) {
float acc = 0.0;
const float camHeight = 3.5;
const float camDistanceRadius = 6.0;
float camPosX = sin(mouselerp.x * PI) * camDistanceRadius;
float camPosY = camHeight;
float camPosZ = cos(mouselerp.x * PI) * camDistanceRadius;
vec3 ro = vec3(camPosX, camPosY, camPosZ);
return ro;
}
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);
}
vec3 getCameraTarget(vec2 uv, vec3 ro) {
vec3 camTarget = vec3(0.0, 1.5, 0.0);
float camRoll = 0.0; float fov = 1.25;
mat3 camMatrix = calcLookAtMatrix(ro, camTarget, camRoll);
vec3 rd = normalize(camMatrix * vec3(uv.x, uv.y, fov));
return rd;
}
vec4 render(vec2 uv) {
float ambient = 0.2;
float spec = 0.2;
vec3 col = vec3(0.0);
vec3 material = vec3(0.2, 0.5, 1.0);
vec3 ro = getCameraPosition();
vec3 rd = getCameraTarget(uv, ro);
float dist = rayMarch(ro, rd);
vec3 p = ro + rd * dist;
vec3 normal = getNormal(p);
vec3 light = normalize(vec3(2.5, 2.5, 1.0));
float l = clamp(dot(light, normal), 0.0, 1.0);
vec3 ref = normalize(reflect(rd, -normal));
float r = clamp(dot(ref, light), 0.0, 1.0);
float occlusion = getAmbientOcclusion(p, normal);
float shadow = getSoftShadow(p, light, 0.1, 5.0);
col = (
min(vec3(max(shadow, ambient)), max(l, ambient)) *
max(occlusion, ambient) * material + pow(abs(r), 64.0) * spec +
normal * 0.02
);
return vec4(col, 1.0);
}
vec4 superSample(vec2 uv, float res, int steps, float offset) {
vec4 rv = vec4(0.0);
vec2 stepSize = vec2(offset) / res / float(steps);
uv -= vec2(0.5) / res;
for (int x = 0; x < steps; ++x) {
for (int y = 0; y < steps; ++y) {
vec2 off = vec2(float(x), float(y)) + vec2(0.5);
rv += render(uv + off * stepSize);
}
}
return rv / float(steps * steps);
}
float gaussian(float z, float u, float o) {
return (
(1.0 / (o * sqrt(TAU))) *
(exp(-(((z - u) * (z - u)) / (2.0 * (o * o)))))
);
}
vec3 gaussGrain(float t) {
vec2 ps = vec2(1.0) / resolution.xy;
vec2 uv = gl_FragCoord.xy * ps;
float seed = dot(uv, vec2(12.9898, 78.233));
float noise = fract(sin(seed) * 43758.5453123 + t);
noise = gaussian(noise, 0.0, 0.5);
return vec3(noise);
}
void main(void) {
vec2 uv = (gl_FragCoord.xy - resolution.xy * 0.5) / resolution.y;
float minRes = min(resolution.x, resolution.y);
vec4 color = superSample(uv, minRes, 2, 1.96);
vec3 grain = gaussGrain(time) * 0.03;
color.xyz -= grain;
fragColor = color;
}
95 fps 18ms
00:00:00.36
0.00