Volumetric Rendering
Once again, I first started by reading the relevant chapters (11 and 14) of the PBRT book. The volumetric rendering was by far the most difficult feature to implement, since I was introduced to a lot of new concepts. After some time working through the PBRT chapters, I actually found it easier to follow the slides of Jan Novák for the course Monte Carlo Methods for Physically Based Volume Rendering which basically deal with the same topics as the PBRT book, but which I nonetheless found more approachable. I also read the relevant section of Ray Tracing – The Next Week, which my implementation FakeVolume is based on. This implementation was also used for the final submission. It uses a combination of a shape and a BSDF to "fake" the volume using the normal pathtracing integrator without the need to introduce a separate Medium class with a new integrator.
After that, I felt comfortable enough with the material to get back into PBRT again and started working through the chapters once more. This time, I implemented a Medium class and a SimpleVolumeIntegrator based on the implementation from PBRT. Renders using this integrator can be seen below. Implementing the homogenous volume was then relatively straightforward, but I got stuck once again when I tried to implement the heterogeneous volume: I wanted to use the NanoVDB library to be able to render voxel-based volumes, but NanoVDB uses a different file format than the regular OpenVDB files that are generated by Blender or found online. Following the examples provided in the documentation, I tried to write a script to convert the file formats, but I did not manage to get it to compile. I suppose this was because the OpenVDB libraries provided by my distribution were outdated. Attempts to compile OpenVDB from scratch also failed. In the end, I found a Debian package able to convert the file formats and set up a Docker container to be able to use it.
Now that I had actual NanoVDB files, I continued my implementation of the heterogeneous volume. In doing so, I tried my best to avoid any avoidable optimizations done by PBRT that only complicate the code but were probably overkill for my use case.
For time reasons, I was not able to finish the work and produce an integrator that can be used on all kinds of scenes. I am able to render both homogeneous and heterogeneous volumes, but only using the SimpleVolumeIntegrator which does not support reflective BSDFs and thus would not have been able to render the image I needed for my final submission. My implementation of heterogeneous media has its flaws, since it is very slow and fails to render two media where one is in front of the other, but it nonetheless produced the image below.
As noted before, the final submission used the regular Pathtracer and the FakeVolume to render the sandstorm. To achieve a look similar to a heterogeneous medium, I stacked three versions of the sandstorm volume inside each other, with different density and color.

The left image shows two homogeneous volumes (the one in the background has an emission), the right image shows a heterogeneous volume read from a converted OpenVDB file.
Bunny cloud model courtesy of the OpenVDB project.