Radiance Cascades

Draw light with the pointer and watch it bounce: a jump-flooded distance field feeds six radiance cascades — base 4, geometric intervals, linear RGBA16F — merged top-down with visibility alpha into 2D global illumination.

Open fullscreen
import { jfa_pick } from "./jfa-step.wgsl";
 
// One jump-flood round: look at the 3x3 neighbourhood `jump` texels away and keep the
// nearest seed. Run with jump = size/2, size/4 ... 1 the seeds converge to the exact
// nearest emitter in ceil(log2(size)) passes instead of a search per texel.
 
struct JfaStep {
  size: vec2f,
  jump: f32,
  _pad: f32,
};
 
@group(0) @binding(0) var<uniform> jfa: JfaStep;
@group(0) @binding(1) var seeds: texture_2d<f32>;
 
@fragment
fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
  let pixel = clamp(floor(uv * jfa.size), vec2f(0.0), jfa.size - 1.0);
  let position = pixel + 0.5;
  let coord = vec2i(pixel);
  let limit = vec2i(jfa.size) - vec2i(1);
  let jump = i32(jfa.jump);
 
  var best = textureLoad(seeds, coord, 0);
  for (var y = -1; y <= 1; y = y + 1) {
    for (var x = -1; x <= 1; x = x + 1) {
      let neighbour = coord + vec2i(x, y) * jump;
      // Out-of-bounds neighbours are skipped rather than clamped: a clamped read would
      // duplicate the edge seed and bias distances along the border.
      if (neighbour.x < 0 || neighbour.y < 0 || neighbour.x > limit.x || neighbour.y > limit.y) { continue; }
      best = jfa_pick(best, textureLoad(seeds, neighbour, 0), position);
    }
  }
  return best;
}