Shadows, Reflection, and Refraction
Secondary rays are where a ray tracer begins to look like a ray tracer. They are also where self-intersection noise appears if bias is handled carelessly.
After a hit you may cast:
- a shadow ray toward a light
- a reflection ray
- a refraction ray
All of them reuse the same intersection code. That reuse is the architectural point.
Shadow, reflection, and refraction rays leave the hit with a small geometric bias.
Shadow rays
origin = p + n_geo * bias
dir = normalize(light - origin)
t_max = distance(light, origin) - bias
occluded = any_hit(origin, dir, t_min, t_max)
Offset along the geometric normal, not a noisy shading normal. Shading normals can point into the mesh and bury the ray. For binary visibility, any-hit is enough. Closest hit is wasted work.
Reflection
wi = reflect(ray.direction, n) // obey your vector convention
origin = p + n_geo * bias
color += weight * trace(origin, wi, depth + 1)
Mirrors are the Dirac case: one direction, no lobe sampling. Glossy reflection needs a sampled direction and a PDF. The bias rule does not change.
Refraction and Fresnel
Snell's law relates angle sines through the index ratio η = ior_in / ior_out. Inside a medium the ratio flips. If the transmitted sine would exceed one, total internal reflection occurs: no refraction ray exists, only reflection.
Fresnel reflectance distributes energy between reflection and transmission. Schlick's approximation is acceptable in a teaching tracer:
R0 = ((1 - ior)/(1 + ior))²
F = R0 + (1 - R0) (1 - cosθ)⁵
Either cast both paths with weights, or randomly choose reflection with probability F and refraction with probability 1 - F and divide by that probability. Dishonest weights bias the image.
Bias and acne
Too little bias produces shadow acne and reflected self-hits. Too much bias opens light leaks at contact. Start near 1e-4 in unit-scale scenes and revise after close-ups. Transmission rays often bias opposite reflection rays.
Nested media
Two glass objects require a medium stack or at least a current IOR. Entering pushes a medium. Leaving restores the previous one. Without that bookkeeping, a ray through a window into a solid glass object uses the wrong η on exit and the interior collapses.
Colored glass may multiply throughput by a distance-dependent absorption factor inside the medium. Thin windows often skip absorption and model a single interface.
Soft shadows
A spherical light with many shadow rays is already Monte Carlo integration. One sample yields a hard edge. Many samples yield a noisy penumbra. The estimator identity is the same as in the sampling chapter. Stabilize hard shadows before pursuing soft ones.
Common errors
- Offsetting along a smoothed normal that dives into the surface
- Leaving
t_min = 0on secondary rays - Forgetting to swap IOR when the ray is inside
- Ignoring total internal reflection
- Applying Fresnel twice in lobe and recursion
When secondary rays misbehave, color the first bounce by ray type. The ridiculous image usually names the bug in one glance.
Energy bookkeeping
Reflection and refraction split energy. If both paths are traced, weights must sum in a way consistent with Fresnel. If a single path is chosen stochastically, divide by the selection probability. Students often implement the random choice and forget the division. The result is a darker dielectric that “looks okay” until compared with a reference.
Shadow terminator note
Low-polygon smooth shading can self-shadow near the terminator because geometric and shading normals disagree. Bias helps. So does not shadowing with a shading normal that points away from the light while the geometry still faces it. Be explicit about which normal each test uses.