Environment Map Emitter (15 pts)

Implementation

Added files:

  • environment_emitter.cpp
  • environment_emitter_naive.cpp

An environment map emitter surrounds the whole scene and is assumed to be infinitely far away. My environment emitter implements the Emitter interface and loads an EXR image through the Bitmap class. Without importance sampling, the implementation is quite straightforward and can be found in the environment_emitter_naive.cpp file, which was only kept due to validation purposes. In this class, we essentially use Warp::squareToUniformSphere to sample a direction on the sphere, and then spherically map this direction to uv coordinates through the dirToUv function.

Environment lights have to be treated differently than other emitters in the integrators. In case a ray does not intersect any geometry, it will always hit our environment light. For this purpose, I have added a getEnvironmentEmitter function to the scene class. Environment emitter support was added to multiple integrators, notably path_mis, path_mats and volpath_mats.

The tricky part of this feature lies in importance sampling the environment light. We importance sample based on the luminance of each pixel. I have followed the approach described in this paper for implementing this functionality. In summary, we use that $p(u, v) = p(u | v) * p(v)$, which allows us to sample from a 2D distribution by sampling twice from a 1D distribution, namely by first sampling a row through the marginal density, then sampling a column inside that row through the conditional density for the sampled row. We then need to convert our sampled uv vector to a 3D direction, which is the job of the uvToDir function (relies on the already implemented sphericalDirection function). An important aspect that further improves our result is to multiply the luminance with $sin(\theta)$ to account for the equirectangular projection.

Validation

I validated my implementation by comparing with Mitsuba. Here are two scenes consisting of three spheres with a mirror, diffuse and dielectric BSDF (from left to right). These spheres are solely lit by an environment map emitter. Both presented environment maps were obtained from PolyHaven.

Room Environment (256 spp)

Mitsuba 3 Mine

Overcast Outdoor Environment (256 spp)

Mitsuba 3 Mine

Effectiveness of Importance Sampling (256 spp)

I furthermore assessed the effectiveness of my importance sampling by comparing it to simple uniform spherical sampling. Here is a comparison of a implementation using uniform spherical sampling to the implementation using importance sampling, both with 256 samples per pixel. As expected, the variant using importance sampling contains considerably less noise.

Uniform Sampling Importance Sampling

2D Importance Sampling

Next to the visibly reduced noise, I further tested whether I am correctly sampling according to the luminance of the environment map. As I lacked the required safety goggles for adapting the warptest.cpp file, I made my own simplified version of it inside the precompute function of environment_emitter.cpp. Essentially, I am generating two images, one with the luminance (multiplied with the sine term) of the environment map corresponding to our 2D PDF, and one where I am generating a lot of samples and every time I sample a specific pixel, I increase the brightness of this pixel, which essentially generates a 2D histogram. In the following comparison one can see that my sample placement closely matches the luminance of the environment map:

Luminance Sample Histogram