10.5 Solid and Procedural Texturing
Once one starts to think of the texture coordinates used by 2D texture functions as quantities that can be computed by arbitrary functions and not just from the parametric coordinates of the surface, it is natural to generalize texture functions to be defined over 3D domains (often called solid textures) rather than just 2D . One reason solid textures are particularly convenient is that all objects have a natural 3D texture mapping—object space position. This is a substantial advantage for texturing objects that don’t have a natural 2D parameterization (e.g., triangle meshes and implicit surfaces) and for objects that have a distorted parameterization (e.g., near the poles of a sphere). In preparation for this idea, Section 10.2.5 defined a general TextureMapping3D interface to compute 3D texture coordinates as well as a TransformMapping3D implementation.
Solid textures introduce a new problem, however: texture representation. A 3D image map takes up a fair amount of storage space and is much harder to acquire than a 2D texture map, which can be extracted from photographs or painted by an artist. Therefore, procedural texturing—the idea that programs could be executed to generate texture values at arbitrary positions on surfaces in the scene—came into use at the same time that solid texturing was developed. A simple example of procedural texturing is a procedural sine wave. If we wanted to use a sine wave for bump mapping (for example, to simulate waves in water), it would be inefficient and potentially inaccurate to precompute values of the function at a grid of points and then store them in an image map. Instead, it makes much more sense to evaluate the sin() function at points on the surface as needed.
If we can find a 3D function that describes the colors of the grain in a solid block of wood, for instance, then we can generate images of complex objects that appear to be carved from wood. Over the years, procedural texturing has grown in application considerably as techniques have been developed to describe more and more complex surfaces procedurally.
Procedural texturing has a number of interesting implications. First, it can be used to reduce memory requirements for rendering, by reducing the need for the storage of large, high-resolution texture maps. In addition, procedural shading gives the promise of potentially infinite detail; as the viewer approaches an object, the texturing function is evaluated at the points being shaded, which naturally leads to the right amount of detail being visible. In contrast, image texture maps become blurry when the viewer is too close to them. On the other hand, subtle details of the appearance of procedural textures can be much more difficult to control than when image maps are used.
Another challenge with procedural textures is antialiasing. Procedural textures are often expensive to evaluate, and sets of point samples that fully characterize their behavior aren’t available as they are for image maps. Because we would like to remove high-frequency information in the texture function before we take samples from it, we need to be aware of the frequency content of the various steps we take along the way so we can avoid introducing high frequencies. Although this sounds daunting, there are a handful of techniques that work well to handle this issue.
10.5.1 UV Texture
Our first procedural texture converts the surface’s coordinates into the red and green components of a Spectrum (Figure 10.17). It is especially useful when debugging the parameterization of a new Shape, for example. It is defined in textures/uv.h and textures/uv.cpp.
The checkerboard is the canonical procedural texture (Figure 10.18). The texture coordinates are used to break up parameter space into square regions that are shaded with alternating patterns. Rather than just supporting checkerboards that switch between two fixed colors, the implementation here allows the user to pass in two textures to color the alternating regions. The traditional black-and-white checkerboard is obtained by passing two ConstantTextures. Its implementation is in the files textures/checkerboard.h and textures/checkerboard.cpp.
For simplicity, the frequency of the check function is 1 in space: checks are one unit wide in each direction. The effective frequency can always be changed by the TextureMapping2D class with an appropriate scale of the coordinates.
The checkerboard is good for demonstrating trade-offs between various antialiasing approaches for procedural textures. The implementation here supports both simple point sampling (no antialiasing) and a closed-form box filter evaluated over the filter region. The image sequence in Figure 10.22 at the end of this section shows the results of these approaches. The aaMethod enumerant selects which approach is used.
The evaluation routine does the usual texture coordinate and differential computation and then uses the appropriate fragment to compute an antialiased checkerboard value (or not antialiased, if point sampling has been selected).
The simplest case is to ignore antialiasing and just point-sample the checkerboard texture at the point. For this case, after getting the texture coordinates from the TextureMapping2D, the integer checkerboard coordinates for that position are computed, added together, and checked for odd or even parity to determine which of the two textures to evaluate.
Given how bad aliasing can be in a point-sampled checkerboard texture, we will invest some effort to antialias it properly. The easiest case happens when the entire filter region lies inside a single check (Figure 10.19). In this case, we simply need to determine which of the check types we are inside and evaluate that one. As long as the Texture inside that check does appropriate antialiasing itself, the result for this case will be properly antialiased.
It’s straightforward to check if the entire filter region is inside a single check by computing its bounding box and seeing if its extent lies inside the same check. For the remainder of this section, we will use the axis-aligned bounding box of the filter region given by the partial derivatives , , and so on, as the area to filter over, rather than trying to filter over the ellipse defined by the partial derivatives as the EWA filter did (Figure 10.20).
Filtering over the bounding box simplifies the implementation here, although somewhat increases the blurriness of the filtered values. The variables ds and dt in the following hold half the filter width in each direction, so the total area filtered over ranges from (s-ds, t-dt) to (s+ds, t+dt).
Otherwise, the lookup method approximates the filtered value by first computing a floating-point value that indicates what fraction of the filter region covers each of the two check types. This is equivalent to computing the average of the 2D step function that takes on the value 0 when we are in tex1 and 1 when we are in tex2, over the filter region. Figure 10.21(a) shows a graph of the checkerboard function , defined as
Given the average value, we can blend between the two subtextures, according to what fraction of the filter region each one is visible for.
The integral of the 1D checkerboard function can be used to compute the average value of the function over some extent. Inspection of the graph reveals that
To compute the average value of the step function in two dimensions, we separately compute the integral of the checkerboard in each 1D direction in order to compute its average value over the filter region.
Figure 10.22 shows a comparison of these filtering techniques.
10.5.3 Solid Checkerboard
The Checkerboard2DTexture class from the previous section wraps a checkerboard pattern around the object in parameter space. We can also define a solid checkerboard pattern based on 3D texture coordinates so that the object appears carved out of 3D checker cubes (Figure 10.23). Like the 2D variant, this implementation chooses between texture functions based on the lookup position. Note that these two textures need not be solid textures themselves; the Checkerboard3DTexture merely chooses between them based on the 3D position of the point.
Ignoring antialiasing, the basic computation to see if a point is inside a 3D checker region is
The Checkerboard3DTexture doesn’t have any built-in support for antialiasing, so its implementation is fairly short.