## 8.3 Sampling Interface

`pbrt`’s `Sampler` interface makes it possible to use a variety of
sample generation algorithms for rendering. The sample points that they
provide are used by `pbrt`’s `Integrator`s in a multitude of ways,
ranging from determining points on the image plane from which camera rays
originate to selecting which light source to trace a shadow ray to and at
which point on it the shadow ray should terminate.

As we will see in the following sections, the benefits of carefully crafted sampling patterns are not just theoretical; they can substantially improve the quality of rendered images. The runtime expense for using good sampling algorithms is relatively small; because evaluating the radiance for each image sample is much more expensive than computing the sample’s component values, doing this work pays dividends (Figure 8.20).

*(Killeroo model courtesy of headus/Rezard.)*

The task of a `Sampler` is to generate uniform -dimensional sample points, where
each coordinate’s value is in the range . The total number of
dimensions in each point is not set ahead of time; `Sampler`s must
generate additional dimensions on demand, depending on the number of
dimensions required for the calculations performed by the light transport
algorithms. (See Figure 8.21.) While this design
makes implementing a `Sampler` slightly more complex than if its task
was to generate all the dimensions of each sample point up front, it is
more convenient for integrators, which end up needing a different number of
dimensions depending on the particular path they follow through the scene.

`pbrt`’s

`Integrator`s.

All the samplers save for `MLTSampler` are defined in this chapter;
that one is used solely by the `MLTIntegrator`,
which is described in the online version of the book.

`Sampler` implementations specify the number of samples to be taken in
each pixel and return this value via `SamplesPerPixel()`. Most
samplers already store this value as a member variable and return it
directly in their implementations of this method. We
will usually not include the straightforward implementations of this method
in the text.

When an `Integrator` is ready to start work on a given pixel sample,
it starts by calling `StartPixelSample()`, providing the coordinates
of the pixel in the image and the index of the sample within the pixel.
(The index should be greater than or equal to zero and less than the value
returned by `SamplesPerPixel()`.) The `Integrator`
may also provide a starting dimension at which sample generation should
begin.

This method serves two purposes. First, some `Sampler`
implementations use the knowledge of which pixel is being sampled to
improve the overall distribution of the samples that they generate—for
example, by ensuring that adjacent pixels do not take two samples that are
close together. Attending to this detail, while it may seem minor, can
substantially improve image quality.

Second, this method allows samplers to put themselves in a deterministic
state before generating each sample point. Doing so is an important part of
making `pbrt`’s operation deterministic, which in turn is crucial for
debugging. It is expected that all samplers will be implemented so that
they generate precisely the same sample coordinate values for a given pixel
and sample index across multiple runs of the renderer. This way, for
example, if `pbrt` crashes in the middle of a lengthy run, debugging can
proceed starting at the specific pixel and pixel sample index where the
renderer crashed. With a deterministic renderer, the crash will reoccur
without taking the time to perform all the preceding rendering work.

`Integrator`s can request dimensions of the -dimensional sample
point one or two at a time, via the `Get1D()` and `Get2D()`
methods. While a 2D sample value could be constructed by using values
returned by a pair of calls to `Get1D()`, some samplers can generate
better point distributions if they know that two dimensions will be used
together. However, the interface does not support requests for 3D or
higher-dimensional sample values from samplers because these are generally
not needed for the types of rendering algorithms implemented here. In that
case, multiple values from lower-dimensional components can be used to
construct higher-dimensional sample points.

A separate method, `GetPixel2D()`, is called to retrieve the 2D sample
used to determine the point on the film plane that is sampled. Some of the
following `Sampler` implementations handle those dimensions of the
sample differently from the way they handle 2D samples in other dimensions;
other `Sampler`s implement this method by
calling their `Get2D()` methods.

Because each sample coordinate must be strictly less than 1, it is useful
to define a constant, `OneMinusEpsilon`, that represents the largest
representable floating-point value that is less than 1. Later, the `Sampler`
implementations will sometimes clamp sample values to be no larger than
this.

A sharp edge of these interfaces is that code that uses sample values must be carefully written so that it always requests sample dimensions in the same order. Consider the following code:

In this case, the first dimension of the sample will always be passed to
the function `a()`; when the code path that calls `b()` is
executed, `b()` will receive the second dimension. However, if the
`if` test is not always true or false, then `c()` will sometimes
receive a sample value from the second dimension of the sample and otherwise
receive a sample value from the third dimension.
This will thus thwart efforts by the sampler to
provide well-distributed sample points in each dimension being evaluated.
Code that uses `Sampler`s should therefore be
carefully written so that it consistently consumes sample dimensions, to
avoid this issue.

`Clone()`, the final method required by the interface, returns a
copy of the `Sampler`. Because
`Sampler` implementations store a variety of state about the current
sample—which pixel is being sampled, how many dimensions of the sample
have been used, and so forth—it is unsafe for a single `Sampler` to
be used concurrently by multiple threads. Therefore, `Integrator`s
call `Clone()` to make copies of an initial `Sampler` so that
each thread has its own. The implementations of the various `Clone()`
methods are not generally interesting, so they will not be included in the
text here.