## 5.1 Spectral Representation

The SPDs of real-world objects can be quite complicated; Figure 5.1 shows graphs of the spectral distribution of emission from a fluorescent light and the spectral distribution of the reflectance of lemon skin. A renderer doing computations with SPDs needs a compact, efficient, and accurate way to represent functions like these. In practice, some trade-off needs to be made between these qualities.

A general framework for investigating these issues can be developed based
on the problem of finding good *basis functions* to represent SPDs.
The idea behind basis functions is to map the infinite-dimensional space of
possible SPD functions to a low-dimensional space of coefficients . For example, a trivial basis function is the constant function
. An arbitrary SPD would be represented in this basis by a
single coefficient equal to its average value, so that its
approximation would be . This is obviously a poor
approximation, since most SPDs are much more complex than this single basis
function is capable of representing accurately.

Many different basis functions have been investigated for spectral
representation in computer graphics; the “Further Reading” section cites
a number of papers and further resources on this topic. Different sets of
basis functions can offer substantially different trade-offs in the
complexity of the key operations like converting an arbitrary SPD into a
set of coefficients (projecting it into the basis), computing the
coefficients for the SPD given by the product of two SPDs expressed in the
basis, and so on. In this chapter, we’ll introduce two representations
that can be used for spectra in `pbrt`: `RGBSpectrum`, which follows
the typical computer graphics practice of representing SPDs with
coefficients representing a mixture of red, green, and blue colors, and
`SampledSpectrum`, which represents the SPD as a set of point
samples over a range of wavelengths.

### 5.1.1 The Spectrum Type

Throughout `pbrt`, we have been careful to implement all computations
involving SPDs in terms of the `Spectrum` type, using a specific
set of built-in operators (addition, multiplication, etc.). The
`Spectrum` type hides the details of the particular spectral
representation used, so that changing this detail of the system only
requires changing the `Spectrum` implementation; other code can remain
unchanged. The implementations of the `Spectrum` type are in the files
`core/spectrum.h` and
`core/spectrum.cpp`.

The selection of which spectrum representation is used for the
`Spectrum` type in `pbrt` is done with a `typedef` in the file
`core/pbrt.h`. By default, `pbrt` uses the more efficient but less
accurate RGB representation.

We have not written the system such that the selection of which
`Spectrum` implementation to use could be resolved at run time; to
switch to a different representation, the entire system must be recompiled. One
advantage to this design is that many of the various `Spectrum` methods
can be implemented as short functions that can be inlined by the compiler,
rather than being left as stand-alone functions that have to be invoked
through the relatively slow virtual method call mechanism. Inlining frequently used
short functions like these can give a substantial improvement in
performance. A second advantage is that structures in the system that hold
instances of the `Spectrum` type can hold them directly rather than
needing to allocate them dynamically based on the spectral representation
chosen at run time.

### 5.1.2 CoefficientSpectrum Implementation

Both of the representations implemented in this chapter are based on
storing a fixed number of samples of the SPD. Therefore, we’ll start by
defining the `CoefficientSpectrum` template class, which represents a
spectrum as a particular number of samples given as the
`nSpectrumSamples` template parameter. Both `RGBSpectrum` and
`SampledSpectrum` are partially implemented by inheriting from
`CoefficientSpectrum`.

One `CoefficientSpectrum` constructor is provided; it initializes a
spectrum with a constant value across all wavelengths.

A variety of arithmetic operations on `Spectrum` objects are needed;
the implementations in `CoefficientSpectrum` are all straightforward.
First, we define operations to add pairs of spectral distributions. For the
sampled representation, it’s easy to show that each sample value for the
sum of two SPDs is equal to the sum of the corresponding sample values.

Similarly, subtraction, multiplication, division, and unary negation are
defined component-wise. These methods are very similar to the ones already
shown, so we won’t include them here. `pbrt` also provides equality and
inequality tests, also not included here.

It is often useful to know if a spectrum represents an SPD with value zero everywhere. If, for example, a surface has zero reflectance, the light transport routines can avoid the computational cost of casting reflection rays that have contributions that would eventually be multiplied by zeros and thus do not need to be traced.

The `Spectrum` implementation (and thus the `CoefficientSpectrum`
implementation) must also provide implementations of a number of slightly
more esoteric methods, including those that take the square root of an
SPD or raise the function it represents to a given power.
These are needed for some of the computations performed by the
`Fresnel` classes in Chapter 8, for example. The
implementation of `Sqrt()` takes the square root of each component to
give the square root of the SPD.
The implementations of `Pow()`
and `Exp()`
are analogous and won’t
be included here.

It’s frequently useful to be able to linearly interpolate between two SPDs with a parameter .

Some portions of the image processing pipeline will want to clamp a spectrum to ensure that the function it represents is within some allowable range.

Finally, we provide a debugging routine to check if any of the sample
values of the SPD is the not-a-number (NaN floating-point value). This
situation can happen due to an accidental division by 0; `Assert()`s
throughout the system use this method to catch this case close to where it
happens.

Most of the spectral computations in `pbrt` can be implemented using the
basic operations we have defined so far. However, in some cases it’s
necessary to be able to iterate over a set of spectral samples that
represent an SPD—for example to perform a spectral sample-based table
lookup or to evaluate a piecewise function over wavelengths. Classes that
need this functionality in `pbrt` include the `TabulatedBSSRDF` class,
which is used for subsurface scattering, and the `HomogeneousMedium`
and `GridDensityMedium` classes.

For these uses, `CoefficientSpectrum` provides a public constant,
`nSamples`, that gives the number of samples used to represent the
SPD and an `operator[]` method to access individual sample values.

Note that the presence of this sample accessor imposes the implicit
assumption that the spectral representation is a set of coefficients that
linearly scale a fixed set of basis functions. If, for example, a
`Spectrum` implementation instead represented SPDs as a sum of
Gaussians where the coefficients alternatingly scaled the Gaussians
and set their width,

then the code that currently uses this accessor would need to be modified,
perhaps to instead operate on a version of the SPD that had been converted
to a set of linear coefficients. While this crack in the `Spectrum`
abstraction is not ideal, it simplifies other parts of the current system
and isn’t too hard to clean up if one adds spectral
representations, where this assumption isn’t correct.