RT Graphics Programming
Lesson 04 of 28

Part 1 · Ray tracing by hand

4. Meeting a Sphere

Solve the first visibility equation and produce a shape without asking the canvas to draw one.

Graphics glossary 22 terms mentioned in this lesson

Select any term for a clear definition. The return button brings you back to the exact term link you used.

We are ready to produce the first recognisable object, but we shall not draw it. The distinction is the whole exercise. A drawing API would project a circle and fill the pixels inside it. Our program will send one ray per sample, substitute that ray into a three-dimensional sphere equation and colour the pixel only when the resulting equation has an acceptable solution.

A sphere is a particularly useful first primitive because its definition is compact and its intersection exposes several ideas that return throughout ray tracing: substitution, multiple mathematical solutions, a validity interval and the need to retain the nearest acceptable result.

What you should be able to account for

  • Derive the ray-sphere quadratic from the ray and sphere equations.
  • Use the discriminant to distinguish a miss, tangent and two-root intersection.
  • Select the smallest root inside a positive query interval.

Describe the surface before testing it

A sphere has centre C and radius r. A point P lies on its surface when its displacement from the centre has length r. Squaring both sides avoids an unnecessary square root:

(P − C) · (P − C) = r²

The ray supplies possible points P(t) = O + tD. Substitute that expression for P:

(O + tD − C) · (O + tD − C) = r²

Expand and collect terms in t. Let oc = O − C:

(D · D)t² + 2(oc · D)t + (oc · oc − r²) = 0

This has the standard quadratic form at² + bt + c = 0. If the direction is normalised, D · D is one, but keeping a in the implementation makes the function correct for any non-zero direction and makes the derivation easier to compare with the code.

The discriminant classifies the question

The discriminant b² − 4ac determines the number of real roots. A negative value means the infinite line misses the sphere. Zero means it touches at one tangent point. A positive value produces two roots, normally the entry and exit positions along the line.

We can use halfB = oc · D to remove repeated factors of two. The discriminant becomes halfB² − ac, and the roots become:

t = (−halfB ± √(halfB² − ac)) / a

function hitSphere(ray, sphere, tMin, tMax) {
    const oc = subtract(ray.origin, sphere.centre);
    const a = dot(ray.direction, ray.direction);
    const halfB = dot(oc, ray.direction);
    const c = dot(oc, oc) - sphere.radius * sphere.radius;
    const discriminant = halfB * halfB - a * c;

    if (discriminant < 0) {
        return null;
    }

    const rootTerm = Math.sqrt(discriminant);
    let root = (-halfB - rootTerm) / a;
    if (root < tMin || root > tMax) {
        root = (-halfB + rootTerm) / a;
        if (root < tMin || root > tMax) {
            return null;
        }
    }

    return root;
}

The smaller root is tested first because the ray should normally report the first surface it encounters. The interval is part of the query. A root behind the camera is mathematically real but irrelevant to camera visibility. A root beyond a nearer object may also be irrelevant once several objects are present. Geometry answers within the interval it is given; the caller decides why that interval matters.

Intersection laboratory

Watch the Discriminant Decide Hit or Miss

Move the sphere across one diagnostic ray and compare its discriminant, roots and hit result with the silhouette produced by all camera rays.

Change this, then watch this: Move the sphere sideways. Watch the selected ray change from two real roots and HIT to no real roots and MISS.

Hit and miss imageA sphere silhouette produced by primary-ray intersection tests.Ready
A sphere silhouette produced by primary-ray intersection tests.
One diagnostic rayOne ray crossing or missing a sphere, with its calculated roots.Calculation
One ray crossing or missing a sphere, with its calculated roots.
Perpendicular offset
0.000
Discriminant
0.672
Roots
2.180, 3.820
Ray result
HIT

t = 3 ± √0.672

What this establishes:

Produce the silhouette

For every pixel centre, construct the primary ray from Lesson 3 and call hitSphere. A valid root receives the sphere colour. A miss receives a sky colour derived from the vertical ray direction. The boundary of the resulting shape is not supplied to Canvas. It emerges where neighbouring camera rays change from negative to non-negative discriminants.

const t = hitSphere(ray, sphere, 0.001, Number.POSITIVE_INFINITY);

if (t !== null) {
    writeLinearColour(pixel, [0.94, 0.68, 0.20]);
} else {
    writeLinearColour(pixel, sky(ray.direction));
}

The image resembles a circle because a sphere viewed by a pinhole camera has a circular silhouette. It is not yet shaded, so it contains no evidence of curvature inside the boundary. Move the sphere horizontally. Its three-dimensional centre changes, the quadratic coefficients change for every ray and a different set of pixels obtains roots. That causal chain is the explanation of the movement.

A ray through the centre

Let the ray begin at O = (0, 0, 0) with D = (0, 0, -1). Let the sphere centre be C = (0, 0, -3) and radius one. Then oc = (0, 0, 3), a = 1, halfB = -3 and c = 8. The discriminant is 9 - 8 = 1. The roots are 3 - 1 = 2 and 3 + 1 = 4. The ray enters at distance two and exits at distance four. A primary visibility query keeps two.

Do not confuse the line with the ray

The quadratic describes intersections along the infinite line parameterised by t. Our visibility ray is one directed part of that line. Suppose the sphere is centred at positive Z while the camera looks down negative Z. The quadratic may still produce real roots, but both are negative. Accepting either would show an object behind the camera as if it were in front.

This is why testing only discriminant >= 0 is insufficient. It answers whether the line meets the sphere, not whether the requested ray interval meets it. A result can be mathematically correct and operationally unusable because it answered an easier question than the renderer asked.

Tangent values and finite precision

A discriminant that should be zero may be a very small positive or negative value after finite-precision arithmetic. We shall not hide every such case behind one universal epsilon. Tolerances depend on scale and on what decision is being protected. For this teaching scene, the direct comparison keeps the derivation clear. Later robustness work should test scenes at different scales and define a policy from those errors.

Classify three results

A sphere test returns roots -4 and -2. A second returns 0.0002 and 1.8. A third has a negative discriminant. For a primary interval [0.001, ∞), what happens in each case?

Reveal the decisions

The first sphere is behind the accepted ray interval, so it is a miss. The second sphere's first root is too close, but the second root is inside the interval, so the ray reports 1.8. This can occur when the origin is inside or numerically at a surface. The third has no real line intersection and is a miss without testing roots.

Interrogate the silhouette

For the centre row of a small image, print each pixel column, discriminant and accepted root. Locate the two columns around one silhouette boundary. Explain the transition using the discriminant, then move the sphere and repeat. Do not use the picture alone as your account of the hit.

Keep this model for Vulkan

The same quadratic can run in many shader invocations or inside a ray tracing intersection stage. Parallel execution does not change which coefficients are correct or which root is valid. The later implementation will make data layout and divergence important, but first it must preserve this interval-tested geometric result.

We can intersect one sphere and obtain its first valid distance. A scene contains several possible answers. Lesson 5 makes visibility explicit by retaining the smallest acceptable distance across all tested objects.