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