## 15.3 Volumetric Light Transport

These sampling building blocks make it possible to implement various light
transport algorithms in participating media. We can now implement the
fragments in the `EstimateDirect()` function from
Section 14.3.1 that handle the cases related to
participating media.

First, after a light has been sampled, if the interaction is a scattering event in participating media, it’s necessary to compute the value of the phase function for the outgoing direction and the incident illumination direction as well as the value of the PDF for sampling that direction for multiple importance sampling. Because we assume that phase functions are sampled perfectly, these values are the same.

The direct lighting calculation needs to take a sample from the phase
function’s distribution. `Sample_p()` provides this capability; as
described earlier, the value it returns gives both the phase function’s
value and the PDF’s.

### 15.3.1 Path Tracing

The `VolPathIntegrator` is a
`SamplerIntegrator` that accounts for scattering and attenuation from
participating media as well as scattering from surfaces. It is defined in the
files `integrators/volpath.h` and
`integrators/volpath.cpp` and has a
general structure that is very similar to the `PathIntegrator`, so here we
will only discuss the differences between those two classes. See
Figures 15.4 and 15.5 for images
rendered with this integrator that show off the importance of accounting for
multiple scattering in participating media.

As a `SamplerIntegrator`, the `VolPathIntegrator`’s
main responsibility is to implement the
`Li()` method. The general structure of its implementation is very
similar to that of `PathIntegrator::Li()`, though with a few small
changes related to participating media.

`ray`with scene and store intersection in

`isect`>>

`maxDepth`was reached>>

At each step in sampling the scattering path, the ray is first intersected with
the surfaces in the scene to find the closest surface intersection, if any.
Next, participating media are accounted for with a call to the
`Medium::Sample()` method, which initializes the provided
`MediumInteraction` if a medium interaction should be the next vertex in
the path. In either case, `Sample()` also returns a factor accounting for
the beam transmittance and sampling PDF to either the surface or medium
interaction.

`VolPathIntegrator`. (

*Scene courtesy “guismo” from blendswap.com.*)

In scenes with very dense scattering media, the effort spent on first
finding surface intersections will often be wasted, as
`Medium::Sample()` will usually generate a medium interaction instead.
For such scenes, a more efficient implementation would be to first sample a
medium interaction, updating the ray’s `tMax` value accordingly before
intersecting the ray with primitives in the scene. In turn, surface
intersection tests would be much more efficient, as the ray to be tested
would often be fairly short. (Further investigating and addressing this
issue is left for Exercise 15.5.)

Depending on whether the sampled interaction for this ray is within participating media or at a point on a surface, one of two fragments handles computing the direct illumination at the point and sampling the next direction.

`maxDepth`was reached>>

Thanks to the fragments defined earlier in this section, the
`UniformSampleOneLight()` function already supports estimating direct
illumination at points in participating media, so we just need to pass the
`MediumInteraction` for the sampled interaction to it. The direction
for the ray leaving the medium interaction is then easily found with a call
to `Sample_p()`.

For scattering from surfaces, the computation performed is almost exactly
the same as the regular `PathIntegrator`, except that attenuation of
radiance from light sources to surface intersection points is incorporated
by calling `VisibilityTester::Tr()` instead of
`VisibilityTester::Unoccluded()` when sampling direct
illumination. Because these differences are minor, we won’t
include the corresponding code here.