Page tree

Contents

This documentation is for developers who wish to author custom sample filter plugins. See the RixSampleFilter.h header for complete details.

Sample filter plugins interpose between the integrator plugin and the renderer core’s framebuffer. They may read and write the contents of various AOV channels for the camera ray samples in a region that an integrator has finished shading and that is now on its way to be accumulated into the framebuffer.

Unlike display filters, sample filters do affect deep images produced by the renderer!  They also affect the adaptive sampler.

Sample filters are not required to make any changes to the samples.  Instead, they may choose simply to passively inspect them as they go by.

Control Flow In Detail

When the renderer wishes to render an update to a bucket (a.k.a. tile) of 2D pixels, it first generates a set of camera ray samples and then sets up a small table to hold the shaded data produced by the integrator for the samples.  This table has space for all of the AOV channels associated with the current camera.  (Multi-camera scenarios such as stereo renders have a separate framebuffer for each camera and send rays to the integrator plugin at different times.)

If there is an active sample filter plugin for the scene, the renderer will then invoke that plugin and give it access to the set of shaded results after the integrator has finished producing them.  The filter is then free to read from and write to any of the channels for any of the current batch of camera ray samples within the bucket.  Though there are differences in what they are allowed to do, sample filters generally serve as a sort of mix-in class for the integrators.

Note that the render only directly invokes a single sample filter at a time. Given multiple sample filters defined for a scene, the last one wins. E.g.:

SampleFilter "PxrShadowFilter" "shadowFilter" "string occludedAov" "occluded" "string unoccludedAov" "unoccluded" "string shadowAov" "occluded"
SampleFilter "PxrImagePlaneFilter" "imagePlane" "string filename" "tableTop.tex" "string holdoutShadowAov" "occluded"
SampleFilter "PxrSampleFilterCombiner" "sampleCombiner" "reference samplefilter[2] filter" ["shadowFilter" "imagePlane"]


As shown here, however, a sample filter may be given a reference to another that precedes it. The standard PxrSampleFilterCombiner, for example, simply runs a chain of other sample filters in sequence.

Once the sample filter invoked by the renderer returns, the camera samples in the table owned by the renderer are pixel filtered and accumulated into the corresponding region of the framebuffer for that bucket.  This includes the portion of the framebuffer dedicated handling deep outputs, if any.  The adaptive sampler then updates its state based on the filtered samples.  (This means that, e.g., a sample filter that applies a tonemapping operator can be used to drive the adaptive sampler.)

Implementing the RixDisplayFilter Interface

Sample filters implement the RixSampleFilter interface found in RixSampleFilter.h. A RixSampleFilter is a subclass of RixShadingPlugin, and therefore shares the same initialization, synchronization, and parameter table logic as other shading plugins. Therefore to start developing your own sample filter, you can #include "RixSampleFilter.h" and make sure that your sample filter class implements the required methods inherited from the RixShadingPlugin interface: Init(), Finalize(), Synchronize(), GetParamTable(), and CreateInstanceData(). You should also use the RIX_SAMPLEFILTERCREATE() and RIX_SAMPLEFILTERDESTROY() macros to define the CreateRixSampleFilter() and DestroyRixSampleFilter() functions for creating and destroying instances of your class.

Often, you will want to have your CreateInstanceData() map AOV names from the parameter list to RixChannelID values. These channel ids can be found by inspecting the RixIntegratorEnvironment. E.g.:


RixRenderState* state = reinterpret_cast<RixRenderState*>(ctx.GetRixInterface(k_RixRenderState));
RixRenderState::FrameInfo frame;
state->GetFrameInfo(&frame);
RixIntegratorEnvironment const* env = reinterpret_cast<RixIntegratorEnvironment const*>(
    frame.integratorEnv);

RtUString name("foo");
RixChannelId id = env->GetDisplayChannel(name)->id;


Note that there may be more than one match for a given name. This may be caused by either the the “string source” parameter to a display channel or the particular AOV being output multiple times. A sample filter may use any of the matches.  Changes that it makes to any one of them will affect all the others.  The GetDisplayChannel() method is provided as a convenience to lookup the first channel entry with a matching name.  You may also do this search over the displays yourself.

Finally, your sample filter subclass must implement the main Filter() method of the RixSampleFilter interface. This method may be called simultaneously from multiple threads for the same plugin and data instances and so must be thread-safe and re-entrant.

Defining RixSampleFilter::Filter()

The Filter() method is where the action takes place:


virtual void Filter(
    RixSampleFilterContext& fCtx,
    RtPointer instanceData) = 0;


Each time the renderer wants to render a bucket and accumulate it into the framebuffer it calls the Filter() method with both a RixSampleFilterContext giving access to the bucket data, and with the blind pointer to the instance data that the plugin setup for itself in CreateInstanceData().

Most plugins will immediately cast this later pointer to a pointer or reference to the internal structure that the plugin uses to stash its internal data for this particular plugin instance. Note that at startup, the renderer will typically only create a single instance of the plugin class via a call to the function defined by the RIX_SAMPLEFILTERCREATE() macro. But it may call CreateInstanceData() many times, each with a different parameter list from the scene. Class member data will thus be shared across all invocations while the instanceData pointer passed back to the Filter() will be unique to its invocation for a specific parameter list.

The RixSampleFilterContext currently provides four main things:

Ray data:
The numSamples field gives the number of original primary camera samples produced by the renderer.  The numAllSamples field will be at least this number and may more; it includes any extra samples generated by the integrator to include information about things such volume interactions or thin transparency.  The context also includes screen position and shutter times along with each of the original rays that they are associated with.  The shadeCtxs used by the integrator and BXDFs is also provided here.  Finally, the depths of all samples, including the additional ones are given, along with the indices of the originals that spawned each given additional sample.
Read/write access:
The overloads of the Read() and Write() method allow the plugin to inspect and change the sample data.  Note that with multi-camera rendering not all possible channel ids found in the integrator environment may be valid during a given call to Filter(). The read and write functions will return false when called with a channel that is not valid for the current instance data.
Reading arbitrary regions:
Normally the read functions are only allowed to read from the current batch of samples being processed for the present bucket.  However, for image processing operations that require a neighborhood it may be useful to be able to read the accumulated and pixel filtered pixels from around the bucket. The ReadRegion() offers the ability to fetch a copy of an arbitrary rectangle of pixels from anywhere in the framebuffer (with zero padding for pixels outside the image). Like Read(), it may fail for some channels. Note that due to multi-threading, successive calls to ReadRegion() may produce different results (and different results from the equivalent Read()) as other rendering threads may continue to update the framebuffer and this may produce transitory bucket artifacts if a sample filter does not take care.
Access to other filters:
The IsEnabled() call allows a sample filter plugin to get a pointer to the instance data for another plugin instance and determine if that instance is currently enabled. This allows one Filter() invocation to recursively invoke another filter instance's Filter() function. See the source to the PxrSampleFilterCombiner in the examples bundle for use of this to chain together plugins.