8.1 Basic Interface
We will first define the interface for the individual BRDF and BTDF functions. BRDFs and BTDFs share a common base class, BxDF. Because both have the exact same interface, sharing the same base class reduces repeated code and allows some parts of the system to work with BxDFs generically without distinguishing between BRDFs and BTDFs.
The BSDF class, which will be introduced in Section 9.1, holds a collection of BxDF objects that together describe the scattering at a point on a surface. Although we are hiding the implementation details of the BxDF behind a common interface for reflective and transmissive materials, some of the light transport algorithms in Chapters 14 through 16 will need to distinguish between these two types. Therefore, all BxDFs have a BxDF::type member that holds flags from BxDFType. For each BxDF, the flags should have at least one of BSDF_REFLECTION or BSDF_TRANSMISSION set and exactly one of the diffuse, glossy, and specular flags. Note that there is no retro-reflective flag; retro-reflection is treated as glossy reflection in this categorization.
The MatchesFlags() utility method determines if the BxDF matches the user-supplied type flags:
The key method that BxDFs provide is BxDF::f(). It returns the value of the distribution function for the given pair of directions. This interface implicitly assumes that light in different wavelengths is decoupled—energy at one wavelength will not be reflected at a different wavelength. By making this assumption, the effect of the reflection function can be represented directly with a Spectrum. Supporting fluorescent materials where this assumption is not true would require that this method return an matrix that encoded the transfer of energy between spectral samples (where is the number of samples in the Spectrum representation).
Not all BxDFs can be evaluated with the f() method. For example, perfectly specular objects like a mirror, glass, or water only scatter light from a single incident direction into a single outgoing direction. Such BxDFs are best described with delta distributions that are zero except for the single direction where light is scattered. These BxDFs need special handling in pbrt, so we will also provide the method BxDF::Sample_f(). This method is used both for handling scattering that is described by delta distributions as well as for randomly sampling directions from BxDFs that scatter light along multiple directions; this second application will be explained in the discussion of Monte Carlo BSDF sampling in Section 14.1.
BxDF::Sample_f() computes the direction of incident light given an outgoing direction and returns the value of the BxDF for the pair of directions. For delta distributions, it is necessary for the BxDF to choose the incident light direction in this way, since the caller has no chance of generating the appropriate direction. The sample and pdf parameters aren’t needed for delta distribution BxDFs, so they will be explained later, in Section 14.1, when we provide implementations of this method for nonspecular reflection functions.
8.1.1 Reflectance
It can be useful to take the aggregate behavior of the 4D BRDF or BTDF, defined as a function over pairs of directions, and reduce it to a 2D function over a single direction, or even to a constant value that describes its overall scattering behavior.
The hemispherical-directional reflectance is a 2D function that gives the total reflection in a given direction due to constant illumination over the hemisphere, or, equivalently, total reflection over the hemisphere due to light from a given direction. It is defined as
The BxDF::rho() method computes the reflectance function . Some BxDFs can compute this value in closed form, although most use Monte Carlo integration to compute an approximation to it. For those BxDFs, the nSamples and samples parameters are used by the implementation of the Monte Carlo algorithm; they are explained in Section 14.1.5.
The hemispherical-hemispherical reflectance of a surface, denoted by , is a spectral value that gives the fraction of incident light reflected by a surface when the incident light is the same from all directions. It is
The BxDF::rho() method computes if no direction is provided. The remaining parameters are again used when computing a Monte Carlo estimate of the value of , if needed.
8.1.2 BxDF Scaling Adapter
It is also useful to take a given BxDF and scale its contribution with a Spectrum value. The ScaledBxDF wrapper holds a BxDF * and a Spectrum and implements this functionality. This class is used by the MixMaterial (defined in Section 9.2.3), which creates BSDFs based on a weighted combination of two other materials.
The implementations of the ScaledBxDF methods are straightforward; we’ll only include f() here.