In Part 3, we wrote our direct lighting function, but now we want to render images with full global illumination. Much of the visual richness in rendering comes from indirect lighting effects.
For Part 4, your job will be to implement the functionat_least_one_bounce_radiance
and finish the function est_radiance_global_illumination
in src/pathtracer/pathtracer.cpp.
- The function
est_radiance_global_illumination
is called to get an estimate of the total radiance with global illumination arriving at a point from a particular direction (e.g. on the image plane and going towards the image's center of projection). At the end of this part, this function will actually implement full global illumination. - The function
at_least_one_bounce_radiance
is the main implementation work for this Part 4. At a high level, it should call theone_bounce_radiance
function, and then recursively call itself to estimate the higher bounces. This recursive call should take one random sample of a direction based on the BSDF at the hit point, trace a ray in that sample direction, and recursively call itself on the new hit point.
At a high level...
When we first enter this function, a ray left the camera and intersected the scene. We want to trace multi-bounce (inverse) paths, so we need to figure out where to go next.
Recall that this next location is actually where the light came from in order to arrive at the original intersection point. In direct lighting, we only cared if this next location was a light -- but now it could be anything!
If the next location is an object, then we need to estimate how much light arrived at that location -- and therein lies the recursion.
Make sure you take a look at the pseudocode from the lecture slides on global illumination.
Details: how do we stop infinite recursion?
Recall that our goal is to integrate over all paths of all lengths -- but this is computationally infeasible.
If energy dissipates, the contribution of higher bounces decreases exponentially.
- Russian Roulette provides us an unbiased method of random termination.
- In theory, the probability of terminating can be arbitrary -- but we suggest using a continuation probability between 0.6 or 0.7
- Remember that depending on the value you choose, your renders may look slightly different from ours or your peers -- but are the same in expectation.
- The
max_ray_depth
field tells you how many maximum bounces your ray should take. If it is> 1
, then indirect illumination is "turned on" and we will always trace at least one indirect bounce (regardless of Russian Roulette). - If the new ray doesn't intersect the scene, then it doesn't bounce off anything, and can't recurse.
Code that will be useful
As in part 3, the starter code gives you:
o2w
andw2o
, matrices for object-to-world space and world-to-object space transformations, respectively.- These were created using the input
isect
's normal to calculate a local coordinate space for the object hit point. In this local space, the normal is , so that is "up". - Note that these are each the transpose and inverse of the other (they're orthonormal!)
- These were created using the input
hit_p
, the hit point of the ray, for convenience (note which coordinate space this is in).w_out
, the outgoing direction in the local object frame.
Other functions that you will probably want to call:
coin_flip(double p)
, from random_util.h- This function returns
true
with probabilityp
- This function returns
BSDF::sample_f(Vector3D& w_out, Vector3D* w_in, float* pdf)
- Recall from Part 3: This function requests the outgoing radiance direction
w_out
and returns the BSDF value as a Spectrum as well as 2 values by pointer. The values returned by pointer are- the probabilistically sampled
w_in
unit vector giving the incoming radiance direction (note that unlike the direction returned bysample_L
, thisw_in
vector is in the object coordinate frame!) and - a
pdf
float giving the probability density function evaluated at the returnw_in
direction.
- the probabilistically sampled
- Recall from Part 3: This function requests the outgoing radiance direction
BVHAccel::intersect(Ray&, Intersection*)
at_least_one_bounce_radiance(Ray&, Intersection&)
-- because of recursion!- EPS_F, the epsilon offset
Notes
- For the ray depth cutoff to work, you should initialize your camera rays' depths as
max_ray_depth
inraytrace_pixel
, and modify this field with each recursive call. - Remember that when generating rays originating from an existing hit point, offset the range of valid intersections with the value
EPS_F
. - Don't forget all the multiplicative and normalization factors, as in Part 3
- Here, we additionally need to normalize by the continuation probability!
Nice work!
You still can only render diffuse BSDFs until completing the first part of Project 3-2, however, you should now see some nice color bleeding in Lambertian scenes. You should be able to correctly render images such as
./pathtracer -t 8 -s 64 -l 16 -m 5 -r 480 360 -f spheres.png ../dae/sky/CBspheres_lambertian.dae
(The last command takes 59 seconds for the reference solution on Hive)
Remember you will be running experiments for the write-up -- since the renders will be taking some time, it's a good idea to start rendering now!