At first glance, we thought that this homework wouldn't be so difficult. We were able to implement the cosine importance sampling pretty fast after all. However, everything else was a struggle.
Building from the previous homework, we added an importance sampling framework to define sampling strategies (cosine, BRDF, and GGX). We also updated the scene loader and engine to store the importance sampling methods. We implemented cosine importance sampling, BRDF importance sampling, and GGX/microfacet sampling in the path tracer integrator.
For cosine importance sampling, we first created a function to generate a direction in a local coordinate space from a cosine-weighted distribution. Then, we transformed this local vector into the world space using another function to construct an orthogonal basis. Then, we divided the contribution of the sampled ray by its PDF to maintain an unbiased result.
For BRDF importance sampling, we implemented a dual-sampling strategy for both Phong and GGX materials. We first calculated a selection probability based on the ratio of diffuse to specular reflectivity to decide which lobe to sample. For the specular lobe in GGX, we sampled a half-vector according to the microfacet distribution and reflected the view vector across it, while for Phong, we sampled a power-cosine distribution around the reflection vector. Then, we calculated the combined PDF as a weighted sum of the diffuse and specular probabilities to properly weight the recursive ray.
For Next Event Estimation (NEE), we sampled the area lights directly at each bounce to reduce variance. We first picked a random point on each quad light and traced an occlusion ray to verify visibility from the hit point. Then, we calculated the geometry factor and the light's PDF relative to the surface area. Then, we added the light's contribution to the accumulated radiance, ensuring we only counted emission on the "first hit" or the camera ray to avoid double-counting the light source.
For Multiple Importance Sampling (MIS), we combined NEE and BRDF sampling using the Power Heuristic. We first calculated the PDF of the light-sampled direction under the BRDF distribution, and conversely, the PDF of the BRDF-sampled direction under the light’s area distribution. Then, we weighted each contribution such that the sampling technique more suited to the local geometry (e.g., BRDF for sharp reflections, NEE for large area lights) dominated the result. Then, we summed these weighted contributions to produce a low-variance estimate of the direct lighting.
Our biggest challenge was making the BRDF. When we were trying to do multiple importance sampling tests, we found that the mis.test scene consistently produced a black floor for some reason. We suspected that this was due to arccosine going out of bounds or something of this nature. After some debugging, we found the bug within the scene loader. We incorrectly assumed that the BRDF should be "global" (and apply to every material), instead of being applied per geometry.
cornellCosine.test
cornellBRDF.test
ggx.test
mis.test
dragon.test