## 9.1 BSDF Representation

There are two components of `pbrt`’s representation of BSDFs: the `BxDF`
interface and its implementations (described in
Section 9.1.2) and the `BSDF` class (described in
Section 9.1.5). The former models specific types of scattering at
surfaces, while the latter provides a convenient wrapper around a pointer to a
specific `BxDF` implementation. The `BSDF` class also centralizes
general functionality so that `BxDF` implementations do not individually
need to handle it, and it records information about the local geometric
properties of the surface.

### 9.1.1 Geometric Setting and Conventions

Reflection computations in `pbrt` are performed in a reflection coordinate
system where the two tangent vectors and the normal vector at the point
being shaded are aligned with the , , and axes, respectively
(Figure 9.2). All direction vectors passed to
and returned from the `BxDF` evaluation and sampling routines will be
defined with respect to this coordinate system. It is important to
understand this coordinate system in order to understand the `BxDF`
implementations in this chapter.

Section 3.8 introduced a range of utility
functions—like `SinTheta()`, `CosPhi()`, etc.—that efficiently evaluate
trigonometric functions of unit vectors expressed in Cartesian coordinates
matching the convention used here. They will be used extensively in this
chapter, as quantities like the cosine of the elevation angle play a central
role in most reflectance models.

We will frequently find it useful to check whether two direction
vectors lie in the same hemisphere with respect to the surface normal in
the BSDF coordinate system; the `SameHemisphere()` function performs
this check.

There are some additional conventions that are important to keep in mind
when reading the code in this chapter and when adding BRDFs and BTDFs to
`pbrt`:

- The incident light direction and the outgoing viewing direction
will both be normalized and
*outward facing*after being transformed into the local coordinate system at the surface. In other words, the directions will not model the physical propagation of light, which is helpful in bidirectional rendering algorithms that generate light paths in reverse order. - In
`pbrt`, the surface normal always points to the “outside” of the object, which makes it easy to determine if light is entering or exiting transmissive objects: if the incident light direction is in the same hemisphere as , then light is entering; otherwise, it is exiting. Therefore, the normal may be on the opposite side of the surface than one or both of the and direction vectors. Unlike many other renderers,`pbrt`does not flip the normal to lie on the same side as . - The local coordinate system used for shading may not be exactly the
same as the coordinate system returned by the
`Shape::Intersect()`routines from Chapter 6; it may have been modified between intersection and shading to achieve effects like bump mapping. See Chapter 10 for examples of this kind of modification.

### 9.1.2 BxDF Interface

The interface for the individual BRDF and BTDF functions is defined by
`BxDF`, which is in the file `base/bxdf.h`.

The `BxDF` interface provides a method to query the material type following
the earlier categorization, which some light transport algorithms in
Chapters 13 through 15 use to specialize
their behavior.

The `BxDFFlags` enumeration lists the previously mentioned categories and
also distinguishes reflection from transmission. Note that retroreflection is
treated as glossy reflection in this list.

`BxDFFlags`definitions>>

These constants can also be combined via a binary or operation to characterize materials that simultaneously exhibit multiple traits. A number of commonly used combinations are provided with their own names for convenience:

`BxDFFlags`definitions>>=

A few utility functions encapsulate the logic for testing various flag characteristics.

The key method that `BxDF`s provide is `f()`, which returns the value
of the distribution function for the given pair of directions. The provided
directions must be expressed in the local reflection coordinate system
introduced in the previous section.

This interface implicitly assumes that light in different wavelengths is
decoupled—energy at one wavelength will not be reflected at a different
wavelength. In this case, the effect of reflection can be described by a
per-wavelength factor returned in the form of a `SampledSpectrum`.
Fluorescent materials that redistribute energy between wavelengths would
require that this method return an matrix to encode the transfer
between the spectral samples of `SampledSpectrum`.

Neither constructors nor methods of `BxDF` implementations will
generally be informed about the specific wavelengths associated with
`SampledSpectrum` entries, since they do not require this information.

The function also takes a `TransportMode` enumerator that indicates
whether the outgoing direction is toward the camera or toward a light source
(and the corresponding opposite for the incident direction). This is necessary
to handle cases where scattering is non-symmetric; this subtle aspect is
discussed further in Section 9.5.2.

`BxDF`s must also provide a method that uses importance sampling to
draw a direction from a distribution that approximately matches the
scattering function’s shape. Not only is this operation crucial for efficient
Monte Carlo integration of the light transport equation (1.1),
it is the only way to evaluate some BSDFs.
For example, perfect specular objects like a mirror, glass, or water only
scatter light from a single incident direction into a single outgoing
direction. Such `BxDF`s are best described with *Dirac delta distributions*
(covered in more detail in Section 9.1.4)
that are zero except for the single direction where light is scattered.
Their `f()` and `PDF()` methods always return zero.

Implementations of the `Sample_f()` method should determine the direction
of incident light given an outgoing direction and return the value
of the `BxDF` for the pair of directions. They take three uniform
samples in the range via the `uc` and `u` parameters. Implementations
can use these however they wish, though it is generally best if they use the 1D
sample `uc` to choose between different types of scattering (e.g.,
reflection or transmission) and the 2D sample to choose a specific direction.
Using `uc` and `u[0]` to choose a direction, for example, would
likely give inferior results to using `u[0]` and `u[1]`, since
`uc` and `u[0]` are not necessarily jointly well distributed. Not
all the sample values need be used, and `BxDF`s that need additional
sample values must generate them themselves. (The `LayeredBxDF` described
in Section 14.3 is one such example.)

Note the potentially counterintuitive direction convention: the outgoing
direction is given, and the implementation then samples an incident
direction . The Monte Carlo methods in this book construct light paths in
*reverse* order—that is, counter to the propagation direction of the
transported quantity (radiance or importance)—motivating this choice.

Callers of this method must be prepared for the possibility that sampling
fails, in which case an unset `optional` value will be returned.

The sample generation can optionally be restricted to the reflection or
transmission component via the `sampleFlags` parameter. A sampling failure
will occur in invalid cases—for example, if the caller requests a transmission
sample on an opaque surface.

If sampling succeeds, the method returns a `BSDFSample` that
includes the value of the BSDF `f`, the sampled direction `wi`, its
probability density function (PDF) measured with respect
to solid angle, and a `BxDFFlags` instance that
describes the characteristics of the particular sample. `BxDF`s should
specify the direction `wi` with respect to the local reflection coordinate
system, though `BSDF::Sample_f()` will transform this direction to
rendering space before returning it.

Some `BxDF` implementations (notably, the `LayeredBxDF` described
in Section 14.3) generate samples via simulation,
following a random light path. The distribution of paths that escape is
the `BxDF`’s exact (probabilistic) distribution, but the returned `f`
and `pdf` are only proportional to their true values. (Fortunately,
by the same proportion!) This case needs special handling in
light transport algorithms, and is indicated by the
`pdfIsProportional` field. For all the `BxDF`s in this
chapter, it can be left set to its default `false` value.

Several convenience methods can be used to query characteristics of the sample
using previously defined functions like `BxDFFlags::IsReflective()`, etc.

The `PDF()` method returns the value of the PDF for a given pair of
directions, which is useful for techniques like multiple importance sampling
that compare probabilities of multiple strategies for obtaining a given sample.

### 9.1.3 Hemispherical Reflectance

With the `BxDF` methods described so far, it is possible to implement
methods that compute the reflectance of a `BxDF` by applying the Monte
Carlo estimator to the definitions of reflectance from
Equations (4.12) and (4.13).

A first variant of `BxDF::rho()` computes the reflectance function
. Its caller is responsible for determining how many
samples should be taken and for providing the uniform sample values to be
used in computing the estimate. Thus, depending on the context, callers
have control over sampling and the quality of the returned estimate.

Each term of the estimator

is easily evaluated.

The hemispherical-hemispherical reflectance is found in the second
`BxDF::rho()` method that evaluates Equation (4.13). As
with the first `rho()` method, the caller is responsible for passing
in uniform sample values—in this case, five dimensions’ worth of them.

Our implementation samples the first direction `wo` uniformly over the
hemisphere. Given this, the second direction can be sampled using
`BxDF::Sample_f()`.

### 9.1.4 Delta Distributions in BSDFs

Several BSDF models in this chapter make use of Dirac delta distributions to represent interactions with perfect specular materials like smooth metal or glass surfaces. They represent a curious corner case in implementations, and we therefore establish a few important conventions.

Recall from Section 8.1.1 that the Dirac delta distribution is defined such that

and

According to these equations, can be interpreted as a normalized
density function that is zero for all . Generating a sample from such a
distribution is trivial, since there is only one value that it can take. In
this sense, the forthcoming implementations of `Sample_f()` involving
delta functions naturally fit into the Monte Carlo sampling framework.

However, sampling alone is not enough: two methods (`Sample_f()` and
`PDF`) also provide sampling densities, and it is considerably less clear
what values should be returned here. Strictly speaking, the delta distribution
is not a true function but constitutes the limit of a sequence of
functions—for example, one describing a box of unit area whose width
approaches 0; see Chapter 5 of Bracewell (2000) for
details. In the limit, the value of must then necessarily tend
toward infinity. This important theoretical realization does not easily
translate into C++ code: certainly, returning an infinite or very large PDF
value is not going to lead to correct results from the renderer.

To resolve this conflict, BSDFs may only contain matched pairs of delta functions in their function and PDF. For example, suppose that the PDF factors into a remainder term and a delta function involving a particular direction :

If the same holds true for , then a Monte Carlo estimator that divides by the PDF will never require evaluation of the delta function:

Implementations of perfect specular materials will thus return a constant PDF
of 1 when `Sample_f()` generates a direction associated with a delta
function, with the understanding that the delta function will cancel in the
estimator.

In contrast, the respective `PDF()` methods should return 0 for all
directions, since there is zero probability that another sampling method
will randomly find the direction from a delta
distribution.

### 9.1.5 BSDFs

`BxDF` class implementations perform all computation in a local
shading coordinate system that is most appropriate for this task. In contrast,
rendering algorithms operate in *rendering space*
(Section 5.1.1); hence a transformation between
these two spaces must be performed somewhere. The `BSDF` is a small wrapper
around a `BxDF` that handles this transformation.

`bxdf`and return

`BSDFSample`>>

In addition to an encapsulated `BxDF`, the `BSDF` holds a shading
frame based on the `Frame` class.

The constructor initializes the latter from the shading normal and using the shading coordinate system convention (Figure 9.3).

The default constructor creates a `BSDF` with a `nullptr`-valued
`bxdf`, which is useful to represent transitions between different media
that do not themselves scatter light. An `operator bool()` method checks
whether the `BSDF` represents a real material interaction,
in which case the `Flags()` method provides further information about its
high-level properties.

The `BSDF` provides methods that perform transformations to and from
the reflection coordinate system used by `BxDF`s.

The `f()` function performs the required coordinate frame conversion and
then queries the `BxDF`. The rare case in which the `wo` direction lies
exactly in the surface’s tangent plane often leads to not-a-number (NaN) values
in `BxDF` implementations that further propagate and may eventually
contaminate the rendered image. The `BSDF` avoids this case by immediately
returning a zero-valued `SampledSpectrum`.

The `BSDF` also provides a second templated `f()` method that can be
parameterized by the underlying `BxDF`. If the caller knows the specific
type of `BSDF::bxdf`, it can call this variant directly without
involving the dynamic method dispatch used in the method above. This approach is used by `pbrt`’s
wavefront rendering path, which groups evaluations based on the underlying
`BxDF` to benefit from vectorized execution on the GPU. The implementation
of this specialized version simply casts the `BxDF` to the provided type
before invoking its `f()` method.

The `BSDF::Sample_f()` method similarly forwards the sampling request
on to the `BxDF` after transforming the direction to the local
coordinate system.

`bxdf`and return

`BSDFSample`>>

If the `BxDF` implementation returns a sample that
has a zero-valued BSDF or PDF or an incident direction in the tangent
plane, this method nevertheless returns an unset sample value. This allows
calling code to proceed without needing to check those cases.

`bxdf`and return

`BSDFSample`>>=

`BSDF::PDF()` follows the same pattern.

We have omitted the definitions of additional templated `Sample_f()` and
`PDF()` variants that are parameterized by the `BxDF` type.

Finally, `BSDF` provides `rho()`
methods to compute the reflectance that forward the call on to its
underlying `bxdf`. They are trivial and therefore not included here.