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);
}