10.3 Texture Interface and Basic Textures

Given a variety of ways to generate 2D and 3D texture coordinates, we will now define the general interfaces for texture functions. As mentioned earlier, pbrt supports two types of Textures: scalar Float-valued, and spectral-valued.

For the first, there is FloatTexture, which is defined in base/texture.h. There are currently 14 implementations of this interface in pbrt, which leads to a lengthy list of types for the TaggedPointer template class. Therefore, we have gathered them into a fragment, <<FloatTextures>>, that is not included here.

<<FloatTexture Definition>>= 
class FloatTexture : public TaggedPointer<<<FloatTextures>> 
FloatImageTexture, GPUFloatImageTexture, FloatMixTexture, FloatDirectionMixTexture, FloatScaledTexture, FloatConstantTexture, FloatBilerpTexture, FloatCheckerboardTexture, FloatDotsTexture, FBmTexture, FloatPtexTexture, GPUFloatPtexTexture, WindyTexture, WrinkledTexture
> { public: <<FloatTexture Interface>> 
using TaggedPointer::TaggedPointer; static FloatTexture Create(const std::string &name, const Transform &renderFromTexture, const TextureParameterDictionary &parameters, const FileLoc *loc, Allocator alloc, bool gpu); std::string ToString() const; Float Evaluate(TextureEvalContext ctx) const;
};

A FloatTexture takes a TextureEvalContext and returns a Float value.

<<FloatTexture Interface>>= 
Float Evaluate(TextureEvalContext ctx) const;

SpectrumTexture plays an equivalent role for spectral textures. It also has so many implementations that we have elided their enumeration from the text. It, too, is defined in base/texture.h.

<<SpectrumTexture Definition>>= 
class SpectrumTexture : public TaggedPointer<<<SpectrumTextures>> 
SpectrumImageTexture, GPUSpectrumImageTexture, SpectrumMixTexture, SpectrumDirectionMixTexture, SpectrumScaledTexture, SpectrumConstantTexture, SpectrumBilerpTexture, SpectrumCheckerboardTexture, MarbleTexture, SpectrumDotsTexture, SpectrumPtexTexture, GPUSpectrumPtexTexture
> { public: <<SpectrumTexture Interface>> 
using TaggedPointer::TaggedPointer; static SpectrumTexture Create(const std::string &name, const Transform &renderFromTexture, const TextureParameterDictionary &parameters, SpectrumType spectrumType, const FileLoc *loc, Allocator alloc, bool gpu); std::string ToString() const; SampledSpectrum Evaluate(TextureEvalContext ctx, SampledWavelengths lambda) const;
};

For the reasons that were discussed in Section 4.5.4, the SpectrumTexture evaluation routine does not return a full spectral distribution (e.g., an implementation of the Spectrum interface from Section 4.5.1). Rather, it takes a set of wavelengths of interest and returns the texture’s value at just those wavelengths.

<<SpectrumTexture Interface>>= 

10.3.1 Constant Texture

The constant textures return the same value no matter where they are evaluated. Because they represent constant functions, they can be accurately reconstructed with any sampling rate and therefore need no antialiasing. Although these two textures are trivial, they are actually quite useful. By providing these classes, all parameters to all Materials can be represented as Textures, whether they are spatially varying or not. For example, a red diffuse object will have a SpectrumConstantTexture that always returns red as the diffuse color of the material. This way, the material system always evaluates a texture to get the surface properties at a point, avoiding the need for separate textured and nontextured versions of materials. Such an approach would grow increasingly unwieldy as the number of material parameters increased.

FloatConstantTexture, like all the following texture implementations, is defined in the files texture.h and texture.cpp.

<<FloatConstantTexture Definition>>= 
class FloatConstantTexture { public: FloatConstantTexture(Float value) : value(value) {} Float Evaluate(TextureEvalContext ctx) const { return value; } private: Float value; };

The spectrum constant texture, SpectrumConstantTexture, is similarly simple. Here is its Evaluate() method; the rest of its structure parallels FloatConstantTexture and so is not included here.

<<SpectrumConstantTexture Public Methods>>= 
SampledSpectrum Evaluate(TextureEvalContext ctx, SampledWavelengths lambda) const { return value.Sample(lambda); }

10.3.2 Scale Texture

We have defined the texture interface in a way that makes it easy to use the output of one texture function when computing another. This is useful since it lets us define generic texture operations using any of the other texture types. The FloatScaledTexture takes two Float-valued textures and returns the product of their values.

<<FloatScaledTexture Definition>>= 
class FloatScaledTexture { public: <<FloatScaledTexture Public Methods>> 
FloatScaledTexture(FloatTexture tex, FloatTexture scale) : tex(tex), scale(scale) {} static FloatTexture Create(const Transform &renderFromTexture, const TextureParameterDictionary &parameters, const FileLoc *loc, Allocator alloc); Float Evaluate(TextureEvalContext ctx) const { Float sc = scale.Evaluate(ctx); if (sc == 0) return 0; return tex.Evaluate(ctx) * sc; } std::string ToString() const;
private: FloatTexture tex, scale; };

FloatScaledTexture ignores antialiasing, leaving it to its two subtextures to antialias themselves but not making an effort to antialias their product. While it is easy to show that the product of two band-limited functions is also band limited, the maximum frequency present in the product may be greater than that of either of the two terms individually. Thus, even if the scale and value textures are perfectly antialiased, the result might not be. Fortunately, the most common use of this texture is to scale another texture by a constant, in which case the other texture’s antialiasing is sufficient.

One thing to note in the implementation of its Evaluate() method is that it skips evaluating the tex texture if the scale texture returns 0. It is worthwhile to avoid incurring the cost of this computation if it is unnecessary.

<<FloatScaledTexture Public Methods>>= 
Float Evaluate(TextureEvalContext ctx) const { Float sc = scale.Evaluate(ctx); if (sc == 0) return 0; return tex.Evaluate(ctx) * sc; }

Figure 10.11: Use of the SpectrumScaledTexture in the Watercolor Scene. The product of (a) a texture of paint strokes and (b) a mask representing splotches gives (c) colorful splotches. (d) When applied to the surface of a table, a convincing paint spill results. (Scene courtesy of Angelo Ferretti.)

Figure 10.12: Use of the Mix Texture in the Kroken Scene. (a) The SpectrumMixTexture is used to find the color at each point on the bottom two cups. (b) Two fixed RGB colors are modulated using this image texture. (Scene courtesy of Angelo Ferretti.)

SpectrumScaledTexture is the straightforward variant and is therefore not included here. An example of its use is shown in Figure 10.11.

10.3.3 Mix Textures

The mix textures are more general variations of the scale textures. They blend between two textures of the same type based on a scalar blending factor. Note that a constant texture could be used for the blending factor to achieve a uniform blend, or a more complex Texture could be used to blend in a spatially nonuniform way. Figure 10.12 shows the use of the SpectrumMixTexture where an image is used to blend between two constant RGB colors.

<<FloatMixTexture Definition>>= 
class FloatMixTexture { public: <<FloatMixTexture Public Methods>> 
FloatMixTexture(FloatTexture tex1, FloatTexture tex2, FloatTexture amount) : tex1(tex1), tex2(tex2), amount(amount) {} Float Evaluate(TextureEvalContext ctx) const { Float amt = amount.Evaluate(ctx); Float t1 = 0, t2 = 0; if (amt != 1) t1 = tex1.Evaluate(ctx); if (amt != 0) t2 = tex2.Evaluate(ctx); return (1 - amt) * t1 + amt * t2; } static FloatMixTexture *Create(const Transform &renderFromTexture, const TextureParameterDictionary &parameters, const FileLoc *loc, Allocator alloc); std::string ToString() const;
private: FloatTexture tex1, tex2; FloatTexture amount; };

To evaluate the mixture, the three textures are evaluated and the floating-point value is used to linearly interpolate between the two. When the blend amount amt is zero, the first texture’s value is returned, and when it is one the second one’s value is returned. The Evaluate() method here makes sure not to evaluate textures unnecessarily if the blending amount implies that only one of their values is necessary. (Section 15.1.1 has further discussion about why the logic for that is written just as it is here, rather than with, for example, cascaded if tests that each directly return the appropriate value.) We will generally assume that amt will be between zero and one, but this behavior is not enforced, so extrapolation is possible as well.

As with the scale textures, antialiasing is ignored, so the introduction of aliasing here is a possibility.

<<FloatMixTexture Public Methods>>= 
Float Evaluate(TextureEvalContext ctx) const { Float amt = amount.Evaluate(ctx); Float t1 = 0, t2 = 0; if (amt != 1) t1 = tex1.Evaluate(ctx); if (amt != 0) t2 = tex2.Evaluate(ctx); return (1 - amt) * t1 + amt * t2; }

We will not include the implementation of SpectrumMixTexture here, as it parallels that of FloatMixTexture.

It can also be useful to blend between two textures based on the surface’s orientation. The FloatDirectionMixTexture and SpectrumDirectionMixTexture use the dot product of the surface normal with a specified direction to compute such a weight. As they are very similar, we will only discuss SpectrumDirectionMixTexture here.

<<SpectrumDirectionMixTexture Definition>>= 
class SpectrumDirectionMixTexture { public: <<SpectrumDirectionMixTexture Public Methods>> 
SpectrumDirectionMixTexture(SpectrumTexture tex1, SpectrumTexture tex2, Vector3f dir) : tex1(tex1), tex2(tex2), dir(dir) {} SampledSpectrum Evaluate(TextureEvalContext ctx, SampledWavelengths lambda) const { Float amt = AbsDot(ctx.n, dir); SampledSpectrum t1, t2; if (amt != 0) t1 = tex1.Evaluate(ctx, lambda); if (amt != 1) t2 = tex2.Evaluate(ctx, lambda); return amt * t1 + (1 - amt) * t2; } static SpectrumDirectionMixTexture *Create(const Transform &renderFromTexture, const TextureParameterDictionary &parameters, SpectrumType spectrumType, const FileLoc *loc, Allocator alloc); std::string ToString() const;
private: <<SpectrumDirectionMixTexture Private Members>> 
SpectrumTexture tex1, tex2; Vector3f dir;
};

<<SpectrumDirectionMixTexture Private Members>>= 
SpectrumTexture tex1, tex2; Vector3f dir;

Figure 10.13: Use of the SpectrumDirectionMixTexture in the Kroken Scene. The texture is used to select between one image texture that uses a planar projection to specify the cover image of the magazine and another that uses a different planar projection for its spine. (Scene courtesy of Angelo Ferretti.)

If the normal is coincident with the specified direction, tex1 is returned; if it is perpendicular, then tex2 is. Otherwise, the two textures are blended. Figure 10.13 shows an example of the use of this texture.

<<SpectrumDirectionMixTexture Public Methods>>= 
SampledSpectrum Evaluate(TextureEvalContext ctx, SampledWavelengths lambda) const { Float amt = AbsDot(ctx.n, dir); SampledSpectrum t1, t2; if (amt != 0) t1 = tex1.Evaluate(ctx, lambda); if (amt != 1) t2 = tex2.Evaluate(ctx, lambda); return amt * t1 + (1 - amt) * t2; }