Page tree

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Next »

Contents

Introduction

This documentation is intended to instruct developers in the authoring of custom bxdfs (previously referred to as surface shaders). Developers should also consult the RixBxdf.h header file for complete details.

The RixBxdfFactory interface is a subclass of RixShadingPlugin, and defines a shading plugin responsible for creating a RixBxdf object from a shading context (RixShadingContext) and the set of connected patterns (RixPattern).

The RixBxdf interface characterizes the light-scattering behavior (sometimes referred to as material response) for a given point on the surface of an object.

Implementing the RixBxdfFactory Interface

RixBxdfFactory is a subclass of RixShadingPlugin, and therefore shares the same initializationsynchronization, 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().

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:

    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 GenerateSampleEvaluateSample, and EmitLocal. Any computations that the Bxdf needs to efficient evaluate its closure functions should be computed 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 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.

The following code demonstrates a trivial constant Bxdf's implementation of BeginScatter(). Here, the parameter id for the emission color is passed to EvalParam() in order to get a pointer emitColor that is passed to the ConstantBxdf() constructor.

RixBxdf *
PxrConstant::BeginScatter(RixShadingContext const *sCtx,
                          RixBXLobeTraits const &lobesWanted,
                          RixSCShadingMode sm,
                          RtPointer instanceData)
{
    // Get all input data
    RtColorRGB const* emitColor; 
    sCtx->EvalParam(k_emitColor, -1, &emitColor, &m_emitColor, true);
    RixShadingContext::Allocator pool(sCtx);
    void *mem = pool.AllocForBxdf<ConstantBxdf>(1);
    ConstantBxdf *eval = new (mem) ConstantBxdf(sCtx, this, lobesWanted, emitColor);
    return eval;
}


RixBxdf

Once a RixBxdf object is obtained, the integrator may invoke the following methods:

  • RixBxdf::GenerateSample() to generate samples of the bxdf function.
  • RixBxdf::EvaluateSample() and RixBxdf::EvaluateSamplesAtIndex()to evaluate the bxdf function.
  • RixBxdf::EmitLocal() to retrieve the bxdf's local emission.

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, evaluating the bxdf function for one or many directions.

RixOpacity

In certain cases, integrators may also call RixBxdfFactory::BeginOpacity() to retrieve a RixOpacity object, and invoke the following methods:

  • RixBxdf::GetPresence() to evaluate the geometry presence.
  • RixBxdf::GetOpacity() to evaluate the opacity color.

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  RixBxdf's ability to generate and evaluate samples across the entire collection of points.  Sample evaluation may be performed in an all-points-one-sample variant using  EvaluateSample(), and a 1-point-n-samples variant via  EvaluateSamplesAtIndex(). Generation, however, is constrained 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.

  • RixBxdf::GenerateSample() generates one sample for each point of the shading context
  • RixBxdf::EvaluateSample() evaluates one direction for each point of the shading context
  • RixBxdf::EvaluateSamplesAtIndex()  evaluates one-or-many directions for a given point of the shading context

Bxdf parameters and correctness

A bxdf should always provide the three methods RixBxdf::GenerateSample()RixBxdf::EvaluateSample() and RixBxdf::EvaluateSamplesAtIndex().

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 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.

As an example, a purely Lambertian diffuse bxdf should loop over the sample points and for each sample point set the result values as follows: set lobeSampled to diffuse reflection, set the reflection direction to a randomly generated direction chosen with a cosine distribution on the hemisphere defined by the surface normal (using a well-stratified 2D "random" sample value generated by calling the provided random number generator), set the weight to the diffuse surface albedo color times dot(Nn, L) divided by pi, set the forward pdf to dot(Nn, Ln) divided by pi, and set the reverse pdf to dot(Nn, Vn). More details can be found in the source code for the PxrDiffuse bxdf.

The parameters to the EvaluateSample() function are very similar: transportTrait, lobesWanted, random number generator, lobesEvaluated, direction, weight, forward pdf and reverse pdf.  The main difference is that direction (Ln) is an array of inputs that the function should compute weights and pdfs for.

The EvaluateSamplesAtIndex() function is very similar to EvaluateSample(), but is used to evaluate multiple samples at the same surface position and normal, but with different illumination directions (Ln). The inputs are the same as for EvaluateSample(), except that it has two additional inputs: index and number of samples. index is used to index into built-in variables such as the normal Nn and viewing direction Vn, and number of samples is the number of directions (Ln) to evaluate the bxdf for. The functionality of EvaluateSamplesAtIndex()–  the former only exists to make sample evaluation more efficient in certain light transport settings.

Bxdfs that do not scatter light (e.g. PxrConstant) should disable all lobes and set the forward pdf and reverse pdf to zero.

In order to maintain physical correctness, bxdfs are expected to conserve energy and obey the Helmholtz reciprocity. Care should be taken so that RixBxdf::GenerateSample()RixBxdf::EvaluateSample() and RixBxdf::EvaluateSamplesAtIndex() return consistent results. This allows bxdf plugins to be compatible with different rendering techniques such as:

Additional Considerations

Bxdf Evaluation Domain

Bxdf Lobes

Bxdf Sample Validity

Non-Opaque Surfaces

Alpha for Compositing

Installation

RenderMan will search for bxdf plugins on demand, under the rixplugin searchpath. The following rib stream will search for a plugin file named MyDiffuse.so

Bxdf "MyDiffuse" "diffuse1" "color tint" [0.5 0.5 0.5]

Creating a Bxdf args File

If you would like RenderMan for Maya or Katana to recognize your Bxdf and provide a user interface for changing input parameters and connecting output parameters to other nodes, then you will need to create an args file for your bxdf. The args file defines the input and output parameters in XML so that tools like RMS or Katana can easily read them, discover their type, default values, and other information used while creating the user interface for the pattern node. Please consult the Args File Reference for more information.