Order of matrix multiplication

Started by
7 comments, last by CC Ricers 9 years, 1 month ago

Hi everyone.

I'm kind of new to game programming and am trying to wrap my head around transformation matrices. My problem is that any articles i find on this topic focus far too heavily on the underlying graphics API being used. I think i have a pretty solid grasp on this; but some confirmation would be great.

So, here is what i understand so far:

  • To get a transformation matrix we have to concatenate three matrices: one for translation, one for rotation and one for scaling.
  • The order of the concatenation matters, as each operation is relative to the origin of the matrix. This is regardless of handedness.
  • The concept of pre v post multiplication is a separate issue from concatenation order.

The correct order of concatenating these matrices is as follows: First Rotate, this will rotate the object around it's point of origin. Next Scale, since we don't want the scaling to affect how far the object is translated from origin it must be scaled first. Finally Translate.

In HLSL this would mean:


float4x4 transform = mul( mul( rotation, scale ), translate);
float4 worldPosition = mul(vertex, transform);

However in GLSL it would be:


mat4 translation = translate * scale * rotate;
vec4 worldPosition = translation * vertex;

Am i correct in assuming all of the above, or did i miss something in there?

Advertisement
When you do it manually I believe it was scale, rotate, translate. Make sure you rotate before you translate, so the origin you rotate around is not changed.

You might also wanna check this out: http://www.gamedev.net/topic/655668-understanding-a-d3dx-model-matrixs-content/

Also I suggest to do this on the CPU Side and not in a shader, because it's model related and doesn't differ per vertex or pixel.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me


To get a transformation matrix we have to concatenate three matrices: one for translation, one for rotation and one for scaling.

If you want to translate and rotate and scale, then you have to concatenate at least 3 dedicated transformation matrices. If you want additional kinds of transformations then there are more dedicated matrices involved. If you want more freedom (center of scaling, axes of scaling, center of rotation) then you need more dedicated matrices, although then the types of additional matrices are rotation and translation again. More on this at the end of this post.


The order of the concatenation matters, as each operation is relative to the origin of the matrix. This is regardless of handedness.

Correct so far, but I don't know whether "origin of the matrix" is a proper wording. I would say that each particular transformation happens with respect to a space, and the properties of the transformation may cause specific mappings of special points or directions in this space. The interesting rules are:

* The point 0 is always mapped onto itself when using a rotation or a scaling.

* A point on a space axis is mapped onto the same axis when using a scaling.


The concept of pre v post multiplication is a separate issue from concatenation order.

The concept of pre- and post-multiplication is because of the matrix product not being commutative. However, whether to use pre- or post-multication in a particular case depends on whether you use row or column vectors and it depends on the concatenation order you want to apply.


The correct order of concatenating these matrices is as follows: First Rotate, this will rotate the object around it's point of origin. Next Scale, since we don't want the scaling to affect how far the object is translated from origin it must be scaled first. Finally Translate.

There is nothing like "the correct order of concatenation". Any order is correct w.r.t. a use case. However, there is one order where the particular transformations do not influence one another, and that order is scaling, followed by rotating, followed by translating.

Why? Because of what I've written above: Scaling has the 2 mapping properties, namely the center and the axes. But the axes are altered by a rotation. Hence doing the rotation first would have an influence on scaling. On the other hand, rotation just map the origin onto itself, and the scaling does so, too, so scaling does not influence rotation.

In general, however, and here we come back to the question of whether a combined transformation always consists of 3 matrices, you may want to use a rotation with an arbitrary center, and you may use a scaling with an arbitrary center and axes. In such a case, rotation and/or scaling themselves are no longer represented by pure rotation or scaling matrices, resp., but by combinations of them together with translations and rotations.

For example, the transform node in X3D uses arbitrary scaling axes and an arbitrary common center for rotation and scaling. When using column vectors (hence read it right to left), the decomposed form looks like

T * C * R * A * S * A-1 * C-1

where T, R, S denotes translation, rotation, and scale, resp., C denotes the center for scaling and rotation, and A denotes the axes for scaling.

Forgotten to answer to this part:


In HLSL this would mean:
float4x4 transform = mul( mul( rotation, scale ), translate);
float4 worldPosition = mul(vertex, transform);
However in GLSL it would be:
mat4 translation = translate * scale * rotate;
vec4 worldPosition = translation * vertex;

That is not correct in so far that neither HLSL nor GLSL prescribe you to use row or column vectors. It is totally legal to use

HLSL: float4 worldPosition = mul(transform, vertex);

GLSL: vec4 worldPosition = vertex * translation;

as well.

BUT: Mathematically neither of the variables in my snippet is the same as its partner in your snippet. Instead, one of them is the transposed form of the other. This is very important, because in HLSL/GLSL you cannot directly see this. Moreover, as long as the matrix in question is a vector, both HLSL and GLSL simply make no distinction between them; instead they simply imply that a pre-multiplicand is a row vector in case that it is a vector at all, and a post-multiplicand is a column vector in case that it is a column vector at all. Nevertheless, in case that the argument is not a vector, you as the programmer has the responsibility to ensure the correct form of the matrix.

For example, you have an own matrix math library that works using column vectors (we let the memory layout aspect aside here). Hence a matrix fetched from the library can be used directly in HLSL when using mul(matrix, vector) as well as in GLSL when using matrix * vector, but it cannot be used in HLSL when using mul(vector, matrix) or in GLSL when using vector * matrix. However, using the transpose operator, it can be used in HLSL as mul(vector, transpose(matrix)) and in GLSL as vector * transpose(matrix).

Hope that helps.

I always remember the acronym "ISROT":

* Identity - Default identity matrix

* Scale - changing of size of object

* Rotation - rotation of that object

* Orbit - rotation around some other object or point

* Translation - Movement to a new world position

@cozzie

Thanks for the quick answer. I am calculating the final transform matrix only one time on the CPU, but i figured that giving an example in the standard shading languages would make my question easier to read.

@haegarr

That was a very in-depth review thank you for taking the time to write all of that out!

You also mentioned that Pre V Post multiplication depends on weather we are using row or column vectors. I was under the assumption that the major of the matrices we use also plays a part in it as: A * B = (BT * AT)T.

To elaborate, given a matrix that contains scaling information S, and translation information T. If the matrices are row major, and T contains translation in it's last row then the multiplication order S * T will work. But if we are using column matrices and the translation is in the last column of T then the multiplication order needs to be T * S. The resulting matrix in either case should contain the original scale and the translation. Is that not correct?

One of the things you've pointed out is the implicit conversions that the shading languages do, not going to lie implicit things annoy the crap out of me. Is there a good book / resource anyone knows of that touches on the nuances of math (mainly things that are done implicitly) in shader code?

@braindigitalis

That is super easy to remember, thanks!

For the following we need to distinguish between "row/column vectors" which means that there is a matrix with 1 row/1 column, resp., and "row/column major layout" which denotes how the 2D structure of a matrix is linearly stored in 1D computer memory. I state this explicitly just to make clear which terms I use for which property.


You also mentioned that Pre V Post multiplication depends on weather we are using row or column vectors. I was under the assumption that the major of the matrices we use also plays a part in it as: A * B = (BT * AT)T.
In a matrix product A * B the left side matrix A is called the pre-multiplicand, and the right side matrix B is called the post-multiplicand. Here "pre" and "post" just denote the sequence of matrices when reading the expression from left to right. In itself, it depends on nothing but is just a naming scheme.
What I meant in my previous post was that if one needs to choose whether to pre- or to post-multiply, one has to consider both (a) whether either row or column vectors are used, and (b) what you want to achieve. For example, you may want to apply a translation T onto an already existing transformation M, and you are using row vectors. Then the solution would be to post-multiply M by T. Why? Because of using row vectors, a vector v need to be written on the left (due to the "row dot column" prescription), and T should be applied in the space M results in. In summary, including the vector v:
v * M * T

On the other hand, you may want to apply a translation T in the local object space before an already existing transform M is applied, and you are using column vectors. Then the solution would be ... again to post-multiply M by T. In summary, with an analog reasoning as above:

M * T * v

So the very interesting is that, just looking at the both transforms, we've M * T in both cases. This is because we have exchanged both the row/column vector usage as well as the locality in space. See, we have only 2 possibilities M * T and T * M, but we have 4 combinations of row/column vector usage and logical application before/after another transform. So each possibility is good for 2 combinations.


To elaborate, given a matrix that contains scaling information S, and translation information T. If the matrices are row major, and T contains translation in it's last row then the multiplication order S * T will work. But if we are using column matrices and the translation is in the last column of T then the multiplication order needs to be T * S. The resulting matrix in either case should contain the original scale and the translation. Is that not correct?

Err, it is not correct in general, because you imply a specific use case already. In fact it is totally legal to first translate and scale in the resulting space. As I mentioned in one of the posts above, using an arbitrary center of scaling is absolutely a feature one may want, and this can be reached only if scaling is done in a translated space.

So again: The transformation S * R * T (in case of row vectors) has a convenient order because it allows to express any transform (possible with that primitives) with the lowest amount of primitive matrices. But that does not make it the one and only solution.

BTW: "Translation in its last row" is not a requirement though. In fact, translation is stored where the homogenous co-ordinate is. This may be any row/column. Using the last row/column is just another commonly used convention but nevertheless a convention only. Of course, all matrices in a computation need to follow the same convention.

That was the perfect answer, no more questions (for now) :D thanks so much for taking the time!

I always remember the acronym "ISROT":

* Identity - Default identity matrix

* Scale - changing of size of object

* Rotation - rotation of that object

* Orbit - rotation around some other object or point

* Translation - Movement to a new world position

That's interesting. I never considered Orbit to be a separate matrix from Rotation.

Typically I just remember "SRT" (starting with the Identity should be implied), and if I wanted the object to orbit around a fixed point, I switch the order of R and T so I rotate around the translated point.

New game in progress: Project SeedWorld

My development blog: Electronic Meteor

This topic is closed to new replies.

Advertisement