Date: Thu, 28 Mar 2024 13:57:12 +0000 (UTC) Message-ID: <273267805.7626.1711634232194@ip-10-0-0-233.us-west-2.compute.internal> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_7625_538561407.1711634232188" ------=_Part_7625_538561407.1711634232188 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
This documentation is intended to instruct developers in the authoring o=
f custom bxdfs<=
/a> (previously referred to as surface shaders). Developers =
should also consult the RixBxdf.h
header file for co=
mplete details.
The RixBxdfFactory
interface is a subclass of Ri=
xBxdf
object from a shading context (RixShadingContext
) and the set of =
connected patterns (RixPa=
ttern
).
The RixBxdf
interface characterizes the light=
-scattering behavior (sometimes referred to as material response=
em>) for a given point on the surface of an object.
RixBxdfFactory
RixBxdfFactory=
code> is a subclass of
RixShadingPlugin<=
/a>
, and therefore shares the same initialization, =
synchronization, and parameter table logic as other shadi=
ng plugins. Therefore to start developing your own Bxdf, you can =
#include "RixBxdf.h"
and make sure your bxdf factory clas=
s implements the required methods inherited from the RixShadingP=
lugin
interface: Init()
, Fin=
alize()
, Synchronize()
, GetPa=
ramTable()
, and CreateInstanceData()
.&nb=
sp;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 (RixInt=
egrator
) use RixBxdfFactory
objects by in=
voking RixBxdfFactory::BeginScat=
ter()
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 t=
he lightweight instancing services provided by RixShad=
ingPlugin
. In particular, BeginScatter()
is provid=
ed a pointer to an instance data that is created by RixBxdfFactory::C=
reateInstanceData
(), which is called once per shading plugin ins=
tance, 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 re=
presentation 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 imple=
mentation of this method should simply return a new
=
allocated copy of your bxdf factory class. Similarly, the RIX_=
BXDFPLUGINDESTROY()
macro defines the DestroyRixBxdf=
Factory()
function called by the renderer to delete an ins=
tance of the bxdf plugin; a typical implementation of this function is to&n=
bsp;delete
the passed in bxdf pointer:
RIX_BXDF= PLUGINCREATE { return new MyBxdfFactory(); } RIX_BXDFPLUGINDESTROY { delete ((MyBxdfFactory*)bxdf); }
RixBxdfFactory=
::BeginScatter()
As mentioned above, integrators invoke RixBxdfFactory::=
BeginScatter()
to obtain a RixBxdf
. The renderer's oper=
ating model is that the Bxdf that is obtained this way is a closure, with the closure functions being GenerateSample
, =
EvaluateSample, EvaluateSamplesAtIndex()
, and EmitLocal
. The RixBxdfFactory
should stash state in the <=
code style=3D"letter-spacing: 0.0px;">RixBxdf object and consid=
er that the RixBxdf
 =
;lifetime is under control of the integrator. Generally integrators wi=
ll attempt to minimize the number of live RixBxdf
objects but may nonetheless requ=
ire a large number. For this reason, the RixBxdf
instances should attempt to minimi=
ze memory consumption and construction / deconstruction costs.
Any computations that the Bxdf need in order to efficient evaluate its c=
losure functions should be computed once inside RixBxdfFactory::Begin=
Scatter()
, and then saved in the overridden RixB=
xdf class. Critically, these computations include upstream evaluation of an=
y pattern networks. Therefore, it is typical for BeginScatter()
to invoke <=
/span>RixShadin=
gContext::EvalParam()
&nbs=
p;in order to evaluate the relevant bxdf input parameters, and then pass th=
e pointers returned from EvalParam
to the Bxdf constructo=
r. Since Bxdfs also generally require geometric data, or built-in variables=
, such as the shading normal (Nn) and viewing direction (Vn), either <=
code>BeginScatter() or the Bxdf constructor itself will need to call=
RixShadingContext::GetBuiltinVar()
function for each such built-in variable.=
span>
The following code demonstrates a=
trivial constant Bxdf's implementation of BeginScatter()
=
. Here, the parameter id for the emission color is passed to Eva=
lParam()
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;=20 sCtx->EvalParam(k_emitColor, -1, &emitColor, &m_emitColor, t= rue); RixShadingContext::Allocator pool(sCtx); void *mem =3D pool.AllocForBxdf<ConstantBxdf>(1); ConstantBxdf *eval =3D new (mem) ConstantBxdf(sCtx, this, lobesWanted, = emitColor); return eval; }
In the following code from =
PxrDiffuse
, we demonstrate how its constructor sets up required geom=
etric information that is later on used for sample generation and evaluatio=
n.
PxrD= iffuse(RixShadingContext const *sc, RixBxdfFactory *bx,=20 RixBXLobeTraits const &lobesWanted, RtColorRGB const *emit,=20 RtColorRGB const *diff,=20 RtColorRGB const *trans, RtNormal3 const *bumpNormal) :=20 RixBxdf(sc, bx),=20 m_lobesWanted(lobesWanted), m_emit(emit), m_diffuse(diff), m_transmission(trans), m_bumpNormal(bumpNormal) { RixBXLobeTraits lobes =3D s_reflDiffuseLobeTraits | s_albedoLobeTra= its; if (m_transmission) lobes |=3D s_tranDiffuseLobeTraits; m_lobesWanted &=3D lobes; sc->GetBuiltinVar(RixShadingContext::k_P, &m_P); if(m_bumpNormal) m_Nn =3D bumpNormal; else sc->GetBuiltinVar(RixShadingContext::k_Nn, &m_Nn); sc->GetBuiltinVar(RixShadingContext::k_Ngn, &m_Ngn); sc->GetBuiltinVar(RixShadingContext::k_Tn, &m_Tn); sc->GetBuiltinVar(RixShadingContext::k_Vn, &m_Vn); }
BeginScatter()
=
is passed an instance data pointer created via CreateInstanceData that can be used to cache and reuse certain calculations common amongst=
all factory instances of the same Bxdf; for more information, please consu=
lt the lightweight instancing discussion in
RixShadingPlugin
.
BeginScatter()
=
is also passed two parameters that can be used as hints to optimiz=
e the calculation. RixBXLobeTraits const &lobesWanted
=
is a description of the Bxdf lobes=
a> that the renderer expects to generate or evaluate; this parameter c=
an be used to avoid any computations not necessary for the requested lobes.=
RixSCShadingMode
will take either the value k_RixS=
CScatterQuery
, indicating that the factory should construct a=
Bxdf for scattering on the surface, or k_RixSCVolumeScatterQuer=
y
, indicating that a Bxdf should be constructed for scattering on th=
e inside of a volume.
RixBxdfFactory=
::BeginOpacity()
In certain cases, integrators may also call RixBxdfFactory::B=
eginOpacity()
to retrieve a RixOpacity
o=
bject. BeginOpacity
should be implemented in a simil=
ar fashion to BeginScatter()
, except that will be only be=
invoked by the renderer in narrower constraints: either for presence and o=
pacity. 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 tak=
e either the value k_RixSCPresenceQuery
or
RixBxdfFactor=
y::BeginInterior()
Bxdfs that have interesting volumetric interior properties need to imple=
ment the BeginInterior()
method, as well as an associated=
RixVolumeIntegrator
. Please consult Writing Volume Integrators for more i=
nformation.
Bxdfs that require special opacity handling or support interior shading =
need to indicate their support for these capabilities via an insta=
nce hint. Most Bxdfs do not require such, and should simply imple=
ment in their factory a trivial implementation of GetInstanceHints()<=
/code> which simply returns
k_TriviallyOpaque
. Bxdfs=
that do modulate opacity and/or require interior shading are required to o=
verride the GetInstanceHints()
method and return the appr=
opriate bits in InstanceHints
to the renderer in order to=
trigger calls to BeginOpacity
and BeginInteri=
or
.
As a further optimization, Bxdfs that deal with opacity or interiors may=
choose to change their behavior based upon their instance parameters. For =
example, they may opt out of opacity entirely if they can prove via inspect=
ion of the parameters that the intended result is equivalent to opaque. Bxd=
fs that choose to do so should "bake" this awareness into their instance da=
ta at the time of CreateInstanceData()
and insp=
ect this instance data within the GetInstanceHints()
impl=
ementation.
Note that at the time of CreateInstanceData
, like other sha=
ding plugins, Bxdfs are unable to inspect the values of pattern network inp=
uts; therefore, in cases these inputs are provided (i.e: RixParameter=
List::GetParamInfo()
returns k_RixSCNetworkValue
) =
the Bxdf may have no choice but to defer inspection of the inputs until BeginInterior()
. At that time=
, those methods may then choose to return NULL instead.
RixBxdf
Once a RixBxdf
object is obtained, the integrator=
may invoke the following methods:
RixBxdf::GetEvaluateDomain()
to figure out the domain over=
which the Bxdf evaluate samples;RixBxdf::GenerateSample()
to generate samples of the =
bxdf function, one sample for each point&n=
bsp;of the shading context;RixBxdf::EvaluateSample()
to evaluate the bxdf functi=
on, one direction for each point of t=
he shading context;RixBxdf::EvaluateSamplesAtIndex()
to evaluate the bxdf fun=
ction, one-or-many directions for a given point=
of the shading context;RixBxdf::EmitLocal()
to retrieve the bxdf's local emi=
ssion.The primary RixBxdf
entry points oper=
ate on a collection of shading points (RixShadingContext
) =
;in order to reasonably maximize shading coherency and support SIMD computa=
tion. 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 EvaluateSample=
sAtIndex()
. Generation, however, is constrained to all-poin=
ts-one-sample. Evaluation typically has different requirements (e.g. f=
or making connections in a bidirectional integrator), whereas generation ty=
pically 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&n=
bsp;RixBxdfFactory::BeginScatter()
, excepting RixBx=
df::EvaluateSamplesAtIndex()
, that operates on a single point of the=
shading context, evaluating the bxdf function for one or many directions.<=
/p>
In order to maintain physical correctness, bxdfs are expected to c=
onserve energy and obey the Helmh=
oltz reciprocity principle. Care should be taken so that RixBxdf::GenerateSample()
, R=
ixBxdf::EvaluateSample()
and RixBxdf::E=
valuateSamplesAtIndex()
return consistent results. This a=
llows bxdf plugins to be compatible with different rendering techniques suc=
h as unidirectional path tracing, =
;bidirectional path tracing=
, photon mapping and vertex connection merging (VCM).
The GenerateSample()
function has the following input =
parameters: transportTrait
, lobesWanted
, and rand=
om number generator rng
.
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. rng
should be called to generate well-stratified samp=
les; such samples typically reduce noise and improve convergence compar=
ed to using uniform random samples. The GenerateSample()=
code> function has the following output parameters (results):
lo=
beSampled
, directions
, weights
, forw=
ardPdfs
, reversePdfs
, and compTrans
.  =
;All results are arrays with one value per sample p=
oint.
lobeSampled
is=
similar to the input lobesWanted
, and specifies which lobe was actually sampled. Bxdfs must respect the transportTrait
and lobesWanted
request and indicate which class of lobe is associ=
ated with each sample by setting lobesSampled
for each generated sample. If and only if the bx=
df is unable to generate a requested sample, then lobesSampled=
code> should be marked as invalid by calling SetValid(false=
)
; for example, if the lobesWanted
argument r=
equests a specific lobe (e.g., diffuse reflection) that the Bxdf does =
not support because the Bxdf only supports glossy reflections, then lobesSampled[i].SetValid(false)
should be called. However, =
if it is possible to generate the requested samples, then lobesS=
ampled
should not be marked invalid and should instead be =
set to the sampled lobe for each point in the shading context. =
When generating a Bxdf sample, you can a=
lso run into corner cases (often legitimate cases) where a valid sample can=
not be generated. For instance, if the camera ray hits the backside of a si=
ngle-sided object, a valid sample cannot be generated. Therefore, the lobeSampled
for this camera ray should also be marked as inval=
id in this case by calling SetValid(false)
.
If an invalid sample is returned to the integrator, the integrator will ter=
minate the ray and avoid any further computation. Bxdfs that do not sc=
atter light (e.g. PxrConstant
) should also mark all samples as=
invalid.
There is a subtle difference between = an invalid sample and a black sample. If a camera ray hits a diffuse black = surface, the sample will have zero contribution to the final image despite = being valid. It would make sense for the integrator to terminate the ray be= cause further bounces will not contribute to the final image either, but it= is important to splat the black contribution to both the beauty and alpha = channel. Failing to do so might result in missing geometry in the rendered = image. An invalid sample on the other hand, should not only be terminated b= ut also ignored for splatting purposes.
directions
is =
the generated ray direction vectors; these directions must=
be unit length. weights
is a c=
olor per sample indicating that sample's weight. forwardPdfs
sh=
ould account for light moving from the L to V direction, whereas reve=
rsePdfs
account f=
or the opposite (from V to L). Bxdfs should always provide both pdf values =
for integrators to use. Bxdfs that do not scatter light (e.g. PxrConst=
ant) should set both pdfs to zero.compTrans
is a=
n optional result which can be used to indicate transmission color; this wi=
ll be used as alpha in compositing. A bxdf should check that compTran=
s
is not NULL before assigning to it. As an example, a purely Lambertian diffuse bxdf should loop over the sam=
ple points and for each sample point set the result values as follows: set =
lobeSampled
to diffuse reflection, set the reflection dir=
ection 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 gene=
rator), set the weight to the diffuse surface albedo color times dot(Nn, Ln=
) 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 cod=
e for the PxrDiffuse bxdf.
The parameters to the EvaluateSample()
function a=
re very similar to GenerateSample()
: transportTrait=
, lobesWanted
, rng
, lobesE=
valuated
, directions
, weights
,&n=
bsp;forwardPdfs
and reversePdfs
. =
The main difference is that directions
is an array o=
f inputs that the function should compute weight=
s and pdfs for.
The EvaluateSamplesAtIndex()
function is very similar to EvaluateSample()=
code>, but is used to evaluate multiple samples at the same surface positio=
n and normal, but with different illumination directions (Ln). The inputs a=
re the same as for
and exists in order to make sample evaluation more efficient in cer=
tain light transport settings.EvaluateSample()
, except that it has two ad=
ditional inputs: index
and number of samples numSam=
ples
. index
is used to index into built-in variabl=
es such as the normal Nn and viewing direction Vn, and numSamples is the number of directions (Ln) to evaluate the bxdf for. The fun=
ctionality of
EvaluateSamplesAtIndex()
otherwise is similar to=
EvaluateSample()
,
Bxdfs can help integrators converge more quickly by providing hints abou=
t the domain over which they need to be integrated (the full domain being t=
he entire sphere). This is done by the bxdf implementing a RixBx=
df::GetEvaluateDomain()
function that returns the appropriate&n=
bsp;RixBXEvaluateDomain
value. For more information, plea=
se see Bxdf Evaluati=
on Domain.
RixOpacity
The renderer will invoke the following methods on RixOpacity<=
/code>:
RixBxdf::GetPresence()
to evaluate the geome=
try presence.RixBxdf::GetOpacity()
to evaluate the opacit=
y color.