Skip to content

Tutorial Matrix Transformation Order

Kai Burjack edited this page Feb 16, 2020 · 1 revision

The order in which to apply matrix transformations is often a source of confusion. How should I transform my model so that it looks correct and is at the right position in the world? Is it rotation, then translation and then scaling? Or was it translation and then scaling and then rotation?

This small tutorial should give insight into the two ways we can interpret the order of transformations. For example, let's look at the transformations translation (T), rotation (R) and scaling (S) in this order. We will also use the OpenGL convention of column-vectors and the M * v (matrix-vector) multiplication instead of the transposed convention that DirectX uses.

When chaining the three aforementioned transformations and applying them to a vector v, we will denote it like so: T * R * S * v. This can then be viewed from two sides. Either we read the transformations from right-to-left (starting with the vector v) by first applying scaling then rotation and then translation. Or we read the transformations from left-to-right by applying translation, then rotation and then scaling.

Let's write the transformations down in legacy (but easy to demonstrate) OpenGL matrix stack code:

glTranslatef(...);
glRotatef(...);
glScalef(...);

Alternatively, we can also build a JOML Matrix4f instance using these same transformations:

Matrix4f m = new Matrix4f()
  .translate(...)
  .rotate(...)
  .scale(...);
// upload this matrix to OpenGL

Both versions result in the same overall transformation, which we will now discuss.

Transforming the vector

When reading the transformations T * R * S * v from right-to-left or from last-executed-to-first-executed code, we can view the transformations as being applied to the vector v relative to a fixed coordinate system.

Let's start with a simple coordinate system that resembles OpenGL's normalized device coordiante system.

Let's assume the blue rectangle is a quad that we are drawing in OpenGL, e.g. via:

glBegin(GL_QUADS);
glVertex2i(-1, -1);
glVertex2i(+1, -1);
glVertex2i(+1, +1);
glVertex2i(-1, +1);
glEnd();

When we apply the first transformation (from right-to-left), we first scale the rectangle (let's say by a factor of 0.2). This yields the following result:

Next, we rotate the scaled-down quad, let's say by 45° in mathematical orientation, that is, counter-clockwise. The important thing here is that every following transformation works on the result of the previous transformation:

In this and the next sequence of images, the previous state of the transformations will show through as a semi-transparent background, to see where we came from.

Lastly, we apply a translation to the rotated and scaled quad, let's say 0.4 both in X and in Y:

When trying to come up with a model-transformation that positions, rotates and scales your model correctly, it is useful to use this "right-to-left" (or bottom-to-top) transformation order when assuming a world-coordinate system in which you manipulate your model by transforming it into its final position/orientiation/size.

Transforming the coordinate system

There is another point of view when looking at the series of transformations T * R * S * v, which when reading code will likely be the first reasonable order. Since code is executed top-to-bottom and matrix multiplications (in OpenGL and JOML) are applied by multiplying to the right of the current matrix, what does it then mean to read from left-to-right?

In essence, when reading from left-to-right, we are following the transformations applied to the source coordinate system (i.e. vector space). When we still use our normalized device coordinates system, this will then look like the following:

First, our untransformed coordinate system:

Now, we first apply the translation (by 0.4 in both X and Y) to this whole coordinate system:

We can see that the whole coordinate system changed (with respect to our initial NDC coordinate system) and that the origin of the coordinate system was shifted to the upper-right.

Next, let's apply the rotation (again by 45° counter-clockwise):

Remember that rotation is a linear transformation, and all linear transformations apply relative to the coordinate system's origin. Since we shifted the origin, the rotation will rotate around the new origin!

Next, we will apply the scaling transformation, again by a factor of 0.2:

When we now imagine drawing the unit quad (as we did before) in this coordinate system, with the rendered unit quad still represented by the blue quad in the image, we end up with the exact same rotated, translated and scaled quad as we did with the previous view on the reversed transformation order. The end result of both views are the same.