3D transformation

3D point is represented as a 1 x 4 matrix called homogeneous co-ordinate representation, [x, y, z, 1]. The reason for this unusual representation is to generalize transformation matrices (if that means anything to you). It's not really important at this point to know why things are done this way, but you do need to know that they just are.

However, I can show you right away how neat it is to shift such points around using matrices. Say I want to move [x, y, z, 1] to [x+5, y+2, z+1, 1], the procedure can be represented by

  [x, y, z, 1] * [1 0 0 0
                  0 1 0 0
                  0 0 1 0
                  5 2 1 1]

Use what you know about matrices from high-school to work out that multiplication. That 4 x 4 matrix is called a translation matrix. You can use the same computation to scale the point relative to the origin. For example, if you want to scale the point to half its current location relative to the origin, just do a

  [x, y, z, 1] * [0.5 0   0   0
                  0   0.5 0   0
                  0   0   0.5 0
                  0   0   0   1]

Rotation around the x axis is represented by

  [1  0          0          0
   0  cos(angle) sin(angle) 0
   0 -sin(angle) cos(angle) 0
   0  0          0          1]

Rotation around the y axis

  [cos(angle) 0 -sin(angle) 0
   0          1  0          0
   sin(angle) 0  cos(angle) 0
   0          0  0          1]

And rotation around the z axis

  [ cos(angle) sin(angle) 0 0
   -sin(angle) cos(angle) 0 0
    0          0          1 0
    0          0          0 1]

You can string these matrices together to create any transformation combination. For example, you can bring about a x-scaling followed by a y-rotation followed by a z-translation with

  [x y z 1] * [0.5 0 0 0  * [cos(angle) 0 -sin(angle) 0  *  [1 0 0   0
               0   1 0 0     0          1  0          0      0 1 0   0
               0   0 1 0     sin(angle) 0  cos(angle) 0      0 0 1   0
               0   0 0 1]    0          0  0          1]     0 0 5.6 1]

As matrix multiplication is associative -- ie. M * (N * O) == (M * N) * O -- you could first multiply the three transform matrices together to form a composite matrix and then apply it to the point. Note that matrix multiplication is not commutative -- ie. M * N != N * M; that's why a rotation followed by a translation doesn't yield the same result as a translation followed by a rotation.

You can use MEL to help you generate the composite matrix. Take the last series of transformation for example, you can open Maya in no-UI mode (maya -prompt) and run the following procedure to get the composite matrix:

  float $angle; matrix $matrix1[4][4], $matrix2[4][4], $matrix3[4][4], $finmatrix[4][4];
  $angle = 30; //say we want a rotation angle of 30 degrees
  $matrix1 = <<0.5, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1>>;
  $matrix2 = <<cosd($angle), 0, -sind($angle), 0; 0, 1, 0, 0; sind($angle), 0, cosd($angle), 0; 0, 0, 0, 1>>;
  $matrix3 = <<1, 0, 0, 0; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 5.6, 1>>;
  $finmatrix = $matrix1 * $matrix2 * $matrix3;

We've just found a use for Maya's matrix type!

Mental Ray provides a bunch of convenient functions to help you work with matrices and points/vectors/normals. Check them out in the help documents. One thing to note is that you shouldn't use the same transformation function to transform a bunch of points, vectors and normals -- use the *_point_* functions for point transformations, *_vector_* functions for vector transformations, and *_normal_* functions for normal transformations. In particular, take note that normal vectors are transformed by the inverse transpose (M-1)T of the transformation that transforms points. The inverse of a matrix is one that when multiplied with the original matrix will yield an identity matrix (eg. [1 0 0 0, 0 1 0 0, 0 0 1 0, 0 0 0 1]). In the case of transformation, you can think of it as a reverse transformation that returns a transformed point back to the original position. All the usual transformation matrices have inverses, except those that scale the object to 0 volume. The transpose of a matrix is the matrix with its rows and columns swapped. For example, the transpose of [1 2 3, 4 5 6, 7 8 9] is [1 4 7, 2 5 8, 3 6 9].

One more thing before we leave the subject -- pivots. So far we've only seen how to rotate and scale a point around the space origin, but how do you rotate/scale around other points, like oh... (3, 7, 9)? Well, first you move the point by (-3, -7, -9), then you rotate/scale the moved point around the origin as usual, and then you move the rotated/scaled point back by (3, 7, 9). Get it? That's machine thinking for you :-) You'll get used to it (I hope).


Spaces

Central to any shader-writing enterprise is the concept of spaces. A space is a named origin from which points and vectors are measured. For example, a point's co-ordinates relative to the scene world origin (a space called, naturally enough, the world space) are different from its co-ordinates relative to the camera (called, guess what, camera space).

MR defines four 3D spaces, namely

and two 2D spaces,

Usually you should put all your points and vectors into the same space before operating on them.

OK, but what is the meaning of putting a point in world space vs putting it in object space? What effects would the different space choices lead to?

Imagine the case of defining surface points relative to the world origin and using these points to calculate a procedural texture; if the object moves, its surface point co-ordinates will change, and the texture will also change accordingly. But normally you would want the texture to stay the same. To achieve this, put the surface points in object space. When an object undergoes a transformation, it's the space that's moved, not the vertices, so the vertex co-ordinates in object space remain constant.

MR provides a bunch of functions to help you transform points, vectors or normals around various spaces. See the MR manual for details.


Something about vectors

If there's only one thing you can bring along in your exploration of the wonderful world of shaders, it's gotta be the understanding of vectors. Here's some ration to get you going:

The sum of two vectors:

+=

Subtracting a vector v2 from a vector v1 is simply a matter of reversing the direction of v2 and then adding the reversed vector to v1.

When you multiply a vector by a scalar number, you're just multiplying the length of the vector. The vector's direction remains unchanged after the operation.

To normalize a vector means to set its length to one unit.

The dot product of two vectors, v1 . v2, yields a scalar value equal to length(v1)*length(v2)*cos(angle between them). When both vectors are normalized, their dot product simply yields the cosine of the angle between them.

The cross product of two vectors, v1 x v2, yields a vector that's perpendicular to both original vectors. This new vector's length is equal to length(v1)*length(v2)* sin(angle between them) and its direction follows the right-hand rule in MR's usual space orientation.

The illustration below shows the projection of one vector on to another vector.

v1 . v2 = length(v1) * length(v2) * cos(angle)

cos(angle) = length(v1's projection on v2) / length(v1)

Therefore length(v1's projection on v2) = (v1 . v2) / length(v2), or

v1's projection on v2 = normalize(v2) * (v1 . v2) / length(v2)



We can put these knowledge to some nifty uses. For example, given a vector and a plane, we can find out where that vector, when extrapolated, would hit the plane:

Consider this: we can define a plane by its center position O and its normal N. To find the hit point H, we really just need to find out the factor to multiply P by in order to extend it to H. This factor is equal to the length of projection of H on N divided by the length of projection of P on N. But the projection of H on N is the same as the projection of O on N. Therefore this factor is ((O.N)/length(N))/((P.N)/length(N)), or (O.N)/(P.N).