Ray Marching: Surface (SDF) vs. Volumetric — with Sample GLSL
Ray Marching: Surface (SDF) vs. Volumetric
1) Surface (SDF) Ray Marching
- Uses a Signed Distance Function (SDF)
d(p)
giving distance to the nearest surface. - Sphere tracing: advance the ray by the current distance; when close enough, compute the normal and shade.
- Notable: works by Iñigo Quílez (iq); common shapes include boxes, spheres, CSG, fractals.
// --- Minimal SDF sphere tracing (Shadertoy-style fragment) ---
// iTime, iResolution are assumed uniforms; write result to fragColor.
// Signed distance to a sphere of radius r at origin
float sdSphere(vec3 p, float r){ return length(p) - r; }
// Distance field (add more primitives/ops as you like)
float mapSDF(vec3 p){
return sdSphere(p, 1.0);
}
// Estimate normal via central differences
vec3 calcNormal(vec3 p){
const vec2 e = vec2(1e-3, 0.0);
return normalize(vec3(
mapSDF(p + e.xyy) - mapSDF(p - e.xyy),
mapSDF(p + e.yxy) - mapSDF(p - e.yxy),
mapSDF(p + e.yyx) - mapSDF(p - e.yyx)
));
}
// Sphere tracing: returns hit distance or -1.0; also outputs hit position
float raymarchSDF(vec3 ro, vec3 rd, out vec3 hitPos){
float t = 0.0;
for(int i=0;i<120;i++){
hitPos = ro + rd * t;
float d = mapSDF(hitPos);
if(d < 1e-3) return t; // hit
t += d; // distance-based jump
if(t > 50.0) break; // miss
}
return -1.0;
}
// Simple Blinn-Phong shading
vec3 shadeSDF(vec3 p, vec3 rd){
vec3 n = calcNormal(p);
vec3 lp = vec3(2.5, 2.0, 2.0); // light position
vec3 l = normalize(lp - p);
float diff = max(dot(n,l), 0.0);
float spec = pow(max(dot(reflect(-l,n), -rd), 0.0), 32.0);
return vec3(0.05) + diff*vec3(1.0) + 0.25*spec;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord){
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
// Camera setup
vec3 ro = vec3(0.0, 0.0, -3.0);
vec3 rd = normalize(vec3(uv, 1.5));
vec3 col = vec3(0.02, 0.03, 0.05); // background
vec3 p;
float t = raymarchSDF(ro, rd, p);
if(t > 0.0){
col = shadeSDF(p, rd);
}
fragColor = vec4(col, 1.0);
}
Tip: If you see artifacts, clamp step size near surfaces (e.g., t += clamp(d, 1e-4, 1.0)
) and cap max steps.
2) Volumetric Ray Marching
- Integrates a density field across space (fog, clouds, emissive gas, light shafts) instead of hitting a surface.
- Accumulates emission and updates transmittance along the ray (Beer–Lambert law).
- Typical: emissive glows (nebulae, “magic”), fog/smoke/flames; add scattering for god-rays.
// --- Minimal Volumetric Emission + Absorption (Shadertoy-style) ---
// iTime, iResolution assumed; write result to fragColor.
// Smooth density field (nebula-like)
float density(vec3 p){
// center the glow a bit in front of the camera
float r = length(p - vec3(0.0, 0.0, 3.0));
float base = exp(-0.6 * r); // radial falloff
float warp = 0.5 + 0.5 * sin(5.0*p.x + iTime) * cos(5.0*p.y - iTime);
return base * warp; // unitless
}
// March the volume using front-to-back compositing
vec3 raymarchVolume(vec3 ro, vec3 rd){
const float stepLen = 0.05; // fixed step length
const float tMax = 20.0;
const float sigma_t = 2.0; // extinction coefficient
const float emitK = 3.0; // emission gain
float t = 0.0;
float T = 1.0; // accumulated transmittance
vec3 L = vec3(0.0); // accumulated radiance
// jitter to reduce banding (per-pixel)
float seed = dot(floor(gl_FragCoord.xy), vec2(12.9898,78.233));
t += fract(sin(seed)*43758.5453) * stepLen;
for(int i=0;i<600;i++){
if(t > tMax || T < 1e-3) break;
vec3 p = ro + rd * t;
float rho = density(p);
float ext = sigma_t * rho;
float a = 1.0 - exp(-ext * stepLen); // alpha from Beer–Lambert
vec3 emiss = emitK * rho * vec3(1.0, 0.9, 1.2); // tinted glow
L += T * emiss * a; // accumulate light
T *= exp(-ext * stepLen); // update transmittance
t += stepLen;
}
return L;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord){
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
// Camera
vec3 ro = vec3(0.0, 0.0, -3.0);
vec3 rd = normalize(vec3(uv, 1.5));
vec3 col = raymarchVolume(ro, rd);
// Optional tone mapping / gamma
col = col / (1.0 + col);
col = pow(col, vec3(1.0/2.2));
fragColor = vec4(col, 1.0);
}
To add light shafts (single scattering), at each step sample a short shadow ray toward a light to estimate
T_light
, then add L += T * (sigma_s * rho * phase * Li * T_light) * stepLen
.
How to use
- These are Shadertoy-style fragments: paste each into a fragment tab there, or adapt to your engine (WebGL/GLSL).
- Uniforms expected:
iTime
(seconds),iResolution
(viewport size). - Mixing solids + volumes: march to the first surface distance and also composite volume in front using a smaller fixed step.
Comments
Post a Comment