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.

<<BxDF Declarations>>= 
class BxDF { public: <<BxDF Interface>> 
virtual ~BxDF() { } BxDF(BxDFType type) : type(type) { } bool MatchesFlags(BxDFType t) const { return (type & t) == type; } virtual Spectrum f(const Vector3f &wo, const Vector3f &wi) const = 0; virtual Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample, Float *pdf, BxDFType *sampledType = nullptr) const; virtual Spectrum rho(const Vector3f &wo, int nSamples, const Point2f *samples) const; virtual Spectrum rho(int nSamples, const Point2f *samples1, const Point2f *samples2) const; virtual Float Pdf(const Vector3f &wi, const Vector3f &wo) const;
<<BxDF Public Data>> 
const BxDFType type;
};

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.

<<BSDF Declarations>>= 
enum BxDFType { BSDF_REFLECTION = 1 << 0, BSDF_TRANSMISSION = 1 << 1, BSDF_DIFFUSE = 1 << 2, BSDF_GLOSSY = 1 << 3, BSDF_SPECULAR = 1 << 4, BSDF_ALL = BSDF_DIFFUSE | BSDF_GLOSSY | BSDF_SPECULAR | BSDF_REFLECTION | BSDF_TRANSMISSION, };

<<BxDF Interface>>= 
BxDF(BxDFType type) : type(type) { }

<<BxDF Public Data>>= 
const BxDFType type;

The MatchesFlags() utility method determines if the BxDF matches the user-supplied type flags:

<<BxDF Interface>>+=  
bool MatchesFlags(BxDFType t) const { return (type & t) == type; }

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 n times n matrix that encoded the transfer of energy between spectral samples (where n is the number of samples in the Spectrum representation).

<<BxDF Interface>>+=  
virtual Spectrum f(const Vector3f &wo, const Vector3f &wi) const = 0;

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 omega Subscript normal i given an outgoing direction omega Subscript normal o 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 omega Subscript normal i 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.

<<BxDF Interface>>+=  
virtual Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample, Float *pdf, BxDFType *sampledType = nullptr) const;

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

rho Subscript normal h normal d Baseline left-parenthesis omega Subscript normal o Baseline right-parenthesis equals integral Underscript script upper H squared left-parenthesis bold n Subscript Baseline right-parenthesis Endscripts f Subscript normal r Baseline left-parenthesis normal p Subscript Baseline comma omega Subscript normal o Baseline comma omega Subscript normal i Baseline right-parenthesis StartAbsoluteValue cosine theta Subscript normal i Baseline EndAbsoluteValue normal d omega Subscript normal i Baseline period
(8.1)

The BxDF::rho() method computes the reflectance function rho Subscript normal h normal d . 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.

<<BxDF Interface>>+=  
virtual Spectrum rho(const Vector3f &wo, int nSamples, const Point2f *samples) const;

The hemispherical-hemispherical reflectance of a surface, denoted by rho Subscript normal h normal h , 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

rho Subscript normal h normal h Baseline equals StartFraction 1 Over pi EndFraction integral Underscript script upper H squared left-parenthesis bold n Subscript Baseline right-parenthesis Endscripts integral Underscript script upper H squared left-parenthesis bold n Subscript Baseline right-parenthesis Endscripts f Subscript normal r Baseline left-parenthesis normal p Subscript Baseline comma omega Subscript normal o Baseline comma omega Subscript normal i Baseline right-parenthesis StartAbsoluteValue cosine theta Subscript normal o Baseline cosine theta Subscript normal i Baseline EndAbsoluteValue normal d omega Subscript normal o Baseline normal d omega Subscript normal i Baseline period

The BxDF::rho() method computes rho Subscript normal h normal h if no direction omega Subscript normal o is provided. The remaining parameters are again used when computing a Monte Carlo estimate of the value of rho Subscript normal h normal h , if needed.

<<BxDF Interface>>+= 
virtual Spectrum rho(int nSamples, const Point2f *samples1, const Point2f *samples2) const;

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.

<<BxDF Declarations>>+=  
class ScaledBxDF : public BxDF { public: <<ScaledBxDF Public Methods>> 
ScaledBxDF(BxDF *bxdf, const Spectrum &scale) : BxDF(BxDFType(bxdf->type)), bxdf(bxdf), scale(scale) { } Spectrum rho(const Vector3f &w, int nSamples, const Point2f *samples) const { return scale * bxdf->rho(w, nSamples, samples); } Spectrum rho(int nSamples, const Point2f *samples1, const Point2f *samples2) const { return scale * bxdf->rho(nSamples, samples1, samples2); } Spectrum f(const Vector3f &wo, const Vector3f &wi) const; Spectrum Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample, Float *pdf, BxDFType *sampledType) const;
private: BxDF *bxdf; Spectrum scale; };

<<ScaledBxDF Public Methods>>= 
ScaledBxDF(BxDF *bxdf, const Spectrum &scale) : BxDF(BxDFType(bxdf->type)), bxdf(bxdf), scale(scale) { }

The implementations of the ScaledBxDF methods are straightforward; we’ll only include f() here.

<<BxDF Method Definitions>>= 
Spectrum ScaledBxDF::f(const Vector3f &wo, const Vector3f &wi) const { return scale * bxdf->f(wo, wi); }