Raytracing
Rasterization asks, for each triangle, which pixels does this cover. Raytracing asks, for each pixel, what does this pixel see — then asks it again from wherever the answer landed. A loop over geometry versus a loop over pixels — and because the pixel loop can always fire another ray, reflections, refraction and accurate shadows fall out almost for free. So does the cost.
The ray and the intersection
A ray is P = origin + t·direction; every intersection test solves for the smallest positive t where P satisfies the shape's equation.
Substitute the ray into a sphere's |P − C|² = r² and you get a quadratic in t; the discriminant answers before you solve it — negative is a miss, zero a tangent graze, positive two roots, entry and exit. A plane is one division: t = ((P₀ − O)·N) / (D·N), undefined when the ray runs parallel. Ray–triangle is normally Möller–Trumbore, which returns t and the barycentric weights 03-rasterization-and-depth.md describes, free with the hit.
Two bugs if skipped: take the nearest positive t, not the first found, or geometry sorts itself by array order; and start secondary rays at t = ε, offset along the normal, or a surface re-hits itself and speckles black.
Secondary rays are the point
Every effect is just another ray cast from the hit point.
A shadow ray runs from the hit toward the light and asks a boolean — is anything in between? Cheaper than a nearest-hit query: any hit closer than the light ends it. A reflection ray is R = D − 2(D·N)N, mirrored about the normal. Refraction bends instead, by Snell's law sin θ₁ / sin θ₂ = n₂ / n₁, with the index of refraction per material (water 1.33, glass ≈ 1.5). Each spawns more, so recursion depth is the knob — and must be bounded, or two facing mirrors never return.
Whitted vs path tracing
Classic raytracing follows one perfect ray per bounce; path tracing follows many random ones and averages them.
Whitted-style gives hard shadows and mirror surfaces, because a point light and a perfect mirror each have exactly one direction to follow. Reality scatters. Path tracing fires many rays per pixel in random directions — Monte Carlo: estimating an integral by averaging random samples — and that is what gives soft shadows, glossy reflections and indirect bounce light. The price is noise, falling only as √N: four times the rays for half the grain. Hence denoisers.
Cost, and why a BVH is not optional
Cost is rays per pixel × bounces × intersection tests, and only the last factor is yours to fix.
Every ray against every triangle is hopeless, so a BVH — the structure in 13-spatial-partitioning.md — turns it into roughly O(log n): a prerequisite, not an optimisation. Hardware RT cores are BVH traversal and triangle intersection in silicon. And real games go hybrid: rasterize primary visibility, raytrace only shadows, reflections or ambient occlusion at reduced resolution, then denoise.
See 15-raymarching.md for finding a hit by stepping along the ray, when a shape has no closed-form solution.
Demo
Rays, bounces and the image they make — drag the eye and the light, raise max bounces to see the recursion a 3D render hides, then switch to jittered sampling for soft shadows and the noise they cost.
Gotchas
| Symptom | Cause |
|---|---|
| Black speckles over lit surfaces | Secondary rays starting at t = 0 and re-hitting their own surface; needs an epsilon or a normal offset |
| Objects showing through each other | Accepting the first hit found instead of the smallest positive t |
| Geometry behind the camera appears | Negative t accepted; only t > 0 is in front of the origin |
| Mirror corridor hangs or blows the stack | Recursion depth unbounded |
| Reflections mirror-flat whatever the material | Whitted single reflection ray; glossy needs scattered samples |
| Image clean but soft shadows are grainy | Too few samples; noise falls as √N, so denoise rather than brute-force |
| Frame time explodes entering a dense area | No acceleration structure, or a BVH refit so long its quality has decayed |
| Edges hard and jagged | One ray per pixel is a point sample; jitter within the pixel and average |
| Glass looks like coloured plastic | Refraction ray never spawned, or total internal reflection unhandled |