Bxdfs can help integrators converge more quickly by providing hints about the domain over which they need to be integrated (the full domain being the entire sphere). This is done by the bxdf implementing a
RixBxdf::GetEvaluateDomain() function that returns the appropriate
RixBXEvaluateDomain value. This value informs the integrator about the domain on which
RixBxdf::EvaluateSamplesAtIndex() would return non-zero values (i.e. a non-zero material response).
Note that the bxdf is still able to generate discrete samples over the whole spherical domain (through
RixBxdf::GenerateSample()), independently of the hint returned. However, if the bxdf was generating non-discrete samples outside of the one returned by
RixBxdf::GetEvaluateDomain()), this would mean there is an inconsistency between
RixBxdf::EvaluateSamplesAtIndex(), which is not good for correctness.
RixBXEvaluateDomain enum values (as defined in
RixBxdf.h) are detailed below. The entire spherical domain is split into the outside and the inside hemispheres, defined with respect to the geometric normal of the surface (the geometric normal pointing toward the outside). Note that each value is a power of two, which means that the enum acts as a bit field and its values can (should) be OR-ed together.
k_RixBXEmptyDomain = 0
This indicates that either:
- the bxdf doesn't scatter light at all, but may still emit some (through
- the bxdf only generates discrete samples (which by definition can never be evaluated) on the entire domain
k_RixBXOutsideReflect = 1
This indicates that the bxdf represents a surface that only reflects light from the outside. That is:
L lie in the outside hemisphere.
This is useful for closed opaque objects, whose interior will never be seen.
k_RixBXOutsideTransmit = 2
This indicates that the bxdf represents a surface that allows light to flow from the outside toward the inside. That is:
L lies in the outside hemisphere, while
V lies in the inside hemisphere.
Note that if used for a closed object, this would allow light to penetrate inside the object, but not exit it. This would only make sense if the camera lies inside the object.
k_RixBXInsideReflect = 4
This indicates that the bxdf represents a surface that only reflects light from the inside. That is: V and L lie in the inside hemisphere.
k_RixBXInsideTransmit = 8
This indicates that the bxdf represents a surface that allows light to flow from the inside toward the outside. That is:
L lies in the inside hemisphere, while
V lies in the outside hemisphere.
Note that if used for a closed object, this would allow light to exit the object, but not go through it. This would only make sense if a light is placed inside the object.
k_RixBXVolume = 16
This indicates that the bxdf represents a volumetric scattering event. This is different from using all of the above, since in the volumetric case, there is no surface cosine term to consider during integration.
RixBXEvaluateDomain also defines some (composed) values corresponding to the most common cases.
k_RixBXReflect = (k_RixBXInsideReflect | k_RixBXOutsideReflect)
This is useful for open surfaces with both the 'outside' and 'inside' being reflective.
k_RixBXTransmit = (k_RixBXInsideTransmit | k_RixBXOutsideTransmit)
This is useful for open surfaces with both the 'outside' and 'inside' being transmissive. This is also useful for closed objects that allow light to pass through them (this may require additional indirect bounces)
k_RixBXBoth = (k_RixBXReflect | k_RixBXTransmit)
This is the most generic type of surface, allowing light to always be reflected or transmitted independently of the domains.
k_RixBXOutside = (k_RixBXOutsideReflect | k_RixBXOutsideTransmit)
This describse surfaces that will only consider light coming from the outside.
k_RixBXInside = (k_RixBXInsideReflect | k_RixBXInsideTransmit)
This describes surfaces that will only consider light coming from the inside.
k_RixBXInsideReflect doesn't prevent the bxdf from being reciprocal.
However, if only one of the bits corresponding to
k_RixInsideTransmit is set, this indicates that the bxdf only allows the light to go through in a particular direction, and as such breaks the reciprocity principle. This may lead to non-intuive, non-plausible renders, and break bidirectional algorithms.
In particular, when a bxdf is used by bidirectional integrators, the meaning for
L for all calls to
EvaluateSamplesAtIndex() may be flipped. For example, when generating photons,
GenerateSample() will be called with
V pointing toward the direction of the light subpath. In this case, non-reciprocal bxdfs needs to make sure they account for this when computing material response. This case can be detected by checking the value of
lightPath is 1, then
V points towards the light subpath.
How to chose a bxdf
First, note that using
k_RixBXVolume) is the best way to ensure your bxdf works properly, as a reference. When choosing a different evaluation domain (for evaluation efficiency), you need to make sure the rendered image doesn't change – if it does change, that could mean you are artificially restricting the evaluation domain of your bxdf, and potentially introducing rendering artifacts.
Most of the time, when dealing with closed and non-transmissive (i.e. translucent) objects, you should be able to restrict the evaluation domain (using
k_RixBXOutsideReflect). This would allow the integrator to skip processing light samples that lie behind the surface (on the other side of the closed object itself for example), reducing the number of bxdf evaluations required, as well as the number of transmission rays that need to be cast.
When dealing with transmissive (i.e. translucent) objects, and only expect translucent scattering to come from lights placed inside the objects, you may be able to use