ACG: Ray Marching
This is a full example of doing ray marching from scratch in GLSL. Everything is done in a single fragment shader using all the advanced tricks and techniques we learned in class.
/* Constants defined at the top. */ const float NEAR_CLIPPING_PLANE = 0.1; const float FAR_CLIPPING_PLANE = 10.0; const int MARCHING_STEPS = 100; const float EPSILON = 0.0001; const float DISTANCE_BIAS = 1.0; const vec3 LIGHT_COL = vec3( 2.5 ); float sdSphere( vec3 p, float r ) { return length(p) - r; } float sdBox( vec3 p, in vec3 b ) { vec3 q = abs(p) - b; return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0); } float sdTorus( vec3 p, vec2 t ) { vec2 q = vec2(length(p.xy)-t.x,p.z); return length(q)-t.y; } float sdPlane( vec3 p, vec4 n ) { return dot( p, n.xyz ) + n.w; } vec2 sdUnion( vec2 a, vec2 b ) { return (a.x < b.x) ? a : b; } float sdSmoothUnion( float a, float b, float k ) { float h = max( k-abs(a-b), 0.0 )/k; return min( a, b ) - h*h*k*(1.0/4.0); } /* The scene is defined here, and is a function of time (u_time uniform) */ vec2 scene( vec3 pos ) { float ds = sdSphere( vec3(pos) + vec3(0.0,-0.05,0.0), 0.25 ); float dx = sdTorus( vec3(pos) + vec3(0.0,-0.05,0.0), vec2(0.2, 0.05)); float db = sdBox(pos - vec3(0.0, -0.25,0.0), vec3(0.2,0.05,0.2)); float dp = sdPlane( vec3(pos), vec4( 0.0, 1.0, 0.0, 0.25 ) ); vec2 dtop = mix( vec2(ds,0.0), vec2(dx,1.0), sin(u_time)*0.5+0.5 ); float dbase = sdSmoothUnion(db,dp,0.1); return sdUnion( vec2(dbase,2.0), dtop ); } /* Computes the normals using a tetrahedron for efficiency. */ vec3 normal( vec3 pos ) { const float e = 0.0001; const vec2 k = vec2(1.0,-1.0); return normalize( k.xyy*scene( pos + k.xyy*e ).x + k.yyx*scene( pos + k.yyx*e ).x + k.yxy*scene( pos + k.yxy*e ).x + k.xxx*scene( pos + k.xxx*e ).x ); } /* The heart of our raymarching approach. */ vec2 raymarch( vec3 ro, vec3 rd ) { vec2 t = vec2( NEAR_CLIPPING_PLANE, 0.0 ); for (int i=0; i < MARCHING_STEPS; i++) { vec3 pos = ro + t.x*rd; vec2 h = scene(pos); if (h.x < EPSILON) break; t.x += h.x * DISTANCE_BIAS; t.y = h.y; if (t.x > FAR_CLIPPING_PLANE) break; } if (t.x > FAR_CLIPPING_PLANE) t = vec2( -1.0, 0.0 ); return t; } /* We are approximating soft shadows for "free" with raymarching. */ float softshadow( vec3 ro, vec3 rd, float k ) { float res = 1.0; float ph = 1e20; float t = NEAR_CLIPPING_PLANE; for (int i=0; i < MARCHING_STEPS; i++) { float h = scene( ro+t*rd ).x; if (h < EPSILON) return 0.0; float y = h*h/(2.0*ph); float d = sqrt(h*h-y*y); res = min( res, k*d/max(0.0,t-y) ); ph = h; t += h * DISTANCE_BIAS; if (t > FAR_CLIPPING_PLANE) break; } return res; } /* This is an analytically anti-aliased checkerboard pattern. */ float checkerboard(vec2 p) { vec2 w = fwidth(p) + 0.001; vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; return 0.5 - 0.5*i.x*i.y; } void main () { /* Light is a point light with no falloff that moves around based on time. */ vec3 LIGHT_POS = 3.0 * vec3( sin(u_time*0.7), 1.0, cos(u_time*0.7) ); /* Very simple orthogonal camera model. Rays go in the negative z-axis. */ vec2 uv = (2.0*gl_FragCoord.xy - u_resolution.xy) / u_resolution.y; vec3 ro = vec3(0.0,0.0,1.0); vec3 rd = normalize( vec3(uv, -1.5) ); /* Raymarch the scene here. */ vec2 t = raymarch( ro, rd ); vec3 col; /* We determine the colour from the material. */ if (t.x > 0.0) { /* Determine the colour mased on material (stored in t.y) */ vec3 pos = ro + t.x * rd; vec3 nor = normal( pos ); vec3 light_dir = normalize(LIGHT_POS - pos); vec3 mat; /* For the floor we use the checkerboard pattern. */ if (t.y == 2.0) mat = vec3(checkerboard( pos.xz*20.0 ) * 0.2 + 0.7) * 0.2; /* Otherwise we interpolate between two different colours. */ else mat = mix( vec3(0.2,0.0,0.0), vec3(0.0,0.2,0.0), t.y ); /* Shadows computed with raymarching. */ float sha = softshadow( pos + nor*0.001, light_dir, 8.0 ); /* Lighting is simple based on a point light. */ float sun = max( dot( nor, light_dir ), 0.0 ); vec3 lin = sun*LIGHT_COL * pow(vec3(sha),vec3(1.5)); /* Ilumination and material are multiplied together. */ col = lin * mat; } /* Ray did not hit anything so we draw "the sky" */ else col = vec3(0.4,0.5,0.75) - 0.4*rd.y; /* Gamma correction. */ col = pow( col, vec3(1.0/2.2) ); colour_out = vec4(col,1.0); }