Ray and Shape Intersections

Ray and Shape Intersections

Intersection converts a ray query into a shading record containing the nearest admissible parameter $t$, the corresponding surface position, the geometric normal, and any additional information required by the material system. The central obligation is not simply to find a mathematical root, but to select the nearest root that is valid for the interval requested by the caller.

Parametric ray:

$$ \mathbf{r}(t) = \mathbf{o} + t,\mathbf{d} $$

This text assumes unit d. If you leave directions unnormalized, scale the formulas accordingly and stay consistent.

Ray intersecting a sphere at two values of t

A ray may miss, graze, or intersect a sphere twice. The renderer keeps the nearest t inside the valid interval.

Sphere

For center c and radius R:

$$ (\mathbf{o} + t\mathbf{d} - \mathbf{c})\cdot(\mathbf{o} + t\mathbf{d} - \mathbf{c}) = R^2 $$

This is a quadratic equation a t^2 + b t + c = 0. With unit d:

$$ \begin{aligned} \mathbf{oc} &= \mathbf{o} - \mathbf{c} \ a &= 1 \ b &= 2(\mathbf{oc}\cdot\mathbf{d}) \ c &= (\mathbf{oc}\cdot\mathbf{oc}) - R^2 \ \Delta &= b^2 - 4ac \end{aligned} $$

A negative discriminant indicates that the ray misses the sphere entirely, whereas two real roots normally correspond to entry and exit points. The smaller root should be tested first, followed by the larger root if necessary, and each candidate must be checked against the interval $[t_{\min},t_{\max}]$ rather than accepted merely because it is positive.

$$ \begin{aligned} \mathbf{p} &= \mathbf{o} + t\mathbf{d} \ \mathbf{n} &= \operatorname{normalize}(\mathbf{p} - \mathbf{c}) \end{aligned} $$

Transmission through a closed surface additionally requires knowledge of whether the ray encountered the front or back face, so that this orientation should be stored explicitly in the hit record instead of reconstructed later from ad hoc sign tests.

Plane and triangle

Plane through p0 with normal n:

$$ t = \frac{(\mathbf{p}_0 - \mathbf{o})\cdot\mathbf{n}}{\mathbf{d}\cdot\mathbf{n}} $$

If the denominator is nearly zero, the ray is parallel. Reject t outside range.

A triangle is the plane test plus a point-in-triangle condition. Möller–Trumbore is the standard compact solution and yields barycentric coordinates. You need barycentrics as soon as normals or UVs are interpolated.

// conceptual form
edge1 = v1 - v0
edge2 = v2 - v0
// solve o + t d = v0 + u edge1 + v edge2
// accept if t in range and u >= 0, v >= 0, u + v <= 1

Geometric normal: normalize(cross(edge1, edge2)). Winding defines the front face.

Nearest hit over a scene

closest = t_max
hit = none
for each shape:
    if shape.hit(ray, t_min, closest, temp):
        closest = temp.t
        hit = temp
return hit

Shrink t_max immediately after a hit. That prunes farther work and is the seed of BVH traversal.

Hit records

Minimum fields:

  • t, p, geometric normal
  • front_face
  • material reference
  • u, v when textured

Set front_face from dot(d, outward_normal) < 0. Shaders often want a normal facing the ray. Shadow tests may still need the geometric side. Keep both concepts available.

Robustness

Reject candidates outside the interval before writing the hit. Grazing rays and needle triangles are numerically hostile. On large scenes, a pure absolute epsilon can fail; scale tolerances with scene size when needed. Drop zero-area primitives at load time rather than hoping intersection code survives them.

Shadow rays should use an any-hit path that can exit early. Closest-hit traversal is for shading. Binary visibility only needs yes or no.

Common errors

  • Taking the farther sphere root first
  • Leaving t_min = 0 and immediately re-hitting the shaded surface
  • Inconsistent winding across meshes
  • Forgetting a dedicated t_max on shadow rays
  • Allocating full shading records when a boolean occlusion query would do

Prove spheres and triangles with a normal-colored debug view under camera motion. If that view is clean, shading has a foundation.

Why unit directions simplify teaching

With unit d, sphere a = 1 and many t values are true distances. That helps debugging. Production code sometimes keeps non-unit directions for transform convenience. Both are valid. The error is mixing them inside one intersection routine. Document the invariant at the top of the file.

Shared edge policy

Two triangles sharing an edge may both claim a ray. Either winner is acceptable if the choice is stable. Sparkling silhouettes under tiny camera motion usually come from numerical t noise or secondary-ray bias, not from the existence of a shared edge. Stabilize the interval and bias before inventing geometric topology hacks.