Task 2 of 5

Where the Ray Hits

Before anything can bounce, a ray has to find a surface. The sibling module Ray-Marched Metaballs walks its rays forward in safe hops, because a signed distance field is all it has to go on. A sphere gives you something better: an exact answer, one square root long.

Write the ray as o + t·d with d a unit vector, put e = o − centre, and asking "where does the ray meet the surface" is asking when |e + t·d|² = r². Because d is unit, the coefficient is exactly 1 and the whole thing collapses to

b = e · d
c = e · e - r * r
t = -b ± Math.sqrt(b * b - c)

Three cases, and all three matter. A negative discriminant means the ray misses. Two roots mean it goes in one side and out the other — the one you can see is the nearer, −b − √(b² − c). And a root at or behind t = 0 is in the ray's past: the sphere is behind the camera, and it must not be reported as a hit. Reject anything up to t = 0.001 rather than just the negatives — the next task fires rays that start on a surface, and in float32 such a ray finds that surface again at a t a hair above zero.

Goal: finish sphereT so it returns the distance to the nearest visible hit, or -1 when there isn't one.

Requirements

Hint 1 — the shape of the function

Three early exits and one answer:

const disc = b * b - c;
if (disc <= 0) {
  return -1;          // the ray misses this sphere entirely
}
const t = -b - Math.sqrt(disc);
if (t <= 0.001) {
  return -1;          // the hit is behind us (or right on top of us)
}
return t;
Hint 2 — why 0.001 and not 0

The next task fires rays that start on a surface. In float32 the starting point is never quite on it, and a root at t = 0.000001 is that same surface hitting itself. The small epsilon is what stops a bounce from being immediately swallowed by the thing it bounced off.

Hint 3 — what the picture should look like

Nearer is brighter. You should get a pale sphere sitting on a floor that fades with distance, and the flat background colour everywhere the ray found nothing.

Same idea elsewhere

An RTX or RDNA ray-tracing core is silicon that does exactly this — traversal plus an intersection test — a few billion times a second, and the API shape around it (DXR's closest-hit shader, OptiX's rtTrace, Vulkan's ray query) is still one thread, one ray, one nearest hit. Analytic primitives never went away either: spheres, boxes and capsules still get intersected in closed form because closed form is faster than a mesh.

All tasks in Progressive Path Tracing: Noise Melting Into an Image

  1. Dice Every Thread Can Roll
  2. Where the Ray Hits
  3. One Path Per Pixel, and It Looks Terrible
  4. The Buffer That Outlives the Frame
  5. The Noise Falls Like 1/√n

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