\[ \def\bold#1{\boldsymbol{ #1} } \]
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).
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:
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.
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:
Warp::squareToBeckmann that you previously implemented in Assignment 3.
Note that you will need to implement both Microfacet::sample() and Microfacet::pdf() to be able to run the following tests.
To obtain the full set of points for the previous exercises, you will need to validate the correctness of your code.
scenes/pa4/tests: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.
scenes/pa4:Make sure that you include comparisons against our reference renderings and screenshots of statistical tests in the report. This will be graded.
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:
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.
scenes/pa4:scenes/pa4/tests:The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.
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.
scenes/pa4: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.
scenes/pa4/tests: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.
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:
BSDF::pdf()) with which the BRDF sampling strategy would hypothetically have sampled the same direction.scenes/pa4:scenes/pa4/tests:The tests are also part of the continous integration environment, so note that your build will "fail" as long as not all tests pass.