Task 4 of 6

Normals Without Geometry

Lighting needs surface normals, and a mesh would hand them to you per-vertex. We have no mesh — but we have something better. The normal of an implicit surface is the gradient of its distance field: the direction in which distance grows fastest is exactly "straight off the surface".

Estimate it with central differences: nudge the hit point by a tiny e along each axis, sample the field on both sides, subtract. Six extra field evaluations, then normalize. The classic way to sanity-check normals is to paint them: n * 0.5 + 0.5 maps each component into color range — a head-on surface (normal (0, 0, -1), pointing at the camera) renders as rgb(0.5, 0.5, 0), that mustard-olive tone every graphics programmer knows.

nudge ± e, subtract, normalize — six taps and the surface points at you
Goal: at each hit point, compute the finite-difference normal of sceneDist and paint each component as n * 0.5 + 0.5 — hint 2 has the exact this.color call.

Requirements

Hint 1 — one axis at a time

The x component before normalizing is

sceneDist(wx + e, wy, pz, …) - sceneDist(wx - e, wy, pz, …)

where pz = -2.5 + tHit. Same pattern for y and z.

Hint 2 — normalize and paint
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
this.color(nx / len * 0.5 + 0.5,
  ny / len * 0.5 + 0.5,
  nz / len * 0.5 + 0.5, 1);

Same idea elsewhere

Gradient-by-central-differences is the same stencil you'd write in a CUDA fluid solver or a ROCm heightfield pipeline, and Metal deferred renderers reconstruct normals from depth buffers with exactly this two-sided sampling.

All tasks in Ray-Marched Metaballs

  1. The Sphere as a Number
  2. Two Spheres Melt Into One
  3. March Until You Hit Something
  4. Normals Without Geometry
  5. Turn On the Light
  6. Soft Shadows, Full Scene

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.