Sheet 1: Recursive Ray Tracing
Exercise: Recursive Ray Tracing (20 points)
So far, you have only traced primary rays from the camera to the objects. Now you will extend the system to support secondary, tertiary, and arbitrarily deep rays for reflection and refraction.
The provided start code already traces primary camera rays, computes intersections, and evaluates the local Phong illumination model at the hit point. Your task in this exercise is to extend that code so that a hit point may spawn additional rays: reflected rays and, when supported by the material, refracted rays. The returned color is then no longer just the local Phong color, but a combination of the local contribution and the recursively traced secondary contributions.
It is important to note that the implementation in this exercise deliberately uses several approximations. These approximations keep the code short, make the control flow easier to understand, and avoid some of the complexity of a physically accurate renderer. The downside is that some design decisions in the implementation are not fully physically correct. In other words, this exercise is not about building a perfect physical light transport simulation, but about adding recursive ray tracing in a simple and manageable way.
Raytraced images of the test scene with recursion depths from 2 to 9.
Implement recursive reflection and refraction for all materials that support it.
Recursion must be terminated at a predefined depth so that tracing always stops after a finite number
of secondary bounces.
At the current hit point X, the final color value CX is computed
by combining local illumination CX,local, reflected
CX,reflected, and transmitted CX,transmitted light.
The factors l, r, and t denote the ratios of local illumination,
reflected light, and transmitted light, respectively:
Cₓ = l · Cₓ,local + r · Cₓ,reflected + t · Cₓ,transmitted
As a first approximation, we make an artificial distinction between three classes of objects in the scene. This simplifies the implementation and makes the system more efficient, but it is not fully physically correct. In reality, reflection and refraction always come as a pair, so this separation is a modeling shortcut rather than a physical truth. For this exercise, however, it is a useful way to keep the recursion logic simple and manageable. This also means that, when you make implementation decisions, you should follow the simplified model used in the code rather than trying to force a fully physical interpretation onto every case.
We therefore distinguish between the following three material types:
- local illumination only (red sphere)
- local illumination + reflection + refraction (blue sphere)
- local illumination + reflection (yellow sphere & ground plane)
To encode this simplified behavior in the objects, each material has an artificial parameter
local. It specifies how much of the final appearance comes from local illumination,
i.e. from the Phong illumination model that is already present in the starter code. The remaining part,
1 - local, is then used for recursive effects. Depending on the chosen material class,
this remainder is assigned either entirely to reflection, or split between reflection and refraction.
So, conceptually, local tells you how much of the surface appearance should stay local,
while 1 - local tells you how much should come from secondary rays.
For materials with local illumination only, there are no secondary rays at all. Therefore
l = 1, r = 0, and t = 0.
For reflective and refractive materials, the non-local part is shared between reflection and
refraction, so 1 - l = r + t.
The ratio of r and t is computed using Fresnel equations,
with Schlick’s approximation.
Here, R₀ denotes the reflectance at normal incidence,
θ is the angle between the incoming ray direction and the surface normal, and
n is the relative refractive index. Under the simplifying assumption that one side of
the interface is always air, compute n from the material IOR, taking care to use the
correct ratio depending on whether the ray is
entering or exiting the object:
R(θ) = R₀ + (1 - R₀)·(1 - cos(θ))⁵
R₀ = ((n - 1)/(n + 1))²
T(θ) = 1 - R(θ)
r = (1 - l) · R(θ)
t = (1 - l) · T(θ)
For example, for reflective but non-refractive materials (e.g., the yellow sphere), use
R₀ = 1 - local, r = R(θ), l = 1 - r, and t = 0.
This is another deliberate simplification: physically, a reflective surface model would usually be
derived differently, but for this exercise it is enough to treat the non-local part as purely
reflective whenever no refraction is supported.
You may assume that:
- one side of the material is always air
- objects do not overlap or intersect
- models are closed (watertight) and all normals point outward
Use the Vec3 class to compute refracted rays.
Note that θ is the angle between the incoming direction vector i and the
surface normal n, so cos(θ) is given by their dot product:
cos(θ) = i · n.
Implementation hint: the starter code already computes the local Phong illumination term in
Scene::traceRay. You should keep that part and extend it by adding recursive reflection,
recursive refraction, and the final combination of local and recursive contributions.
In particular, the missing work is roughly as follows:
- add a recursion stop condition at the beginning of
Scene::traceRay - spawn and trace a reflected secondary ray for materials that support reflection
- spawn and trace a refracted secondary ray for materials that support refraction
- compute the weights
l,r, andtaccording to the material type - combine
localColor, reflected color, and refracted color into the final result - extend the
Materialclass with a helper that computes the reflectivity using Schlick’s approximation
A useful practical hint is that, under the assumptions of this exercise, you can determine whether a ray is entering or leaving an object from the sign of the dot product between ray direction and surface normal. Also, when you spawn a secondary ray, offset its origin by a small epsilon so that it does not immediately intersect the same surface again due to numerical precision errors.
Drag with the left mouse button or tab and drag to rotate the scene. Stop interacting to start the raytracing.