\[ \def\bold#1{\boldsymbol{ #1} } \]

Overview

This assignment marks the culmination of the previous sequence of homeworks. You will get to build a path tracer that accounts for global illumination to render realistic images featuring interreflection between objects with advanced reflectance models that account for surface roughness. Your implementation will rely on two distinct sampling strategies and combine them using multiple importance sampling (MIS) to give preference to each strategy where it performs best.

As usual, begin by importing the latest base code updates into your repository by running

$ git pull upstream main

If there were any concurrent changes to the same file, you may have to perform a merge (see the git tutorials under "Preliminaries" for more information).

Part 1: Microfacet BRDF (30 points)

The Ajax bust rendered with a relatively smooth (\(\alpha=0.08\)) microfacet BRDF.
The Ajax bust rendered with a relatively rough (\(\alpha=0.28\)) microfacet BRDF.

In this part we will extend the rudimentary implementation in microfacet.cpp into a full-fledge reflectance model based on the microfacet theory presented in class. This utilizes the sampling techniques for the Beckmann distribution that you implemented in a previous assignment. The process is split into two parts:

Part 1.1: Evaluating the Microfacet BRDF (15 pts)

The Microfacet BRDF in src/microfacet.cpp will be used to simulate plastic-like materials. It consists of a linear blend between a diffuse BRDF (to simulate a potentially colored reflection from the interior of the material) and a rough dielectric microfacet BRDF (to simulate a non-colored specular reflection from the rough boundary). Implement Microfacet::eval() which evaluates the described microfacet BRDF for a given pair of directions in the local shading coordinate frame: \[ f_r(\bold{\omega_i},\bold{\omega_o}) = \frac{k_d}{\pi} + {k_s} \frac{D(\bold{\omega_{h}})~ F\left({(\bold{\omega_h} \cdot \bold{\omega_i})}, \eta_{e},\eta_{i}\right)~ G(\bold{\omega_i},\bold{\omega_o},\bold{\omega_{h}})}{4 \cos{\theta_i} \cos{\theta_o}\cos\theta_h}, ~~ \bold{\omega_{h}} = \frac{\left(\bold{\omega_i} + \bold{\omega_o}\right)}{\left|\left|\bold{\omega_i} + \bold{\omega_o}\right|\right|_2} \] Here, \(k_d \in [0,1]^3\) is the RGB diffuse reflection coefficient, \(k_s = 1 - \max(k_d)\), \(F\) is the Fresnel reflection coefficient (check common.cpp), \(\eta_e\) is the exterior index of refraction and \(\eta_i\) is the interior index of refraction. The various \(\cos\theta_k\) cosine factors relate to the angle that the corresponding direction \(\bold{\omega_k}\) makes with the Z axis in the local coordinate system. The shadowing term uses the rational function approximation defined in class: \[ G(\bold{\omega_i},\bold{\omega_o},\bold{\omega_{h}}) = G_1(\bold{\omega_i},\bold{\omega_{h}})~G_1(\bold{\omega_o},\bold{\omega_{h}}), \] \[ G_1(\bold{\omega_v},\bold{\omega_h}) = \chi^+\left(\frac{\bold{\omega_v}\cdot\bold{\omega_h}}{\bold{\omega_v}\cdot\bold{n}}\right) \begin{cases} \frac{3.535b+2.181b^2}{1+2.276b+2.577b^2}, & b \lt 1.6, \\ 1, & \text{otherwise}, \end{cases} \\ b = (\alpha \tan{\theta_v})^{-1}, ~~ \chi^+(c) = \begin{cases} 1, & c > 0, \\ 0, & c \le 0, \end{cases} \\ \] where \(\theta_v\) is the angle between the surface normal \(\bold{n}\) and the \(\omega_v\) argument of \(G_1\).

Recall the Beckmann distribution from Assignment 3, which is used to model the probability density of normals on a random rough surface: \[ D(\theta, \phi) = \underbrace{\frac{1}{2\pi}}_{\text{azimuthal part}}\ \cdot\ \underbrace{\frac{2 e^{\frac{-\tan^2{\theta}}{\alpha^2}}}{\alpha^2 \cos^3 \theta}}_{\text{longitudinal part}}\!\!\!. \] Note that this definition is slightly different from the one shown in class: the density includes an extra cosine factor for normalization purposes so that it integrates to one over the hemisphere.

Part 1.2: Sampling the Microfacet BRDF (15 pts)

In this part you will generate samples according to the following density function: \[ k_s ~ D(\omega_h) ~ J_h + (1-k_s) \frac{\cos{\theta_o}}{\pi} \] where \(\omega_o\) is sampled and \(J_h = (4 (\omega_h \cdot \omega_o))^{-1}\) is the Jacobian of the half direction mapping discussed in class. This can be done using the following sequence of steps:

  1. Decide between a diffuse or a specular reflection by comparing a uniform variate \(\xi_1\) against \(k_s\)
  2. Scale and potentially offset the uniform variate \(\xi_1\) so that it can be reused for a later sampling step (see DiscretePDF::sampleReuse)
  3. In the diffuse case, generate a cosine-weighted direction on the sphere following the approach in src/diffuse.cpp
  4. In the specular case:
    1. Sample a normal from the Beckmann distribution using Warp::squareToBeckmann that you previously implemented in Assignment 3.
    2. Reflect the incident direction using this normal to generate an outgoing direction.

Note that you will need to implement both Microfacet::sample() and Microfacet::pdf() to be able to run the following tests.

Validation

To obtain the full set of points for the previous exercises, you will need to validate the correctness of your code.

  1. Pass the following statistical tests in scenes/pa4/tests:
  2. The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.

    The warptestGUI also contains a \(\chi^2\) test for the BRDF model, but this is just to facilitate debugging and visualization; the XML files are the real validation benchmark. Mention in your report if running these tests produces any errors.


  3. Use your whitted integrator from the previous assignment to render a microfacet Ajax bust and ensure that you can match our references for the following scenes in scenes/pa4:

Make sure that you include comparisons against our reference renderings and screenshots of statistical tests in the report. This will be graded.

Part 2: path_mats Brute force path tracer (15 points)

The Cornell box rendered using the brute-force path tracer. Note the indirect illumination on the ceiling and the light-focusing behavior of the sphere.
The Veach material test scene, rendered with brute-force path tracing.

In the lecture you saw that there are multiple ways to solve the rendering equation and the emitter sampling strategy implemented in Assignment 3 was just one possibility. A much simpler (but also more naive) version of path tracing relies on hitting light sources in the scene by pure chance instead of explicitly sampling points on them during integration. In other words, you exchange emitter sampling with sampling outgoing directions according to the BSDF.
Furthermore, we will extend the Whitted-style algorithm into a full path tracer that accounts for indirect illumination as well. The main differences from src/whitted.cpp are:

  1. We (temporarily) removed emitter sampling.
  2. Paths should now continue not only after specular surface interactions, but for all interactions.

Create a new "material sampling" (mats) path tracer named src/path_mats.cpp that does exactly this. Note that you should use BSDF::sample() to generate new outoing directions at each scattering event. This outgoing direction is used to estimate the indirect illumination component. We recommend that you implement all integrators in this homework using an "iterative" approach with a for-loop instead of recursion that you might have used for the Whitted-style integrator. This is more efficient and will also be considerably easier to debug.

Note: to match the reference renders exactly, you will need to use the following Russian Roulette heuristic for path termination:

continuation probability = min(maximum component of the throughput * eta^2, 0.99)
where eta is the product of all \(\eta\) terms encountered along the path, starting with eta = 1.f. Additionally, we recommend to only start doing Russian Roulette after at least three bounces in order to avoid terminating very short paths which would lead to a lot of variance.

Validation

  1. Render the following scenes in scenes/pa4:

  2. Pass the following statistical tests in scenes/pa4/tests:
  3. The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.

Make sure to discuss the design choices and relevant technical information about your implementation in the report and include comparisons against our reference renderings.

Part 3: path_ems Path tracer with next event estimation (25 points)

The Cornell box rendered using the Next Event Estimation path tracer.
The Veach material test scene. Notice the significant variance for the top bar (shiniest) reflecting the largest light source. The path tracer with multiple importance sampling will address this issue.

In part 2, you implemented a brute-force path tracer. As you may have noticed, it suffers from high variance due to its naive sampling strategy. We'll now implement an "emitter sampling" (ems) path tracer named src/path_ems.cpp (this corresponds to the "Path Tracing with explicit shadow rays" discussed during the lecture). The last part of the assignment will involve adding multiple importance sampling (MIS) to create a full-featured path tracer that combines both sampling strategies to reduce variance.
Starting from path_mats, simply add back emitter sampling while taking care of the "double counting" problem discussed in the lecture.

Note: Different (correct) implementations of emitter sampling will lead to potentially very different noise patterns in the test scenes. If you want to match the reference renders exactly, you will need to (in each iteration) 1) Pick one light source uniformly at random, and 2) Sample one point on the associated mesh uniformly over its surface area.

Validation

  1. Render the following scenes in scenes/pa4:
  2. The first scene only uses diffuse and specular materials and can be used to test your path tracer if you didn't do part 1 of this assignment yet. The latter two assume that the microfacet model BRDF is ready.


  3. Pass the following statistical tests in scenes/pa4/tests:
  4. The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.

Make sure to discuss the design choices and relevant technical information about your implementation in the report and include comparisons against our reference renderings.

Part 4: path_mis Path tracer with Multiple Importance Sampling (30 points)

Table scene featuring a water-filled glass and a bowl modeled using a microfacet material.
The Veach material test scene, now rendered using MIS.

For this last part you will combine both sampling strategies from the previous tasks into one integrator src/path_mis.cpp that has multiple importance sampling:

  1. When generating a sample on a light source, determine the density of this sampling strategy. Also compute the density (using BSDF::pdf()) with which the BRDF sampling strategy would hypothetically have sampled the same direction.
  2. Weight the contribution of the light source sample using the following formula known as the balance heuristic: \[ w_\mathrm{Light}(p_\mathrm{Light}, p_\mathrm{BRDF}) = \frac{p_\mathrm{Light}}{p_\mathrm{Light} + p_\mathrm{BRDF}}. \] Remember that this only makes sense if both probabilities are expressed in the same measure (i.e. with respect to solid angles or unit area). This means that you will have convert one of them to the measure of the other (which one doesn't matter).
  3. When generating a BRDF sample (which would normally only be used to estimate the indirect illumination component), check if it hits a light source. In this case, also use this sample to estimate the direct illumination component at the current vertex.
  4. Once more, estimate the probability with which light source sampling would hypothetically have sampled this point, and weight the contribution of the sample using the balance heuristic: \[ w_\mathrm{BRDF}(p_\mathrm{Light}, p_\mathrm{BRDF}) = \frac{p_\mathrm{BRDF}}{p_\mathrm{Light} + p_\mathrm{BRDF}}. \] Note the changed numerator in the above expression.

Validation

  1. Render the following scenes in scenes/pa4:

  2. Pass the following statistical tests in scenes/pa4/tests:
  3. The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.

Make sure to discuss the design choices and relevant technical information about your implementation in the report and include comparisons against our reference renderings.