This documentation is intended to instruct developers in the authoring of custom light filters. Developers should also consult the
RixLightFilter.h header file for complete details.
Light filters change the color of a light after the light has been sampled.
Binding and linking
Light filters are bound to lights like bxdfs are bound to geometry: they obey attribute scoping and the last one specified wins.
Like lights, light filters are enabled by default. Light filters may be selectively enabled using a group membership similar to light linking and ray tracing subsets. By default, a light filter belongs to a global group and is enabled everywhere. If a filter expresses that it belongs to a named group, it will instead be disabled by default and enabled for only those geometric primitives that subscribe to the group. Light filter groups are specified through a parameter to the light filter and reported to the renderer through the light filter
GetProperty() method (see below). For example:
Will turn on all light filters in group "blockers" for all lights shining on the sphere. By convention, all of the filters shipped with RenderMan use "linkingGroups" as the name of the parameter. Both the "linkingGroups" and "lightfilter:subset" strings may be comma-separated lists of group names. Group name matching is not scoped to a light. Any filter in a matching group on any light active on a given geometric primitive will be enabled.
Implementing the RixLightFilter interface
Light filters implement the RixLightFilter interface found in RixLightFilter.h. RixLightFilter 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 ownlLight filter, you can #include "RixLightFilter.h" and make sure your light filter class implements the required methods inherited from the RixShadingPlugin interface: Init(), Finalize(), Synchronize(), GetParamTable(), and CreateInstanceData(). Additionally, your light filter subclass must implement the three methods of the RixLightFilter interface, Filter(), GetProperty(), and GetRadianceModifier().
Generally, there is one shading plugin instance of a RixLightFilter per bound RiLightFilter (RIB) request. This instance may be active in multiple threads simultaneously.
The RIX_LIGHTFILTERPLUGINCREATE() macro defines the CreateRixLightFilter() function, which is called by the renderer to create an instance of the light filter plugin. Generally, the implementation of this method should simply return a new allocated copy of your light filter class. Similarly, the RIX_LIGHTFILTERPLUGINDESTROY() macro defines the DestroyRixLightFilter() function called by the renderer to delete an instance of the light filter plugin; a typical implementation of this method is to delete the passed in light filter pointer:
Filter is where the work is done.
Like pattern plugins, light filters are supplied a context. The
RixLightFilterContext is a subset of the
and does not support full pattern generation (
EvalParam() is not supported). That capability is under consideration for future releases.
Light filtering happens after lighting services has picked which lights will be sampled for each point in a shading context. The
RixLightFilterContext member variable
numPts is the number of points in the underlying shading context. Light filters are called on a light-by-light basis, and not all of the points in a shading context will be getting a sample from a given light. Some may get zero samples, others may get multiple samples. For this reason, the Filter call is sample-centric: it operates over the samples generated for a given light.
shadingCtxIndex maps a particular sample back to the point on the shading context for which it was generated.
The three lighting arrays
numSamples in length. These are the vector to the light, the distance from the point on the shading context to the sample point on the light, and the pdf of the sample on the light.
The input/output array contribution contains the lighting contribution distributed into some number of diffuse and specular lobes. For each lobe there are
numSamples entries. For instance, to modulate the lighting by a filter that passes only the red channel:
GetProperty() is an extensible API through which the renderer may query the plugin. The list of valid queries are enumerated in
LightFilterProperty. If a plugin can answer the query, it fills in
result and returns
GetProperty should return
k_RixSCInvalidDetail. The example given shows how the plugin returns the unique string for the linking groups to which the light filter belongs.
GetRadianceModifier() returns a single float representing how much the filter modulates the light emitted from a light source. Not all filters attenuate or amplify the light signal; those plugins would return false and not modify
For a detailed discussion of instance data, see lightweight instancing services. When the CreateInstanceData routine is called, the plugin has access to the parameter list of the light filter and can create arbitrary data that is stored by the renderer and supplied as the instanceData pointer during filtering. This should be uniform data that can be accessed by multiple threads simultaneously. Pre-processing the parameter list is an important performance optimization. A light filter will be invoked for every surface-to-light interaction, and in a production-level shot there can be billions of these events.
As an example, below is the CreateInstanceData method for PxrLightFilterCombiner. Like bxdfs, light filters can be referenced in arguments to other light filters. To run multiple light filters, call EvalParam to get pointers to the upstream light filter and it's instance. These are stored and used later during filtering.
Running multiple filters
Like bxdfs, light filters can be parameters to other light filters. The last filter given before an Light call (the root filter in a tree of filters) is responsible for delegating to the filters in its parameter list. Furthermore, light filters can be disabled on a gprim basis. To respect this, it is the responsibility of the filter to call
RixLightFilterContext::IsEnabled(). If the upstream filter is not enabled, don't run it.
IsEnabled() also returns a pointer to the upstream filter's
instanceData, which should be passed to its