10.2 Texture Coordinate Generation

Almost all the textures in this chapter are functions that take a 2D or 3D coordinate and return a texture value. Sometimes there are obvious ways to choose these texture coordinates; for parametric surfaces, such as the quadrics in Chapter 6, there is a natural 2D left-parenthesis u comma v right-parenthesis parameterization of the surface, and for all types of surfaces the shading point normal p Subscript is a natural choice for a 3D coordinate.

In other cases, there is no natural parameterization, or the natural parameterization may be undesirable. For instance, the left-parenthesis u comma v right-parenthesis values near the poles of spheres are severely distorted. Therefore, this section introduces classes that provide an interface to different techniques for generating these parameterizations as well as a number of implementations of them.

The Texture implementations later in this chapter store a tagged pointer to a 2D or 3D mapping function as appropriate and use it to compute the texture coordinates at each point at which they are evaluated. Thus, it is easy to add new mappings to the system without having to modify all the Texture implementations, and different mappings can be used for different textures associated with the same surface. In pbrt, we will use the convention that 2D texture coordinates are denoted by left-parenthesis s comma t right-parenthesis ; this helps make clear the distinction between the intrinsic left-parenthesis u comma v right-parenthesis parameterization of the underlying surface and the possibly different coordinate values used for texturing.

TextureMapping2D defines the interface for 2D texture coordinate generation. It is defined in the file base/texture.h. The implementations of the texture mapping classes are in textures.h and textures.cpp.

<<TextureMapping2D Definition>>= 
class TextureMapping2D : public TaggedPointer<UVMapping, SphericalMapping, CylindricalMapping, PlanarMapping> { public: <<TextureMapping2D Interface>> 
using TaggedPointer::TaggedPointer; PBRT_CPU_GPU TextureMapping2D(TaggedPointer<UVMapping, SphericalMapping, CylindricalMapping, PlanarMapping> tp) : TaggedPointer(tp) {} static TextureMapping2D Create(const ParameterDictionary &parameters, const Transform &renderFromTexture, const FileLoc *loc, Allocator alloc); TexCoord2D Map(TextureEvalContext ctx) const;
};

The TextureMapping2D interface consists of a single method, Map(). It is given a TextureEvalContext that stores relevant geometric information at the shading point and returns a small structure, TexCoord2D, that stores the left-parenthesis s comma t right-parenthesis texture coordinates and estimates for the change in left-parenthesis s comma t right-parenthesis with respect to pixel x and y coordinates so that textures that use the mapping can determine the left-parenthesis s comma t right-parenthesis sampling rate and filter accordingly.

<<TextureMapping2D Interface>>= 
TexCoord2D Map(TextureEvalContext ctx) const;

<<TexCoord2D Definition>>= 
struct TexCoord2D { Point2f st; Float dsdx, dsdy, dtdx, dtdy; };

In previous versions of pbrt, the Map() interface was defined to take a complete SurfaceInteraction; the TextureEvalContext structure did not exist. For this version, we have tightened up the interface to only include specific values that are useful for texture coordinate generation. This change was largely motivated by the GPU rendering path: with the CPU renderer, all the relevant information is already at hand in the functions that call the Map() methods; most likely the SurfaceInteraction is already in the CPU cache. On the GPU, the necessary values have to be read from off-chip memory. TextureEvalContext makes it possible for the GPU renderer to only read the necessary values from memory, which in turn has measurable performance benefits.

TextureEvalContext provides three constructors, not included here. Two initialize the various fields using corresponding values from either an Interaction or a SurfaceInteraction and the third allows specifying them directly.

<<TextureEvalContext Definition>>= 
struct TextureEvalContext { <<TextureEvalContext Public Methods>> 
TextureEvalContext() = default; PBRT_CPU_GPU TextureEvalContext(const Interaction &intr) : p(intr.p()), uv(intr.uv) {} PBRT_CPU_GPU TextureEvalContext(const SurfaceInteraction &si) : p(si.p()), dpdx(si.dpdx), dpdy(si.dpdy), n(si.n), uv(si.uv), dudx(si.dudx), dudy(si.dudy), dvdx(si.dvdx), dvdy(si.dvdy), faceIndex(si.faceIndex) {} PBRT_CPU_GPU TextureEvalContext(Point3f p, Vector3f dpdx, Vector3f dpdy, Normal3f n, Point2f uv, Float dudx, Float dudy, Float dvdx, Float dvdy, int faceIndex) : p(p), dpdx(dpdx), dpdy(dpdy), n(n), uv(uv), dudx(dudx), dudy(dudy), dvdx(dvdx), dvdy(dvdy), faceIndex(faceIndex) {} std::string ToString() const;
Point3f p; Vector3f dpdx, dpdy; Normal3f n; Point2f uv; Float dudx = 0, dudy = 0, dvdx = 0, dvdy = 0; };

10.2.1 left-parenthesis u comma v right-parenthesis Mapping

UVMapping uses the left-parenthesis u comma v right-parenthesis coordinates in the TextureEvalContext to compute the texture coordinates, optionally scaling and offsetting their values in each dimension.

<<UVMapping Definition>>= 
class UVMapping { public: <<UVMapping Public Methods>> 
UVMapping(Float su = 1, Float sv = 1, Float du = 0, Float dv = 0) : su(su), sv(sv), du(du), dv(dv) {} std::string ToString() const; TexCoord2D Map(TextureEvalContext ctx) const { <<Compute texture differentials for 2D left-parenthesis u comma v right-parenthesis mapping>> 
Float dsdx = su * ctx.dudx, dsdy = su * ctx.dudy; Float dtdx = sv * ctx.dvdx, dtdy = sv * ctx.dvdy;
Point2f st(su * ctx.uv[0] + du, sv * ctx.uv[1] + dv); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy}; }
private: Float su, sv, du, dv; };

<<UVMapping Public Methods>>= 
UVMapping(Float su = 1, Float sv = 1, Float du = 0, Float dv = 0) : su(su), sv(sv), du(du), dv(dv) {}

The scale-and-shift computation to compute left-parenthesis s comma t right-parenthesis coordinates is straightforward:

<<UVMapping Public Methods>>+= 
TexCoord2D Map(TextureEvalContext ctx) const { <<Compute texture differentials for 2D left-parenthesis u comma v right-parenthesis mapping>> 
Float dsdx = su * ctx.dudx, dsdy = su * ctx.dudy; Float dtdx = sv * ctx.dvdx, dtdy = sv * ctx.dvdy;
Point2f st(su * ctx.uv[0] + du, sv * ctx.uv[1] + dv); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy}; }

For a general 2D mapping function f left-parenthesis u comma v right-parenthesis right-arrow left-parenthesis s comma t right-parenthesis , the screen-space derivatives of s and t are given by the chain rule:

StartFraction partial-differential left-parenthesis s comma t right-parenthesis Over partial-differential left-parenthesis x comma y right-parenthesis EndFraction equals Start 2 By 2 Matrix 1st Row 1st Column StartFraction partial-differential s Over partial-differential left-parenthesis u comma v right-parenthesis EndFraction dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential x EndFraction 2nd Column StartFraction partial-differential s Over partial-differential left-parenthesis u comma v right-parenthesis EndFraction dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential y EndFraction 2nd Row 1st Column StartFraction partial-differential t Over partial-differential left-parenthesis u comma v right-parenthesis EndFraction dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential x EndFraction 2nd Column StartFraction partial-differential t Over partial-differential left-parenthesis u comma v right-parenthesis EndFraction dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential y EndFraction EndMatrix equals Start 2 By 2 Matrix 1st Row 1st Column StartFraction normal d s Over normal d x EndFraction 2nd Column StartFraction normal d s Over normal d y EndFraction 2nd Row 1st Column StartFraction normal d t Over normal d x EndFraction 2nd Column StartFraction normal d t Over normal d y EndFraction EndMatrix period

Note that the TextureEvalContext provides the values partial-differential left-parenthesis u comma v right-parenthesis slash partial-differential left-parenthesis x comma y right-parenthesis .

In this case, f left-parenthesis u comma v right-parenthesis equals left-parenthesis s Subscript u Baseline u plus d Subscript u Baseline comma s Subscript v Baseline v plus d Subscript v Baseline right-parenthesis and so

StartFraction partial-differential left-parenthesis s comma t right-parenthesis Over partial-differential left-parenthesis x comma y right-parenthesis EndFraction equals Start 2 By 2 Matrix 1st Row 1st Column left-parenthesis s Subscript u Baseline comma 0 right-parenthesis dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential x EndFraction 2nd Column left-parenthesis s Subscript u Baseline comma 0 right-parenthesis dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential y EndFraction 2nd Row 1st Column left-parenthesis 0 comma s Subscript v Baseline right-parenthesis dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential x EndFraction 2nd Column left-parenthesis 0 comma s Subscript v Baseline right-parenthesis dot StartFraction partial-differential left-parenthesis u comma v right-parenthesis Over partial-differential y EndFraction EndMatrix equals Start 2 By 2 Matrix 1st Row 1st Column s Subscript u Baseline StartFraction normal d u Over normal d x EndFraction 2nd Column s Subscript u Baseline StartFraction normal d u Over normal d y EndFraction 2nd Row 1st Column s Subscript v Baseline StartFraction normal d v Over normal d x EndFraction 2nd Column s Subscript v Baseline StartFraction normal d v Over normal d y EndFraction EndMatrix period

We will skip past the straightforward fragment that implements Equation (10.7) to initialize dsdx, dsdy, dtdx, and dtdy.

10.2.2 Spherical Mapping

Figure 10.9: Use of the SphericalMapping in the Kroken Scene. (a) Visualization of the resulting left-parenthesis u comma v right-parenthesis parameterization. (b) Effect of using the SphericalMapping to apply a texture. Note that although the shape is spherical, it is modeled with a triangle mesh, to which the SphericalMapping is applied. (Scene courtesy of Angelo Ferretti.)

Another useful mapping effectively wraps a sphere around the object. Each point is projected along the vector from the sphere’s center through the point on to the sphere’s surface. Since this mapping is based on spherical coordinates, Equation (3.8) can be applied, with the angles it returns remapped to left-bracket 0 comma 1 right-bracket :

f left-parenthesis normal p Subscript Baseline right-parenthesis equals left-parenthesis StartFraction 1 Over 2 pi EndFraction left-parenthesis pi plus arc tangent StartFraction normal p Subscript Baseline Subscript y Baseline Over normal p Subscript Baseline Subscript x Baseline EndFraction right-parenthesis comma StartFraction 1 Over pi EndFraction arc cosine StartFraction normal p Subscript Baseline Subscript z Baseline Over double-vertical-bar normal p Subscript Baseline Subscript x Superscript 2 Baseline plus normal p Subscript Baseline Subscript y Superscript 2 Baseline plus normal p Subscript Baseline Subscript z Superscript 2 Baseline double-vertical-bar EndFraction right-parenthesis period

Figure 10.9 shows the use of this mapping with an object in the Kroken scene.

The SphericalMapping further stores a transformation that is applied to points before this mapping is performed; this effectively allows the mapping sphere to be arbitrarily positioned and oriented with respect to the object.

<<SphericalMapping Definition>>= 
class SphericalMapping { public: <<SphericalMapping Public Methods>> 
SphericalMapping(const Transform &textureFromRender) : textureFromRender(textureFromRender) {} std::string ToString() const; TexCoord2D Map(TextureEvalContext ctx) const { Point3f pt = textureFromRender(ctx.p); <<Compute partial-differential s slash partial-differential normal p Subscript and partial-differential t slash partial-differential normal p Subscript for spherical mapping>> 
Float x2y2 = Sqr(pt.x) + Sqr(pt.y); Float sqrtx2y2 = std::sqrt(x2y2); Vector3f dsdp = Vector3f(-pt.y, pt.x, 0) / (2 * Pi * x2y2); Vector3f dtdp = 1 / (Pi * (x2y2 + Sqr(pt.z))) * Vector3f(pt.x * pt.z / sqrtx2y2, pt.y * pt.z / sqrtx2y2, -sqrtx2y2);
<<Compute texture coordinate differentials for spherical mapping>> 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(dsdp, dpdx), dsdy = Dot(dsdp, dpdy); Float dtdx = Dot(dtdp, dpdx), dtdy = Dot(dtdp, dpdy);
<<Return left-parenthesis s comma t right-parenthesis texture coordinates and differentials based on spherical mapping>> 
Vector3f vec = Normalize(pt - Point3f(0,0,0)); Point2f st(SphericalTheta(vec) * InvPi, SphericalPhi(vec) * Inv2Pi); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy};
}
private: <<SphericalMapping Private Members>> 
Transform textureFromRender;
};

<<SphericalMapping Private Members>>= 
Transform textureFromRender;

The Map() function starts by computing the texture-space point pt.

<<SphericalMapping Public Methods>>= 
TexCoord2D Map(TextureEvalContext ctx) const { Point3f pt = textureFromRender(ctx.p); <<Compute partial-differential s slash partial-differential normal p Subscript and partial-differential t slash partial-differential normal p Subscript for spherical mapping>> 
Float x2y2 = Sqr(pt.x) + Sqr(pt.y); Float sqrtx2y2 = std::sqrt(x2y2); Vector3f dsdp = Vector3f(-pt.y, pt.x, 0) / (2 * Pi * x2y2); Vector3f dtdp = 1 / (Pi * (x2y2 + Sqr(pt.z))) * Vector3f(pt.x * pt.z / sqrtx2y2, pt.y * pt.z / sqrtx2y2, -sqrtx2y2);
<<Compute texture coordinate differentials for spherical mapping>> 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(dsdp, dpdx), dsdy = Dot(dsdp, dpdy); Float dtdx = Dot(dtdp, dpdx), dtdy = Dot(dtdp, dpdy);
<<Return left-parenthesis s comma t right-parenthesis texture coordinates and differentials based on spherical mapping>> 
Vector3f vec = Normalize(pt - Point3f(0,0,0)); Point2f st(SphericalTheta(vec) * InvPi, SphericalPhi(vec) * Inv2Pi); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy};
}

For a mapping function based on a 3D point normal p Subscript , the generalization of Equation (10.6) is

StartFraction partial-differential left-parenthesis s comma t right-parenthesis Over partial-differential left-parenthesis x comma y right-parenthesis EndFraction equals Start 2 By 2 Matrix 1st Row 1st Column StartFraction partial-differential s Over partial-differential normal p Subscript Baseline EndFraction dot StartFraction partial-differential normal p Subscript Baseline Over partial-differential x EndFraction 2nd Column StartFraction partial-differential s Over partial-differential normal p Subscript Baseline EndFraction dot StartFraction partial-differential normal p Subscript Baseline Over partial-differential y EndFraction 2nd Row 1st Column StartFraction partial-differential t Over partial-differential normal p Subscript Baseline EndFraction dot StartFraction partial-differential normal p Subscript Baseline Over partial-differential x EndFraction 2nd Column StartFraction partial-differential t Over partial-differential normal p Subscript Baseline EndFraction dot StartFraction partial-differential normal p Subscript Baseline Over partial-differential y EndFraction EndMatrix equals Start 2 By 2 Matrix 1st Row 1st Column StartFraction normal d s Over normal d x EndFraction 2nd Column StartFraction normal d s Over normal d y EndFraction 2nd Row 1st Column StartFraction normal d t Over normal d x EndFraction 2nd Column StartFraction normal d t Over normal d y EndFraction EndMatrix period

Taking the partial derivatives of the mapping function, Equation (10.8), we can find

StartLayout 1st Row 1st Column StartFraction partial-differential s Over partial-differential normal p Subscript Baseline EndFraction 2nd Column equals StartFraction 1 Over 2 pi left-parenthesis x squared plus y squared right-parenthesis EndFraction left-parenthesis negative y comma x comma 0 right-parenthesis 2nd Row 1st Column StartFraction partial-differential t Over partial-differential normal p Subscript Baseline EndFraction 2nd Column equals StartFraction 1 Over pi left-parenthesis x squared plus y squared plus z squared right-parenthesis EndFraction left-parenthesis StartFraction x z Over StartRoot x squared plus y squared EndRoot EndFraction comma StartFraction y z Over StartRoot x squared plus y squared EndRoot EndFraction comma minus StartRoot x squared plus y squared EndRoot right-parenthesis period EndLayout

These quantities are computed using the texture-space position pt.

<<Compute partial-differential s slash partial-differential normal p Subscript and partial-differential t slash partial-differential normal p Subscript for spherical mapping>>= 
Float x2y2 = Sqr(pt.x) + Sqr(pt.y); Float sqrtx2y2 = std::sqrt(x2y2); Vector3f dsdp = Vector3f(-pt.y, pt.x, 0) / (2 * Pi * x2y2); Vector3f dtdp = 1 / (Pi * (x2y2 + Sqr(pt.z))) * Vector3f(pt.x * pt.z / sqrtx2y2, pt.y * pt.z / sqrtx2y2, -sqrtx2y2);

The final differentials are then found using the four dot products from Equation (10.9).

<<Compute texture coordinate differentials for spherical mapping>>= 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(dsdp, dpdx), dsdy = Dot(dsdp, dpdy); Float dtdx = Dot(dtdp, dpdx), dtdy = Dot(dtdp, dpdy);

Finally, previously defined spherical geometry utility functions compute the mapping of Equation (10.8).

<<Return left-parenthesis s comma t right-parenthesis texture coordinates and differentials based on spherical mapping>>= 
Vector3f vec = Normalize(pt - Point3f(0,0,0)); Point2f st(SphericalTheta(vec) * InvPi, SphericalPhi(vec) * Inv2Pi); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy};

10.2.3 Cylindrical Mapping

Figure 10.10: Use of the Cylindrical Texture Mapping. (a) Visualization of the left-parenthesis u comma v right-parenthesis mapping from the CylindricalMapping. (b) Kettle with texture maps applied. (c) Scratch texture that is applied using the cylindrical texture mapping. (Scene courtesy of Angelo Ferretti.)

The cylindrical mapping effectively wraps a cylinder around the object and then uses the cylinder’s parameterization.

f left-parenthesis normal p Subscript Baseline right-parenthesis equals left-parenthesis StartFraction 1 Over 2 pi EndFraction left-parenthesis pi plus arc tangent StartFraction normal p Subscript Baseline Subscript y Baseline Over normal p Subscript Baseline Subscript x Baseline EndFraction right-parenthesis comma normal p Subscript Baseline Subscript z Baseline right-parenthesis period

See Figure 10.10 for an example of its use.

Note that the t texture coordinate it returns is not necessarily between 0 and 1; the mapping should either be scaled in z so that the object being textured has t element-of left-bracket 0 comma 1 right-bracket or the texture being used should return results for coordinates outside that range that match the desired result.

<<CylindricalMapping Definition>>= 
class CylindricalMapping { public: <<CylindricalMapping Public Methods>> 
CylindricalMapping(const Transform &textureFromRender) : textureFromRender(textureFromRender) {} std::string ToString() const; TexCoord2D Map(TextureEvalContext ctx) const { Point3f pt = textureFromRender(ctx.p); <<Compute texture coordinate differentials for cylinder left-parenthesis u comma v right-parenthesis mapping>> 
Float x2y2 = Sqr(pt.x) + Sqr(pt.y); Vector3f dsdp = Vector3f(-pt.y, pt.x, 0) / (2 * Pi * x2y2), dtdp = Vector3f(0, 0, 1); Vector3f dpdx = textureFromRender(ctx.dpdx), dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(dsdp, dpdx), dsdy = Dot(dsdp, dpdy); Float dtdx = Dot(dtdp, dpdx), dtdy = Dot(dtdp, dpdy);
Point2f st((Pi + std::atan2(pt.y, pt.x)) * Inv2Pi, pt.z); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy}; }
private: <<CylindricalMapping Private Members>> 
Transform textureFromRender;
};

CylindricalMapping also supports a transformation to orient the mapping cylinder.

<<CylindricalMapping Private Members>>= 
Transform textureFromRender;

Because the s texture coordinate is computed in the same way as it is with the spherical mapping, the cylindrical mapping’s partial-differential s slash partial-differential normal p Subscript matches the sphere’s in Equation (10.10). The partial derivative in t can easily be seen to be partial-differential t slash partial-differential normal p Subscript Baseline equals left-parenthesis 0 comma 0 comma 1 right-parenthesis . Since the cylindrical mapping function and derivative computation are only slight variations on the spherical mapping’s, we will not include the implementation of its Map() function here.

10.2.4 Planar Mapping

Another classic mapping method is planar mapping. The point is effectively projected onto a plane; a 2D parameterization of the plane then gives texture coordinates for the point. For example, a point normal p Subscript might be projected onto the z equals 0 plane to yield texture coordinates given by s equals normal p Subscript Baseline Subscript x and t equals normal p Subscript Baseline Subscript y .

One way to define such a parameterized plane is with two nonparallel vectors bold v Subscript s and bold v Subscript t and offsets d Subscript s and d Subscript t . The texture coordinates are given by the coordinates of the point with respect to the plane’s coordinate system, which are computed by taking the dot product of the vector from the point to the origin with each vector bold v Subscript s and bold v Subscript t and then adding the corresponding offset:

f left-parenthesis normal p Subscript Baseline right-parenthesis equals left-parenthesis left-parenthesis normal p Subscript Baseline minus left-parenthesis 0 comma 0 comma 0 right-parenthesis right-parenthesis dot bold v Subscript s Baseline plus d Subscript s Baseline comma left-parenthesis normal p Subscript Baseline minus left-parenthesis 0 comma 0 comma 0 right-parenthesis right-parenthesis dot bold v Subscript t Baseline plus d Subscript t Baseline right-parenthesis period

<<PlanarMapping Definition>>= 
class PlanarMapping { public: <<PlanarMapping Public Methods>> 
PlanarMapping(const Transform &textureFromRender, Vector3f vs, Vector3f vt, Float ds, Float dt) : textureFromRender(textureFromRender), vs(vs), vt(vt), ds(ds), dt(dt) {} TexCoord2D Map(TextureEvalContext ctx) const { Vector3f vec(textureFromRender(ctx.p)); <<Initialize partial derivatives of planar mapping left-parenthesis s comma t right-parenthesis coordinates>> 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(vs, dpdx), dsdy = Dot(vs, dpdy); Float dtdx = Dot(vt, dpdx), dtdy = Dot(vt, dpdy);
Point2f st(ds + Dot(vec, vs), dt + Dot(vec, vt)); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy}; } std::string ToString() const;
private: <<PlanarMapping Private Members>> 
Transform textureFromRender; Vector3f vs, vt; Float ds, dt;
};

A straightforward constructor, not included here, initializes the following member variables.

<<PlanarMapping Private Members>>= 
Transform textureFromRender; Vector3f vs, vt; Float ds, dt;

<<PlanarMapping Public Methods>>= 
TexCoord2D Map(TextureEvalContext ctx) const { Vector3f vec(textureFromRender(ctx.p)); <<Initialize partial derivatives of planar mapping left-parenthesis s comma t right-parenthesis coordinates>> 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(vs, dpdx), dsdy = Dot(vs, dpdy); Float dtdx = Dot(vt, dpdx), dtdy = Dot(vt, dpdy);
Point2f st(ds + Dot(vec, vs), dt + Dot(vec, vt)); return TexCoord2D{st, dsdx, dsdy, dtdx, dtdy}; }

The planar mapping differentials can be computed directly using the partial derivatives of the mapping function, which are easily found. For example, the partial derivative of the s texture coordinate with respect to screen-space x is just partial-differential s slash partial-differential x equals left-parenthesis bold v Subscript s Baseline dot partial-differential normal p slash partial-differential x right-parenthesis .

<<Initialize partial derivatives of planar mapping left-parenthesis s comma t right-parenthesis coordinates>>= 
Vector3f dpdx = textureFromRender(ctx.dpdx); Vector3f dpdy = textureFromRender(ctx.dpdy); Float dsdx = Dot(vs, dpdx), dsdy = Dot(vs, dpdy); Float dtdx = Dot(vt, dpdx), dtdy = Dot(vt, dpdy);

10.2.5 3D Mapping

We will also define a TextureMapping3D class that defines the interface for generating 3D texture coordinates.

<<TextureMapping3D Definition>>= 
class TextureMapping3D : public TaggedPointer<PointTransformMapping> { public: <<TextureMapping3D Interface>> 
using TaggedPointer::TaggedPointer; PBRT_CPU_GPU TextureMapping3D(TaggedPointer<PointTransformMapping> tp) : TaggedPointer(tp) {} static TextureMapping3D Create(const ParameterDictionary &parameters, const Transform &renderFromTexture, const FileLoc *loc, Allocator alloc); TexCoord3D Map(TextureEvalContext ctx) const;
};

The Map() method it specifies returns a 3D point and partial derivative vectors in the form of a TexCoord3D structure.

<<TextureMapping3D Interface>>= 
TexCoord3D Map(TextureEvalContext ctx) const;

TexCoord3D parallels TexCoord2D, storing both the point and its screen-space derivatives.

<<TexCoord3D Definition>>= 
struct TexCoord3D { Point3f p; Vector3f dpdx, dpdy; };

The natural 3D mapping takes the rendering-space coordinate of the point and applies a linear transformation to it. This will often be a transformation that takes the point back to the primitive’s object space. Such a mapping is implemented by the PointTransformMapping class.

<<PointTransformMapping Definition>>= 
class PointTransformMapping { public: <<PointTransformMapping Public Methods>> 
PointTransformMapping(const Transform &textureFromRender) : textureFromRender(textureFromRender) {} std::string ToString() const; TexCoord3D Map(TextureEvalContext ctx) const { return TexCoord3D{textureFromRender(ctx.p), textureFromRender(ctx.dpdx), textureFromRender(ctx.dpdy)}; }
private: Transform textureFromRender; };

Because it applies a linear transformation, the differential change in texture coordinates can be found by applying the same transformation to the partial derivatives of position.

<<PointTransformMapping Public Methods>>= 
TexCoord3D Map(TextureEvalContext ctx) const { return TexCoord3D{textureFromRender(ctx.p), textureFromRender(ctx.dpdx), textureFromRender(ctx.dpdy)}; }