Within a volumetric domain, we require a
RixVolumeIntegrator plugin to respond to the global integrator's request for
GetTransmission() results. In the case of
GetNearestHits(), a volume integrator typically executes after a
RixBxdf generates samples for the purposes of indirect rays (i.e. to generate the next hit along the path).
RixIntegrator and Volumes
Let's go into more detail about the interplay between the global
RixIntegrator plugin and local
RixVolumeIntegrators. Consider the diagram above. Two rays fired from the camera hit the same surface at points A and B; this surface has a local volume integrator bound to it. Before the global integrator even considers the presence of the volume or not, the Bxdf bound to the surface generates samples (runs
GenerateSamples) to determine the direction of indirect rays. One of these directions is a reflection. At this point, we introduce the convention that reflection rays are interested in an incident volume to the surface, and the global integrator asks the renderer to begin volume integration on the reflection ray via a call to
BeginIncidentVol() on the shading context.
Note that in this example, we've only discussed a set of two camera rays hitting a volume. Of course, a camera ray batch size is typically much higher, resulting in a set of rays that reflect and a set of rays that refract at the volume boundary; ideally, we would shade these rays in parallel in only two batches and run the volume integrator only once. To do so, the primary integrator can and should sort the rays into subsets, appropriate for delegation to the correct volume integrator.
In this second diagram, suppose that the blue square represents a volume and the red sphere is a diffuse solid object inside the volume. The process of running the volume shader on the interval A-B proceeds as described earlier. The new scenario happens at B: the Bxdf which runs there has no notion of volume shading because it will never allow the possibility of rays transmitting into the red sphere.
We will not go into the full explanation of how transmission is handled internal to the renderer, but as an example of the types of issues that crop up for transmission rays, we will consider this situation of a blue glass sphere lit by a yellow square.
The shader bound to the glass specifically requests cheap, non-physically based shadows for transmission rays. This means that when we are direct lighting the point A, the transmission ray that goes through the glass to the light position at C also intersects the glass at D, but D is not considered to be a refractive event: the transmission ray does not bend. For that ray that went through the glass, the renderer must now consider the opposite volume integrator bound to A (which would likely be created by the glass shader itself), and run its
GetTransmission() method on the interval between A-D to correctly attenuate the transmission of the direct lighting contribution between A and D. Note that if there is an opacity shader bound to the surface, then
GetTransmission() is run on A-D in addition to the opacity shaders at A and D.