Sheet 9: Photon Mapping
Exercise: Photon Mapping for Indirect Illumination (25 points)
In the previous exercises you implemented path tracing and radiosity. This exercise uses a third global-illumination method: photon mapping. The renderer first emits photons from the light source and stores them where they hit diffuse surfaces. Camera rays are traced afterwards. When a camera ray hits a diffuse surface, the renderer estimates illumination by looking up nearby stored photons.
The application already creates the scene, emits photons over multiple frames,
displays the photon positions as small points, traces camera rays, and builds a
spatial hash grid for fast photon lookup. Your task is to complete the missing code
in PhotonMapper.cpp, specifically tracePhotonPath and
estimatePhotonRadiance.
A stored photon represents incoming flux at a diffuse surface point. In the code this
is the Photon struct: it stores the hit position, the incoming photon
direction, the surface normal at the hit point, and the photon power.
Task 1: Trace Photon Paths (13 points)
Complete PhotonMapper::tracePhotonPath. This function follows one photon
emitted by the light source through the scene. The function receives the initial
photon origin, direction, and power. The prepared variables ray and
photonPower store the current state of the photon path.
Inside the bounce loop:
- Intersect the current photon ray with the scene using
scene.intersect(ray, false). - Terminate the path if the photon leaves the scene.
- Compute hit position, normal, and material from the intersection.
- Terminate the path if the photon hits emissive geometry.
- For glass, use the Fresnel reflectivity as the probability of reflection. Otherwise refract if possible.
- For mirror-like material, reflect the photon and multiply its power by the specular color.
- For diffuse material, store the photon with
storePhoton. - After a diffuse hit, multiply the photon power by the diffuse color and continue in a random diffuse direction.
- Use
offsetRayOriginwhenever the next ray starts on a surface.
Do not store photons on glass or mirror surfaces. In this simplified implementation they only redirect photons. Diffuse surfaces are the places where the photon map records incoming light.
Task 2: Gather Photons (12 points)
Complete PhotonMapper::estimatePhotonRadiance. This function is called
when a camera ray hits a diffuse surface. It estimates outgoing radiance by summing
nearby photons and dividing by the area of the gather disk:
In the formula, x is the visible surface point, r is
PHOTON_GATHER_RADIUS, ρd is the diffuse
material color, and ΔΦp is the power stored in one
nearby photon. The implementation also multiplies by
PHOTON_RADIANCE_SCALE, a constant chosen to make the small educational
photon map visually useful.
Use the hash grid instead of looping over all photons. Compute the grid-cell range around the hit position, visit all candidate cells, and then apply the precise radius test. Keep only photons that:
- are inside the gather radius,
- arrived from the front side of the visible surface, and
- were stored on a surface with a sufficiently similar normal.
The normal test reduces light leaking across sharp edges, for example from the floor
through a wall corner. After summing the accepted photon powers, multiply the sum by
the diffuse color and divide by pi * r * r.
Reference Solution
The reference solution below shows the expected behavior: photons are emitted from the rectangular area light, bounce through the scene, and are gathered on diffuse surfaces. Mirror and glass surfaces redirect camera rays and photon paths, so they still show indirect illumination from the photon map.
Left mouse drag rotate, R reset rotation