layout: default title: (Task 4) Path Tracing permalink: /pathtracer/path_tracing parent: "A3: Pathtracer" usemathjax: true


(Task 4) Path Tracing

Up to this point, your renderer has only computed object visibility using ray tracing. Now, we will simulate the complicated paths that light can take throughout the scene, bouncing off many surfaces before eventually reaching the camera. Simulating this multi-bounce light is referred to as global illumination, and it is critical for producing realistic images, especially when specular surfaces are present. Note that all functions in bsdf.cpp are in local space to the surface with respect to the ray intersection point, while functions in pathtracer.cpp are generally in world space.


Step 1: Pathtracer::trace

Pathtracer::trace is the function responsible for coordinating the path tracing procedure. We've given you code to intersect a ray with the scene and collect information about the surface intersection necessary for computing the lighting at that point. You should read this function and remove the early-out that colors surfaces based on their normal direction.

Step 2: BSDF_Lambertian

Implement BSDF_Lambertian::scatter, BSDF_Lambertian::evaluate, and BSDF_Lambertian::pdf. Note that their interfaces are defined in rays/bsdf.h. Task 5 will further discuss sampling BSDFs, so reading ahead may help your understanding.

Note: a variety of sampling functions are provided in student/samplers.cpp. The Lambertian BSDF includes a cosine-weighted hemisphere sampler Samplers::Hemisphere::Cosine sampler (see rays/bsdf.h).

Step 3: Pathtracer::sample_indirect_lighting

In this function, you will estimate light that bounced off at least one other surface before reaching our shading point. This is called indirect lighting.

Step 4: Pathtracer::sample_direct_lighting

Finally, you will estimate light that hit our shading point after being emitted from a light source without any bounces in between. For now, you should use the same sampling procedure as Pathtracer::sample_indirect_lighting, except for using the direct component of incoming light. Note that since we are only interested in light emitted from the first intersection, we can trace a ray with depth = 0.

Note: separately sampling direct lighting might seem silly, as we could have just gotten both direct and indirect lighting from tracing a single BSDF sample. However, separating the components will allow us to improve our direct light sampling algorithm in task 6.


Reference Results

After correctly implementing task 4, your renderer should be able to make a beautifully lit picture of the Cornell Box with Lambertian spheres (cbox_lambertian.dae). Below is a render using 1024 samples per pixel (spp):

cbox_lambertian

cbox_lambertian

Note the time-quality tradeoff here. This image was rendered with a sample rate of 1024 camera rays per pixel and a max ray depth of 8. This will produce a relatively high quality result, but will take quite some time to render. Rendering a fully converged image may take a even longer, so start testing your path tracer early!

Thankfully, runtime will scale (roughly) linearly with the number of samples. Below are the results and runtime of rendering the Lambertian cornell box at 720p on a Ryzen 5950x (max ray depth 8):

cbox_lambertian_timing

cbox_lambertian_timing


Extra Credit

Table of Content