Heterogeneous Participating Media (30pts)

Homogeneous Medium

Implementation

Added files:

  • medium.h
  • medium.cpp
  • homogeneous.cpp
  • volpath_mats.cpp

For the implementation of media, I closely followed the approach described in PBRv3. For this, I first had to add an interface medium.h that all media have to be a subclass of. This interface specifies all methods a medium implementation has to offer, the most important ones being Sample() and Tr(), where the first one is responsible for sampling the free path inside a medium and returning an importance weight and the second one to compute the transmittance between two points inside the medium.
Both methods use the formulas described in the lecture but additionally support spectrally varying extinction coefficients, by uniformly sampling the wavelength to use for the distance sampling.

By adding a Medium member to the ray, I ensure that the ray can keep track of the medium it is currently inside of. Its Medium member gets initially set by the camera, to which one can attach a medium via the xml, and later gets updated whenever the ray intersects with a mesh. For this purpose, up to two mediums can be attached to a mesh in the xml, one for inside the mesh and one for outside the mesh. Then, if the ray continues to the inside of the mesh, ray.medium will be updated with the medium on the inside, otherwise with the medium on the outside of the mesh. Having medias as children of shapes also allows them to be placed anywhere inside the scene by just placing the respective mesh. If one would like to have a pure medium boundary, the bsdf for the mesh can be omitted, so the mesh no longer represents a light scattering surface.

Lastly, I also had to add a new integrator volpath_mats.cpp that supports participating media. It is really similar to the path_mats.cpp integrator but additionally checks in each loop iteration, whether the ray is in a medium and if so, it samples a free path by calling the Sample() method from the medium. This will update the throughput by the returned importance weight and if a medium interaction is sampled, the integrator will sample the phase function for a new direction, otherwise it will handle the intersection with a light scattering surface the same way as path_mats.cpp with the additional change that it will skip over null bsdfs (i.e. pure medium boundaries).
I also added support for the environment map emitters that were implemented by Michael.

Some other modifications to the existing code are additions of convenience methods and / or members to the Intersection, common.cpp and scene.h. Particularly for scene.h, I added an IntersectTr() method, that returns whether a ray intersecting with a light scattering surface and the transmittance up to that point.

The xml syntax for specifying a medium is the following (it's similar for cameras, except that you can only specify one medium there):

<mesh> ... <!-- Only add one medium to the mesh and set this boolean to true if one only wants a medium outside the mesh. If omitted, the default value is false. --> <!-- <boolean name="onlyOutsideMedium" value="true"/> --> <!-- medium inside mesh, can be omitted in which case a vaccum is assumed --> <medium type="homogeneous"> <!-- can be omitted, in which case an isotropic phase function will be instantiatet --> <phase type="henyeygreenstein"> <float name="g" value="0" /> </phase> <color name="sigma_a" value="1 1 1" /> <color name="sigma_s" value="2 2 2" /> </medium> <!-- medium outside mesh, can be omitted in which case a vacuum is assumed --> <medium type="homogeneous"> <phase type="henyeygreenstein"> <float name="g" value="0.8" /> </phase> <color name="sigma_a" value="1 1 1" /> <color name="sigma_s" value="0.5 0.5 0.5" /> </medium> ... </mesh>

Validation

I validated my implementation by comparing with Mitsuba as well as varying some medium parameters. One of the meshes used is the Stanford Bunny. Since mitsuba uses a MIS integrator, I use a lot higher samples per pixels for my images and the noise generally looks worse. However, for some scenes, Mitsuba has a lot more fireflies than my implementation.

Stanford Bunny

Nori Bunny Scattering Mitsuba Bunny Scattering Nori Bunny Absorption Mitsuba Bunny Absorption
Nori Bunny Scattering
Mitsuba Bunny Scattering
Nori Bunny Absorption
Mitsuba Bunny Absorption

Nested Media

Nori Nested Mitsuba Nested
Nori Nested
Mitsuba Nested

Colored Spheres with and without dielectric bsdf

Nori Spheres Mitsuba Spheres Nori Spheres Dielectric Mitsuba Spheres Dielectric
Nori Spheres
Mitsuba Spheres
Nori Spheres Dielectric
Mitsuba Spheres Dielectric

Additional Result (Attaching a medium to a camera)

Nori no fog Nori with fog
Nori no fog
Nori with fog




Heterogeneous Medium

Implementation

Added files:

  • heterogeneous.cpp
  • volumegrid.h
  • volumegrid.cpp

For the heterogeneous medium, I closely followed the PBRv3 implementation again. This means that also the heterogeneous medium must implement the medium.h interface. Here, for the Sample() method, I use Delta Tracking as described in the lecture slides and for the Tr() method, I use Ratio Tracking. However, in the case of heterogeneous media, I do not allow for a spectrally varying extinction coefficient anymore (The absorption and scattering coefficient may still be spectrally varying though as long as their sum is uniform over all spectra).

To model the spatially varying extinction coefficient of heterogeneous media, I decided to use the .vol file format that is also used by Mitsuba. This is convenient as this format is relatively simple but I can easily obtain data as Mitsuba provides a .vdb to .vol file converter. To parse the .vol files, I implemented the volumegrid.cpp class. In the constructor, this class parses the .vol file specified in the xml. It offers a key method Density() that for a given point p returns the density of the extinction coefficient at p. The density gets trilinearly interpolated in case p doesn't lie exactly on the voxel grid. The return value of Density() is used by the Delta Tracking and Ratio Tracking algorithms to scale the baseline extinction coefficient passed to the medium in the xml. One key challenge here is that the point passed to the Density() method must be in the medium coordinate system. I solve this by first mapping the mesh that places the medium in the scene to a centered unit cube and then mapping this unit cube to the medium bounding box.

Adding heterogeneous media to the scene can be done analogeously as for homogeneous media - attaching them as children of a mesh object or a camera. But this time, the mesh associated with the medium should be a centered unit cube that was placed in the scene by a series of translations, rotations and scalings. The integrator is exactly the same as for homogeneous media.

The xml syntax for specifying a heterogeneous medium is almost the same as for a homogeneous one, but it additionally takes a filename as a parameter:

<medium type="heterogeneous"> <phase type="henyeygreenstein"> <float name="g" value="0.8" /> </phase> <color name="sigma_a" value="1 1 1" /> <color name="sigma_s" value="0.5 0.5 0.5" /> <string name="density_file" value="./volumes/bunny_cloud.vol" /> </medium>

Validation

I validated my implementation by comparing with Mitsuba as well as varying some medium parameters. I obtained the .vol files by using the converter offered by mitsuba to convert vdb files which can for example be obtained from here. Since mitsuba uses a MIS integrator, I use a lot higher samples per pixels for my images and the noise generally looks worse. However, for scenes with high(er) scattering, Mitsuba has a lot more fireflies than my implementation.

Bunny Cloud Scattering

Nori Mitsuba
Nori
Mitsuba

Bunny Cloud Absorption

Nori Mitsuba
Nori
Mitsuba

Additional Results ($\sigma_s=100$, $\sigma_a=10$ vs. $\sigma_s=150$, $\sigma_a=50$)

Nori Nori
Nori
Nori