## 10.6 Noise

In order to write solid textures that describe complex surface appearances, it is helpful to be able to introduce some controlled variation to the process. Consider a wood floor made of individual planks; each plank’s color is likely to be slightly different from the others. Or consider a windswept lake; we might want to have waves of similar amplitude across the entire lake, but we don’t want them to be homogeneous over all parts of the lake (as they might be if they were constructed from a sum of sine waves, for example). Modeling this sort of variation in a texture helps make the final result look more realistic.

One difficulty in developing textures like these is that the renderer
evaluates the surface’s texture functions at an irregularly distributed set
of points, where each evaluation is completely independent of the others.
As such, procedural textures must *implicitly* define a complex
pattern by answering queries about what the pattern’s value is at all of
these points. In contrast, the *explicit* pattern description
approach is embodied by the PostScript® language, for example, which
describes graphics on a page with a series of drawing commands. One
difficulty that the implicit approach introduces is that the texture can’t
just call `RNG::UniformFloat()` at each point at which it is evaluated to
introduce randomness: because each point would have a completely different
random value from its neighbors, no coherence would be possible in the
generated pattern.

An elegant way to address this issue of introducing controlled randomness
to procedural textures in graphics is the application of what is known as a
*noise function*. In general, noise functions used in graphics are
smoothly varying functions taking , for at
least , without obvious repetition. One of the most crucial
properties of a practical noise function is that it be band limited with a
known maximum frequency. This makes it possible to control the frequency
content added to a texture due to the noise function so that frequencies
higher than those allowed by the Nyquist limit aren’t introduced.

Many of the noise functions that have been developed are built on the idea
of an integer lattice over . First, a value is associated with each
integer position in space. Then, given an arbitrary position in
space, the eight surrounding lattice values are found. These lattice values
are then interpolated to compute the noise value at the particular point.
This idea can be generalized or restricted to more or fewer dimensions ,
where the number of lattice points is . A simple example of this
approach is *value noise*, where pseudo-random numbers between
and are associated with each lattice point, and actual noise values are
computed with trilinear interpolation or with a more complex spline
interpolant, which can give a smoother result by avoiding derivative
discontinuities when moving from one lattice cell to another.

For such a noise function, given an integer lattice point, it must be possible to efficiently compute its parameter value in a way that always associates the same value with each lattice point. Because it is infeasible to store values for all possible points, some compact representation is needed. One option is to use a hash function, where the coordinates are hashed and then used to look up parameters from a fixed-size table of precomputed pseudo-random parameter values.

### 10.6.1 Perlin Noise

In `pbrt` we will implement a noise function introduced by Ken
Perlin (1985a, 2002); as such, it is known as *Perlin
noise*. It has a value of zero at all integer lattice points.
Its variation comes from gradient vectors at each lattice point
that guide the interpolation of a smooth function in between the points
(Figure 10.24).
This noise function has many of the desired characteristics of a noise
function described above, is computationally efficient, and is
easy to implement.

Figure 10.25 shows the value of the noise function rendered on a sphere.

For convenience, there is also a variant of `Noise()` that takes a
`Point3f` directly:

The implementation first computes the integer coordinates of the cell that contains the given point and the fractional offsets of the point from the lower cell corner:

It next calls `Grad()` to get eight weight values, one for each corner
of the cell that the point lies inside. `Grad()` uses the cell
indices to index into a table; for efficiency we ensure that all of the
indices are within the range of the table here by zeroing any high bits
that would put a component past the table’s size. (The table size must be
a power of two for this trick to work—otherwise an expensive integer
modulus operation would be needed in place of the bitwise “and.”)

Each integer lattice point has a gradient vector
associated with it. The influence of the gradient vector for any point
inside the cell is obtained by computing the dot product of the vector from
the gradient’s corner to the lookup point and the gradient vector
(Figure 10.26); this is handled by the
`Grad()` function. Note that the vectors to the corners other than the
lower left one can be easily computed incrementally based on that vector.

The gradient vector for a particular integer lattice point is found by
indexing into a precomputed table of integer values, `NoisePerm`.
The four low-order bits of the table’s value for the lattice point
determine which of 16 gradient vectors is associated with it. In a
preprocessing step, this table of size `NoisePermSize` was filled with
numbers from 0 to `NoisePermSize-1` and then randomly permuted. These
values were then duplicated, making an array of size `2*NoisePermSize`
that holds the table twice in succession. The second copy of the table
makes lookups in the following code slightly more efficient.

Given a particular `(ix,iy,iz)` lattice point, a series of
table lookups gives a value from the random-number table:

By doing three nested permutations in this way, rather than
`NoisePerm[ix+iy+iz]`, for example, the final result is more irregular.
The first approach doesn’t usually return the same
value if `ix` and `iy` are interchanged, as the second does.
Furthermore, since the table was replicated to be twice the original length, the lookups
can be done as described above, eliminating the need for modulus operations
in code along the lines of

Given a final value from the permutation table that determines the gradient number, the dot product with the corresponding gradient vector must be computed. However, the gradient vectors do not need to be represented explicitly. All of the gradients use only , , or in their coordinates, so that the dot products reduce to addition of some (possibly negated) components of the vector. The final implementation is the following:

Given these eight contributions from the gradients, the next step is to
trilinearly interpolate between them at the point. Rather than
interpolating with `dx`, `dy`, and `dz` directly, though,
each of these values is passed through a smoothing function. This ensures
that the noise function has first- and second-derivative continuity as
lookup points move between lattice cells.

### 10.6.2 Random Polka Dots

A basic use of the noise function is as part of a polka dot texture that divides
texture space into rectangular cells (Figure 10.27).
Each cell has a 50% chance of having a dot inside of it, and the dots are
randomly placed inside their cells. `DotsTexture` takes the usual 2D mapping
function, as well as two `Texture`s, one for the regions of the surface
outside of the dots and one for the regions inside. It is defined in the files
`textures/dots.h` and
`textures/dots.cpp`.

`pbrt`’s Quadric Shapes.

`insideDot`result if point is inside dot>>

The evaluation function starts by taking the texture coordinates
and computing integer `sCell` and `tCell` values, which give
the coordinates of the cell they are inside. We will not consider antialiasing
of the polka dots texture here; an exercise at the end of the chapter
outlines how this might be done.

`insideDot`result if point is inside dot>>

Once the cell coordinate is known, it’s necessary to decide if there is a
polka dot in the cell. Obviously, this computation needs to be consistent
so that for each time this routine runs for points in a particular
cell it returns the same result. Yet we’d like the result not to
be completely regular (e.g., with a dot in every other cell). Noise
solves this problem: by evaluating the noise function at a position that is the same for
all points inside this cell—`(sCell+.5, tCell+.5)`—we can compute an
irregularly varying but consistent value for each cell.
If this value is greater
than zero, a dot is placed in the cell.

If there is a dot in the cell, the noise function is used again to randomly shift the center of the dot within the cell. The points at which the noise function is evaluated for the center shift are offset by arbitrary constant amounts, however, so that noise values from different noise cells are used from them, eliminating a possible source of correlation with the noise value used to determine the presence of a dot in the first place. (Note that the dot’s radius must be small enough so that it doesn’t spill over the cell’s boundary after being shifted; in that case, points where the texture was being evaluated would also need to consider the dots based in neighboring cells as potentially affecting their texture value.)

Given the dot center and radius, the texture needs to decide if the coordinates are within the radius of the shifted center. It does this by computing their squared distance to the center and comparing it to the squared radius.

`insideDot`result if point is inside dot>>=

### 10.6.3 Noise Idioms and Spectral Synthesis

The fact that noise is a band-limited function means that its frequency
content can be adjusted by scaling the domain over which it is evaluated.
For example, if `Noise(p)` has some known frequency content, then the
frequency content of `Noise(2*p)` will be twice as high. This is just
like the relationship between the frequency content of and
. This technique
can be used to create a noise function with a desired rate of
variation.

For many applications in procedural texturing, it’s useful to have
variation over multiple scales—for example, to add finer variations to
the base noise function. One effective way to do this with noise is to
compute patterns via *spectral synthesis*, where a complex function
is defined by a sum of contributions from another function :

for a set of weight values and parameter scale values . If the base function has a well-defined frequency content (e.g., is a sine or cosine function or a noise function), then each term also has a well-defined frequency content as described earlier. Because each term of the sum is weighted by a weight value , the result is a sum of contributions of various frequencies, with different frequency ranges weighted differently.

Typically, the scales are chosen in a geometric progression such that
and the weights are . The result is
that as higher frequency variation is added to the function, it has
relatively less influence on the overall shape of . Each
additional term is called an *octave* of noise,
since it has twice the frequency content of the previous one. When
this scheme is used with Perlin noise, the result is often referred to as
*fractional Brownian motion* (fBm), after a
particular type of random process that varies in a similar manner.

Fractional Brownian motion is a useful building block for procedural textures
because it gives a function with more complex variation than plain noise, while
still being easy to compute and still having well-defined frequency
content. The utility function `FBm()` implements the fractional
Brownian motion function.
Figure 10.28 shows two graphs of it.

`FBm()`Function. (a) 3 and (b) 6 octaves of noise. Notice how as more levels of noise are added, the graph has progressively more detail, although its overall shape remains roughly the same.

In addition to the point at which to
evaluate the function and the function’s partial derivatives at that point,
the function
takes an `omega` parameter, which ranges from zero to one and affects the
smoothness of the pattern by controlling the falloff of contributions at higher
frequencies (values around work well), and `maxOctaves`, which gives
the maximum number of octaves of noise that should be used in computing the
sum.

The implementation here uses a technique called *clamping*
to antialias the fBm function. The idea is that when we
are computing a value based on a sum of components, each with known
frequency content, we should stop adding in components that would have
frequencies beyond the Nyquist limit and instead add their average values
to the sum. Because the average value of `Noise()` is zero, all that
needs to be done is to compute the number of octaves such that none of the
terms has excessively high frequencies and not evaluate the noise function
for any higher octaves.

`Noise()` (and thus the first term of as well) has a maximum
frequency content of roughly . Each subsequent term represents
a doubling of frequency content. Therefore, we would like to find the
appropriate number of terms such that if the sampling rate in noise
space is , then

which ensures that there are frequencies right up to the Nyquist frequency but not past it. Equivalently,

where we’ve used the identity to write the last expression in a more convenient form for the following.

The squared sampling rate can be computed with one over the maximum of the squared length of the differentials and , which we’ll denote by . We can turn this inversion into a negation of the logarithm, and equivalently write this as:

Finally, the integral number of octaves up to the Nyquist limit are added
together
and the last octave is faded in, according to the fractional part of
`n`. This ensures that successive octaves of noise fade in
gradually, rather than appearing abruptly, which can cause
visually noticeable artifacts at the transitions. The implementation here
actually increases the frequency between octaves by 1.99, rather than by a
factor of 2, in order to reduce the impact of the fact that the noise
function is zero at integer lattice points. This breaks up that regularity
across sums of octaves of noise, which can also lead to subtle visual
artifacts.

The `SmoothStep()` function takes a minimum and maximum value and a
point at which to evaluate a smooth interpolating function. If the
point is below the minimum zero is returned, and if it’s above the
maximum one is returned. Otherwise, it smoothly interpolates between zero
and one using a cubic Hermite spline.

Closely related to the `FBm()` function is the `Turbulence()`
function. It also computes a sum of terms of the noise function but takes
the absolute value of each one:

Taking the absolute value introduces first-derivative discontinuities in the synthesized function, and thus the turbulence function has infinite frequency content. Nevertheless, the visual characteristics of this function can be quite useful for procedural textures. Figure 10.29 shows two graphs of the turbulence function.

`Turbulence()`function for (a) 3 and (b) 6 octaves of noise. Note that the first derivative discontinuities introduced by taking the absolute value of the noise function make this function substantially rougher than FBm.

The `Turbulence()` implementation here tries to antialias itself in the
same way that `FBm()` did. As described earlier, however, the
first-derivative discontinuities in the function introduce infinitely high-frequency
content, so these efforts can’t hope to be perfectly successful. The
`Turbulence()` antialiasing here at least eliminates some of the worst of
the artifacts; otherwise, increasing the pixel sampling rate is the best
recourse. In practice, this function doesn’t alias too terribly when used in
procedural textures, particularly compared to the aliasing from infinitely
high frequencies from geometric and shadow edges.

The average value of the absolute value of the noise function is roughly 0.2; this value should be added to the sum for the octaves where the noise function’s estimated frequency would be higher than the sampling rate.

### 10.6.4 Bumpy and Wrinkled Textures

The fBm and turbulence functions are particularly useful as a source of
random variation for bump mapping. The `FBmTexture` is a
`Float`-valued texture that uses `FBm()` to compute offsets, and
`WrinkledTexture` uses `Turbulence()` to do so. They are
demonstrated in Figures 10.30 and 10.31
and are implemented in `textures/fbm.h`, `textures/fbm.cpp`,
`textures/wrinkled.h`, and `textures/wrinkled.cpp`.

The implementation of `WrinkledTexture` is
almost identical to `FBmTexture`, save for a call to `Turbulence()`
instead of `FBm()`. As such, it isn’t included here.

### 10.6.5 Windy Waves

Application of fBm can give a reasonably convincing representation of waves
(Ebert et al. 2003). Figures 1.11
and 4.1 use this texture for the water in those
scenes. This `Texture` is based on two observations. First, across
the surface of a wind-swept lake (for example), some areas are relatively
smooth and some are more choppy; this effect comes from the natural
variation of the wind’s strength
from area to area. Second, the overall
form of individual waves on the surface can be described well by the
fBm-based wave pattern scaled by the wind strength. This texture is
implemented in `textures/windy.h` and
`textures/windy.cpp`.

The evaluation function uses two calls to the `FBm()` function. The
first scales down the point `P` by a factor of 10; as a result, the
first call to `FBm()` returns relatively low-frequency variation over
the surface of the object being shaded. This value is used to determine
the local strength of the wind. The second call determines the amplitude
of the wave at the particular point, independent of the amount of wind
there. The product of these two values gives the actual wave offset for
the particular location.

### 10.6.6 Marble

Another classic use of the noise function is to perturb texture coordinates
before using another texture or lookup table. For example, a facsimile of
marble can be made by modeling the marble material as a series of layered
strata and then using noise to perturb the coordinate used for finding a
value among the strata. The `MarbleTexture` in this section implements
this approach. Figure 10.32 illustrates the idea behind this
texture. On the left, the layers of marble are indexed directly using the
coordinate of the point on the sphere. On the right, fBm has been used
to perturb the value, introducing variation. This texture is
implemented in `textures/marble.h`
and
`textures/marble.cpp`.

`MarbleTexture`perturbs the coordinate used to index into a 1D table of colors using FBm, giving a plausible marble appearance.

`t`>>

The texture takes the usual set of parameters to control the `FBm()`
function that will be used to perturb the lookup coordinate. The
`variation` parameter modulates the magnitude of the perturbation.

An offset into the marble layers is computed by adding the variation to the
point’s component and using the sine function to remap its value into
the range . The <<Evaluate marble spline at `t`>>
fragment uses the value as the evaluation point for a cubic spline
through a series of colors that are similar to those of real marble.

`t`>>