...
RixBxdfFactory
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 Bxdf, you can #include "RixBxdf.h"
and make sure your bxdf factory class implements the required methods inherited from the RixShadingPlugin
interface: Init()
, Finalize()
, Synchronize()
, GetParamTable()
, and CreateInstanceData()
. Generally, there is one shading plugin instance of a RixBxdfFactory per bound RiBxdf
(RIB) request. This instance may be active in multiple threads simultaneously.
Integrators (RixIntegrator
) use RixBxdfFactory
objects by invoking RixBxdfFactory::BeginScatter()
to obtain a RixBxdf.
Because a RixBxdf
is expected to be a lightweight object that may be created many times over the course of the render, RixBxdfFactory
is expected to take advantage of the lightweight instancing services provided by RixShadingPlugin
. In particular, BeginScatter()
is provided a pointer to an instance data that is created by RixBxdfFactory::CreateInstanceData
(), which is called once per shading plugin instance, as defined by the unique set of parameters supplied to the material description. It is expected that the instance data will point to a private cached representation of any expensive setup which depends on the parameters, and BeginScatter()
will reuse this cached representation many times over the course of the render to create RixBxdf
objects.
The RIX_BXDFPLUGINCREATE()
macro defines the CreateRixBxdfFactory()
method, which is called by the renderer to create an instance of the bxdf plugin. Generally, the implementation of this method should simply return a new
allocated copy of your bxdf factory class. Similarly, the RIX_BXDFPLUGINDESTROY()
macro defines the DestroyRixBxdfFactory()
method called by the renderer to delete an instance of the bxdf plugin; a typical implementation of this method is to delete
the passed in bxdf pointer:
Code Block | ||
---|---|---|
| ||
RIX_BXDFPLUGINCREATE { return new MyBxdfFactory(); } RIX_BXDFPLUGINDESTROY { delete ((MyBxdfFactory*)bxdf); } |
RixBxdfFactory::BeginScatter()
As mentioned above, integrators invoke RixBxdfFactory::BeginScatter()
to obtain a RixBxdf
. The renderer's operating model is that the Bxdf that is obtained this way is a closure, with the closure functions being GenerateSample
, EvaluateSample
, and EmitLocal
. The RixBxdfFactory
should stash state in the RixBxdf
object and consider that the RixBxdf
lifetime is under control of the integrator. Generally integrators will attempt to minimize the number of live RixBxdf
objects but may nonetheless require a large number. For this reason, the RixBxdf
instances should attempt to minimize memory consumption and construction / deconstruction costs.
Any Any computations that the Bxdf need in order to efficient evaluate its closure functions should be computed once inside RixBxdfFactory::BeginScatter()
, and then saved in the overridden RixBxdf class. Critically, these computations include upstream evaluation of any pattern networks. Therefore, it is typical for BeginScatter()
to invoke RixShadingContext::EvalParam()
in order to evaluate the relevant bxdf input parameters, and then pass the pointers returned from EvalParam
to the Bxdf constructor. Since Bxdfs also generally require geometric data, or built-in variables, such as the shading normal, geometric normal, and viewing direction, either BeginScatter()
or the Bxdf constructor itself will need to call RixShadingContext::GetBuiltinVar()
function for each such built-in variable.
...
BeginScatter()
is also passed two parameters that can be used as hints to optimize the calculation. RixBXLobeTraits const &lobesWanted
is a description of the Bxdf lobes that the renderer expects to generate or evaluate; this parameter can be used to avoid any computations not necessary for the requested lobes. RixSCShadingMode
will take either the value k_RixSCScatterQuery
, indicating that the factory should construct a Bxdf for scattering on the surface, or k_RixSCVolumeScatterQuery
, indicating that a Bxdf should be constructed for scattering on the inside of a volume.
RixBxdf
Once a RixBxdf
object object is obtained, the integrator may invoke the following methods:
RixBxdf::GenerateSample()
...
- to generate samples of the bxdf function, one sample for each point of the shading context.
RixBxdf::EvaluateSample()
...
- to evaluate the bxdf function
...
- , one direction for each point of the shading context
RixBxdf::EvaluateSamplesAtIndex()
...
- to evaluate the bxdf function
...
- , one-or-many directions
...
RixOpacity
...
- for a given point of the shading context
RixBxdf::
...
EmitLocal()
to
...
- retrieve the bxdf's local emission.
The primary RixBxdf
entry
Execution Model
There is one instance of a RixBxdfFactory
per bound RiBxdf
(RIB) request. This instance may be active in multiple threads simultaneously.
The context for a per-thread execution is signaled by the various methods Begin___()
and End___()
. As a consequence, RixBxdf
objects can be assumed as being used used in a single-threaded context.
The RixBxdfFactory
should stash state in the RixBxdf
object and consider that the RixBxdf
lifetime is under control of the integrator. Generally integrators will attempt to minimize the number of live RixBxdf
objects but may nonetheless require a large number. For this reason, the RixBxdf
instances should attempt to minimize memory consumption and construction / deconstruction costs.
The primary RixBxdf
entry points operate on a collection of shading points (RixShadingContext
) in order to reasonably maximize shading coherency and support SIMD computation. Integrators rely on the the RixBxdf
's ability to generate and evaluate samples across the entire collection of points. Sample evaluation may be performed in an an all-points-one-sample variant using variant using EvaluateSample()
, and a a 1-point-n-samples variant via variant via EvaluateSamplesAtIndex()
. Generation, however, is constrained to to all-points-one-sample. Evaluation typically has different requirements (e.g. for making connections in a bidirectional integrator), whereas generation typically benefits from being performed all points at once.
...
The RixBxdf
methods above are expected to return quantities for each point of the shading
...
context originally given to RixBxdfFactor::
...
BeginScatter()
...
, excepting RixBxdf::EvaluateSamplesAtIndex()
...
, that operates on a single point of the shading context
...
Bxdf parameters and correctness
A bxdf should always provide the three methods RixBxdf::GenerateSample()
, RixBxdf::EvaluateSample()
and RixBxdf::EvaluateSamplesAtIndex()
, evaluating the bxdf function for one or many directions.
The GenerateSample()
function has the following input parameters: transportTrait, lobesWanted, and random number generator. The transportTrait tells the Bxdf the subset of light transport to consider: direct illumination, indirect illumination, or both. lobesWanted specifies what lobes are requested, for example specular reflection, diffuse transmission, etc. The The random number generator should be called to generate well-stratified samples; such samples typically reduce noise and improve convergence compared to using uniform random samples.
The GenerateSample()
function has the following output parameters (results): lobeSampled, direction, weight, forward pdf, reverse pdf, and compTrans. lobeSampled is similar to the input lobesWanted, and specifies which lobe was actually sampled. direction (Ln) is the generated ray direction vectors; these directions must have unit length. weight is a color per sample indicating that sample's weight. The forward pdf should account for light moving from the L to V direction where as the reverse pdf account for the opposite (from V to L). Bxdfs should always provide both pdf values for the integrators to use. compTrans is an optional result which can be used to indicate transmission color; this will be used as alpha in compositing. A bxdf should check that compTrans is not NULL before assigning to it. All results are arrays with one value per sample point.
...
RixOpacity
In certain cases, integrators may also call RixBxdfFactory::BeginOpacity()
to retrieve a RixOpacity
object. BeginOpacity
should be implemented in a similar fashion to BeginScatter()
, except that will be only be invoked by the renderer in narrower constraints: either for presence and opacity. As such, any inputs to the factory that do not affect presence nor opacity need not be evaluated. Furthermore, the RixSCShadingMode
can be examined to further narrow down the inputs; it will take either the value k_RixSCPresenceQuery
or k_RixSCOpacityQuery
.
The renderer will invoke the following methods on RixOpacity
:
RixBxdf::GetPresence()
to evaluate the geometry presence.RixBxdf::GetOpacity()
to evaluate the opacity color.
Additional Considerations
...