2.8 Applying Transformations
We can now define routines that perform the appropriate matrix multiplications to transform points and vectors. We will overload the function application operator to describe these transformations; this lets us write code like:
2.8.1 Points
The point transformation routine takes a point and implicitly represents it as the homogeneous column vector It then transforms the point by premultiplying this vector with the transformation matrix. Finally, it divides by to convert back to a nonhomogeneous point representation. For efficiency, this method skips the division by the homogeneous weight, , when , which is common for most of the transformations that will be used in pbrt—only the projective transformations defined in Chapter 6 will require this division.
2.8.2 Vectors
The transformations of vectors can be computed in a similar fashion. However, the multiplication of the matrix and the column vector is simplified since the implicit homogeneous coordinate is zero.
2.8.3 Normals
Normals do not transform in the same way that vectors do, as shown in Figure 2.14.
Although tangent vectors transform in the straightforward way, normals require special treatment. Because the normal vector and any tangent vector on the surface are orthogonal by construction, we know that
When we transform a point on the surface by some matrix , the new tangent vector at the transformed point is . The transformed normal should be equal to for some 44 matrix . To maintain the orthogonality requirement, we must have
This condition holds if , the identity matrix. Therefore, , and so and we see that normals must be transformed by the inverse transpose of the transformation matrix. This detail is one of the main reasons why Transforms maintain their inverses.
Note that this method does not explicitly compute the transpose of the inverse when transforming normals. It just indexes into the inverse matrix in a different order (compare to the code for transforming Vector3fs).
2.8.4 Rays
Transforming rays is conceptually straightforward: it’s a matter of transforming the constituent origin and direction and copying the other data members. (pbrt also provides a similar method for transforming RayDifferentials.)
The approach used in pbrt to manage floating-point round-off error introduces some subtleties that require a small adjustment to the transformed ray origin. The <<Offset ray origin to edge of error bounds>> fragment handles these details; it is defined in Section 3.9.4, where round-off error and pbrt’s mechanisms for dealing with it are discussed.
2.8.5 Bounding Boxes
The easiest way to transform an axis-aligned bounding box is to transform all eight of its corner vertices and then compute a new bounding box that encompasses those points. The implementation of this approach is shown below; one of the exercises for this chapter is to implement a technique to do this computation more efficiently.
2.8.6 Composition of Transformations
Having defined how the matrices representing individual types of transformations are constructed, we can now consider an aggregate transformation resulting from a series of individual transformations. Finally, we will see the real value of representing transformations with matrices.
Consider a series of transformations . We’d like to compute a new transformation such that applying gives the same result as applying each of , , and in reverse order; that is, . Such a transformation can be computed by multiplying the matrices of the transformations , , and together. In pbrt, we can write:
Then we can apply T to Point3fs p as usual, Point3f pp = T(p), instead of applying each transformation in turn: Point3f pp = A(B(C(p))).
We use the C++ * operator to compute the new transformation that results from postmultiplying a transformation with another transformation t2. In matrix multiplication, the th element of the resulting matrix is the inner product of the th row of the first matrix with the th column of the second.
The inverse of the resulting transformation is equal to the product of t2.mInv * mInv. This is a result of the matrix identity
2.8.7 Transformations and Coordinate System Handedness
Certain types of transformations change a left-handed coordinate system into a right-handed one, or vice versa. Some routines will need to know if the handedness of the source coordinate system is different from that of the destination. In particular, routines that want to ensure that a surface normal always points “outside” of a surface might need to flip the normal’s direction after transformation if the handedness changes.
Fortunately, it is easy to tell if handedness is changed by a transformation: it happens only when the determinant of the transformation’s upper-left 33 submatrix is negative.