Cg Programming/Applying Matrix Transformations
Applying the conventional vertex transformations (see Section “Vertex Transformations”) or any other transformations that are represented by matrices in shaders is usually accomplished by specifying the corresponding matrix in a uniform variable of the shader and then multiplying the matrix with a vector. There are, however, some differences in the details. Here, we discuss the transformation of points (i.e. 4D vectors with a 4th coordinate equal to 1), the transformation of directions (i.e. vectors in the strict sense: 3D vectors or 4D vectors with a 4th coordinate equal to 0), and the transformation of surface normal vectors (i.e. vectors that specify a direction that is orthogonal to a plane).
This section assumes some knowledge of the syntax of Cg as described in Section “Vector and Matrix Operations”.
For points, transformations usually are represented by a 4×4 matrice since they might include a translation by a 3D vector t in the 4th column:
(Projection matrices will also include additional values unequal to 0 in the last row.)
Three-dimensional points are represented by four-dimensional vectors with the 4th coordinate equal to 1:
In order to apply the transformation, the matrix is multiplied to the vector :
The Cg code to apply a 4×4 matrix to a point represented by a 4D vector is straightforward:
float4x4 mymatrix; float4 point; float4 transformed_point = mul(mymatrix, point);
If a transposed matrix is given, one could apply the function
transpose to compute the required matrix. However, it is more common to multiply the vector from the left to the transposed matrix as mentioned in Section “Vector and Matrix Operations”:
float4x4 matrix_transpose; float4 point; float4 transformed_point = mul(point, matrix_transpose);
Directions in three dimensions are represented either by a 3D vector or by a 4D vector with 0 as the fourth coordinate. (One can think of them as points at infinity; similar to a point at the horizon of which we cannot tell the position in space but only the direction in which to find it.)
In the case of a 3D vector, we can either transform it by multiplying it with a 3×3 matrix:
float3x3 mymatrix; float3 direction; float3 transformed_direction = mul(mymatrix, direction);
or with a 4×4 matrix, if we convert the 3D vector to a 4D vector with a 4th coordinate equal to 0:
float4x4 mymatrix; float3 direction; float4 transformed_direction = mul(mymatrix, float4(direction, 0.0));
Alternatively, the 4×4 matrix can also be converted to a 3×3 matrix.
On the other hand, a 4D vector can be multiplied directly with a 4×4 matrix. It can also be converted to a 3D vector in order to multiply it with a 3×3 matrix:
float3x3 mymatrix; float4 direction; // 4th component is 0 float4 transformed_direction = float4(mul(mymatrix, direction.xyz), 0.0);
Transforming Normal Vectors
Similarly to directions, surface normal vectors (or “normal vectors” for short) are represented by 3D vectors or 4D vectors with 0 as the 4th component. However, they transform differently. (The mathematical reason is that they represent something that is called a covector, covariant vector, one-form, or linear functional.)
To understand the transformation of normal vectors, consider the main feature of a surface normal vector: it is orthogonal to a surface. Of course, this feature should still be true under transformations, i.e. the transformed normal vector should be orthogonal to the transformed surface. If the surface is being represented locally by a tangent vector, this feature requires that a transformed normal vector is orthogonal to a transformed direction vector if the original normal vector is orthogonal to the original direction vector.
Mathematically spoken, a normal vector n is orthogonal to a direction vector v if their dot product is 0. It turns out that if v is transformed by a 3×3 matrix , the normal vector has to be transformed by the transposed inverse of : . We can easily test this by checking the dot product of the transformed normal vector n and the transformed direction vector v:
In the first step we have used = , then = , then , then (i.e. the identity matrix).
The calculation shows that the dot product of the transformed vectors is in fact the same as the dot product of the original vectors; thus, the transformed vectors are orthogonal if and only if the original vectors are orthogonal. Just the way it should be.
Thus, in order to transform normal vectors in Cg, the transposed inverse matrix is often specified as a uniform variable (together with the original matrix for the transformation of directions and points) and applied as any other transformation:
float3x3 matrix_inverse_transpose; float3 normal; float3 transformed_normal = mul(matrix_inverse_transpose, normal);
In the case of a 4×4 matrix, the normal vector can be cast to a 4D vector by appending 0:
float4x4 matrix_inverse_transpose; float3 normal; float3 transformed_normal = mul(matrix_inverse_transpose, float4(normal, 0.0)).xyz;
Alternatively, the matrix can be cast to a 3×3 matrix.
If the inverse matrix is known, the normal vector can be multiplied from the left to apply the transposed inverse matrix as mentioned in Section “Vector and Matrix Operations”. Thus, in order to multiply a normal vector with the transposed inverse matrix, we can multiply it from the left to the inverse matrix:
float3x3 matrix_inverse; float3 normal; float3 transformed_normal = mul(normal, matrix_inverse);
In the case of multiplying a 4×4 matrix to a 4D normal vector (from the left or the right), it should be made sure that the 4th component of the resulting vector is 0. In fact, in several cases it is necessary to discard the computed 4th component (for example by casting the result to a 3D vector):
float4x4 matrix_inverse; float4 normal; float4 transformed_normal = float4(mul(normal, matrix_inverse).xyz, 0.0);
Note that any normalization of the normal vector to unit length is not preserved by this transformation. Thus, normal vectors are often normalized to unit length after the transformation (e.g. with the built-in Cg function
Transforming Normal Vectors with an Orthogonal Matrix
A special case arises when the transformation matrix is orthogonal. In this case, the inverse of is the transposed matrix; thus, the transposed of the inverse of is the twice transposed matrix, which is the original matrix, i.e. for a orthogonal matrix :
Thus, in the case of orthogonal matrices, normal vectors are transformed with the same matrix as directions and points:
float3x3 mymatrix; // orthogonal matrix float3 normal; float3 transformed_normal = mul(mymatrix, normal);
Transforming Points with the Inverse Matrix
Sometimes it is necessary to apply the inverse transformation. In most cases, the best solution is to define another uniform variable for the inverse matrix and set the inverse matrix in the main application. The shader can then apply the inverse matrix like any other matrix. This is by far more efficient than computing the inverse in the shader.
There is, however, a special case: If the matrix is of the form presented above (i.e. the 4th row is (0,0,0,1)):
with an orthogonal 3×3 matrix (i.e. the row (or column) vectors of are normalized and orthogonal to each other; for example, this is usually the case for the view transformation, see Section “Vertex Transformations”), then the inverse matrix is given by (because for an orthogonal matrix ):
For the multiplication with a point that is represented by the 4D vector with the 3D vector p we get:
Note that the vector t is just the 4th column of the matrix , which could be accessed this way::
float4x4 m; float4 last_column = float4(m, m, m, m);
As mentioned above, multiplying a transposed matrix with a vector can be easily expressed by multiplying the vector from the left to the matrix and corresponds to a matrix-vector product of the transposed matrix with the corresponding column vector:
Using these features of Cg, the term (p - t) is easily and efficiently implemented as follows (note that the 4th component of the result has to be set to 1 separately):
float4x4 m; // upper, left 3x3 matrix is orthogonal; // 4th row is (0,0,0,1) float4 point; // 4th component is 1 float4 last_columm = float4(m, m, m, m); float4 point_transformed_with_inverse = float4(mul(point - last_column, m).xyz, 1.0);
Transforming Directions with the Inverse Matrix
As in the case of points, the best way to transform a direction with the inverse matrix is usually to compute the inverse matrix in the main application and communicate it to a shader via another uniform variable.
The exception is an orthogonal 3×3 matrix (i.e. all rows (or columns) are normalized and orthogonal to each other) or a 4×4 matrix of the form
where is an orthogonal 3×3 matrix. In these cases, the inverse matrix is equal to the transposed matrix .
As discussed above, the best way in Cg to multiply a vector with the transposed matrix is to multiply it from the left to the original matrix because this is interpreted as a product of a row vector with the original matrix, which corresponds to the product of the transposed matrix with the column vector:
Thus, the transformation with the transposed matrix (i.e. the inverse in case of a orthogonal matrix) is written as:
float4x4 mymatrix; // upper, left 3x3 matrix is orthogonal float4 direction; // 4th component is 0 float4 direction_transformed_with_inverse = float4(float3(mul(direction, mymatrix)), 0.0);
Note that the 4th component of the result has to be set to 0 separately since the 4th component of
mul(direction, mymatrix) is not meaningful for the transformation of directions. (It is, however, meaningful for the transformation of plane equations, which are not discussed here.)
The versions for 3x3 matrices and 3D vectors only require different cast operations between 3D and 4D vectors.
Transforming Normal Vectors with the Inverse Transformation
Suppose the inverse matrix is available, but the transformation corresponding to is required. Moreover, we want to apply this transformation to a normal vector. In this case, we can just apply the transpose of the inverse by multiplying the normal vector from the left to the inverse matrix (as discussed above):
float4x4 matrix_inverse; float3 normal; float3 transformed_normal = mul(float4(normal, 0.0), matrix_inverse).xyz;
(or by casting the matrix).
The transformation of normal vectors is also described in Section 12.1.2 of the “OpenGL 4.5 Compatibility Profile Specification” available at the Khronos OpenGL web site.
A more accessible description of the transformation of normal vectors is given in Appendix F of the free HTML version of the “OpenGL Programming Guide” available online.