Sign in to follow this  
Vincent_M

Transformation Hierarchy

Recommended Posts

Vincent_M    969

I've started to work on a transformation hierarchy, but I have found that if I attach a camera to a scene node that doesn't have a uniform scale of 1.0, then it throws off my view matrix when I calculate it. I calculate my view matrix from my camera scene node by taking its final transform matrix in world-space, and creating the inverse-transpose of that whenever something changes.

 

Another issue I keep getting stuck on is how to calculate global position, rotation, and scale. My current Transform class has a pointer to its parent, knows its final transform by the time it has to update, and can get the parent transform's local position, rotation and scale. I thought about just multiplying my Transform's final transform matrix by a vector of zero to get its global position, but any scaling from up the hierarchy would be applied to it too.

Edited by Vincent_M

Share this post


Link to post
Share on other sites
L. Spiro    25638
What are you expecting vs. what you get?
I would expect that the world shrinks on the screen if the camera scales up and vice-versa. If that is what you are getting then your results are not thrown off at all.

If that is not what you want to get (which is different from it being the correct result), normalize the world matrix’s first 3 rows before getting its inverse for the view matrix.


L. Spiro


[EDIT]
Note that I mentioned an inverse matrix, not an inverse-transpose. I somehow skimmed over this point (I read it but it didn’t register).
Don’t derive your view matrix via inverse-transpose of the world matrix. View = World.Inverse, not World.Transpose.Inverse.
[/EDIT] Edited by L. Spiro

Share this post


Link to post
Share on other sites
Vincent_M    969

What are you expecting vs. what you get?
I would expect that the world shrinks on the screen if the camera scales up and vice-versa. If that is what you are getting then your results are not thrown off at all.

If that is not what you want to get (which is different from it being the correct result), normalize the world matrix’s first 3 rows before getting its inverse for the view matrix.


L. Spiro

After a closer look, it looks like this is working correctly. I originally thought that it was affecting my perspective matrix, but I tested it out in another scenario, and it worked fine. The only weird thing is that I achieve correct results when I only perform the inverse of my camera node's world-space matrix. If I do an inverse-transpose of that matrix, nothing draws. At least, the single quad of geometry I am drawing to my scene doesn't display. I'll search for it with my FPS camera I've got setup with my gamepad, but no luck haha.

 

Anyway, I was adding onto my original post earlier, and things came up, so I couldn't save it. A huge question I've been trying to answer is how to get my object's position, orientation and scale from its final transform matrix. My Transform class does store position, rotation and scale, but that's only to calculate its local transform matrix that's used to calculate the transform's final transform matrix for that frame along with the parent's final transform.

Share this post


Link to post
Share on other sites
L. Spiro    25638

If I do an inverse-transpose of that matrix, nothing draws.

A view matrix is an inverse, not an inverse-transpose. The view matrix is meant to transform geometry, not normals.


L. Spiro

Share this post


Link to post
Share on other sites
haegarr    7372
Another issue I keep getting stuck on is how to calculate global position, rotation, and scale. My current Transform class has a pointer to its parent, knows its final transform by the time it has to update, and can get the parent transform's local position, rotation and scale. I thought about just multiplying my Transform's final transform matrix by a vector of zero to get its global position, but any scaling from up the hierarchy would be applied to it too.

Let's use the term "local transform" for the transformation to go from the local space into the parent space. If you have a local scale SL, a local rotation RL, and a local translation TL, each one expressed as matrix, then the overall local transform is usually compute as (using row vectors)

   ML := SL * RL * TL

 

Let's use the term "world transform" for the transformation to go from the local space into the world space, which means going through the hierarchy of parent spaces until reaching the one conventionally defined to be the global one. So this is computed as

   MG := ML * PG

where PG denotes the global transform of the parent. Notice please that this formula can be computed recursively by substituting the global transform of the parent.

 

You now want to interpret the result again as combination of scale, rotation, and translation but in world space. As long as you don't want to interpret it w.r.t. extra-ordinary rotation center and/or scale axes and center, this means to have the equivalence

   MG = SG * RG * TG

 

To decompose this transform, you first look where the local origin is when expressed in world space:

   oG := [ 0 0 0 1 ] * MG , so that TG = T( oG )

If you think about this, you see that this operation just picks the 4-th row vector from MG.

 

Then you compute the world space equivalents of the local unit vectors along the 3 axes:

   nGx := [ 1 0 0 0 ] * MG

   nGy := [ 0 1 0 0 ] * MG

   nGz := [ 0 0 1 0 ] * MG

If you think about this, you see that these operations just picks the 1-st to 3-rd row vectors from MG. These vectors are influenced by both SG and RG. Because a pure rotation always follows the ortho-normal scheme, the basis vectors can be computed by normalization:

   sGx := | nGx |

   sGy := | nGy |

   sGz := | nGz |

   rGx := nGx / sGx
   rGy := nGy / sGy
   rGz := nGz / sGz
Then the rotation matrix is just the composition of the basis vectors
   RG := RrGx, rGy, rGz ) = [ rGxTrGyTrGzT [ 0 0 0 1 ]T ]T
 
As a by-product, the normalization factors computed above are the scale factors in global space.

   sGx, sGy, sGz

 

[...] I thought about just multiplying my Transform's final transform matrix by a vector of zero to get its global position, but any scaling from up the hierarchy would be applied to it too.
Yes, the global position will be influenced by rotations and scalings of intermediate spaces (i.e. it is not influenced in a one hop local-to-global transform, because of the farseeing compositing order when computing ML). However, that is the global position. If you want something else, then you need to ask another question. So ... what do you want to get instead?
Edited by haegarr

Share this post


Link to post
Share on other sites
L. Spiro    25638

The convention in every math and science field is to use column vectors…

Fine and dandy for math and science, and of course I use column-major matrices if I ever do math on paper, and I know that any papers I read will be using column-major.

But there really is no reason to use column-major notation in games (except that you are forced to by OpenGL).  His arguments are only valid for the world of math and science, even though he includes graphics and games in his list.

 

Why?  Because his argument is specifically a mathematical argument.  All he said was, “The math is this, so use column vectors everywhere.”  That’s a compelling reason to use columns in math.  It has nothing to do with anything else.

As I said, if I am doing math on paper, I will be using column-major.  The code I write will produce the correct result, but will be in row-major.

It’s really as simple as that.

 

And no argument can be made against refactoring the math for use in your graphics engine or game.  Even the most trivial equations such as those used in shading models get refactored such that cos? becomes a dot product.  Everything you put into your code should go through a rough translation and refactorization stage.

 

 

That is just me.

 

 

L. Spiro

Share this post


Link to post
Share on other sites
Vincent_M    969

First off, I just want to say thank you to haegar for the detailed response. It's much-appreciatedbiggrin.png I do have some questions about notation and implementation.

 

 

...

 (using row vectors)

...

 

Here's a good explanation why you should not use row vectors. The convention in every math and science field is to use column vectors…

 

This here, and what L. Spiro has said brings up another question about what I've seen about the order of math in written notation vs how to implement it in code. For example, the following:


Let's use the term "world transform" for the transformation to go from the local space into the world space, which means going through the hierarchy of parent spaces until reaching the one conventionally defined to be the global one. So this is computed as
   MG := ML * PG
where PG denotes the global transform of the parent. Notice please that this formula can be computed recursively by substituting the global transform of the parent.

This is actually how I do it: I loop through my scene's root node's children to find their local transform matrix, then their global from their parent's global matrix (the root node, in this case) and their own local matrix. Then, I loop through each child node's children to do the same thing, etc. However, I multiply my matrices in the opposite like this:

// defined in the class interface
Matrix4 localMat;
Matrix4 globalMat;

...

// calculated during the scene's update routine
globalMat = parent->GetGlobalMatrix() * localMat;

Assume that my matrices are column-major, and that the first matrix in the operation is processed horizontally by row, and the second matrix is process vertically by column. Is haegarr's matrix math the same, except assuming that the data is arranged in a row-major format?

 

Same as here:


Then you compute the world space equivalents of the local unit vectors along the 3 axes:
   nGx := [ 1 0 0 0 ] * MG
   nGy := [ 0 1 0 0 ] * MG
   nGz := [ 0 0 1 0 ] * MG

Whenever I transform my vectors, I treat them column matrices, and so they are the second operand instead of the first in the multiplication operation. I mean, you really can't multiply a row matrix by a 4x4 matrix in that order mathematically, unless this notation is to suggest something additional, such as data layout in how each element in memory represents the layout of the matrix.

 

If this is the case, and I should be multiplying my matrices in the opposite order as displayed above, then would this:


ML := SL * RL * TL

Actually be:

ML = TL * RL * SL

in code?

 

I've seen row-major explanations for transformation matrices in this same fashion quite a bit online too, and it's confused me in the past. For example, I've seen people refer to the projection|view|model matrix as the "mvpMatrix" in examples, but then it'd be calculated in the order of:

projection * view * model. I've assumed that it uses the first matrix in the operation as a collection as row vectors, and the second operand as column matrices.

 

Then again, I've also seen "mvpMatrix" computed as model * view * projection in OpenGL (still using a column-major layout), but the way the rotation and translation matrices are transposed to the projection * view * model approach. For example, the translation matrix would store its data like so:

 1  0  0  0
 0  1  0  0
 0  0  1  0
tx ty tz  1

In my projects, I multiply everything as projection * view * model where the OpenGL layout is using the default row-major layout, and my translation matrix looks like this:

1 0 0 tx
0 1 0 ty
0 0 1 tz
0 0 0 1

I have come to the conclusion that:

Mproj * Mview * Mmodel == MTmodel * MTview * MTproj

 

In other words, you could multiply a collection of matrices in one order, then multiply a transposed copy of those same matrices, and yield the same result.

 

Notation aside, I do have questions about the implementation as well:


You now want to interpret the result again as combination of scale, rotation, and translation but in world space. As long as you don't want to interpret it w.r.t. extra-ordinary rotation center and/or scale axes and center, this means to have the equivalence
   MG = SG * RG * TG

I'd always rotate and scale my objects from the node's local origin. If I wanted to rotate or scale my object from a point, I'd translate it relative to its parent, and just rotate or scale the parent to achieve that effect. Will this work for non-uniform scaling, though?

Share this post


Link to post
Share on other sites
haegarr    7372
Assume that my matrices are column-major, and that the first matrix in the operation is processed horizontally by row, and the second matrix is process vertically by column. Is haegarr's matrix math the same, except assuming that the data is arranged in a row-major format?

When I write "row vector" I mean a matrix with 1 row and N columns; when I write "column vector" I mean a matrix with N rows and 1 column. The math is so that in a matrix product M1 * M2 the count of columns in M1 is the same as the count of rows in M2. If now one of the matrices is a 4 x 4 homogeneous transformation matrix M, and the other is a vector v, then writing

    M * v

requires that v has 4 rows (and as a vector then 1 column). The result will be a matrix with 4 rows and 1 column, a.k.a. column vector. On the other hand, writing

    v * M

requires that v has 4 rows (and as a vector then 1 column).The result will be a matrix with 4 columns and 1 row, a.k.a. row vector. (Notice please that strictly seen above the both M's are not the same, as well as the both v's are not the same; more to this below.)
 
If you define that a transform as above results in a new vector v', and you want to apply a 2nd transform, you obviously have to use the same scheme, since the resulting vector is of the same form as the argument vector. So for column vectors you get
   M2 * ( M1 * v ) = M2 * M1 * v
and for row vectors you get
   ( vM1 ) * M2 = vM1 * M2
 
As can be seen, w.r.t. the matrix product the order is reversed totally. However, and this is important, although I have named both the column vector as well as the row vector v, they are obviously not the same, because they differ in their respective count of columns and rows. Mathematically that can be expressed by using the transpose operator which can be imagined to exchange columns and rows. So, with c being a column vector (with N rows) and r being a row vector (with N columns), it is valid
   cT = r and rT = c and hence (vT)T == v
Notice please that the transpose operator can be applied to a column vector as well as to the row vector, and that it does not mark a vector to be of the one kind or the other.
 
Now consider that a vector is just a 1xN or Nx1 matrix, the same principle is valid for matrices in general. That means if I write down a matrix product in row vector manner, I can convert it into column vector manner by using the transpose operator:
   ( v * M1 * M2 )TM2T * M1T * vT
What this means is that the resulting vector will be the same if the order of matrix multiplication is reversed AND each matrix has the rows and columns exchanged when being compared to the original (this is also valid for the resulting vector, of course).
 
Important: Nothing of the above is related to how a matrix is organized to fit into linear computer memory. It is based solely on math. I use the term "row major layout" and "column major layout" to name the layout of matrix values in computer memory. They are not used in my previous post because they play no role for what I've described. They play a role if and only if the implementation of the matrix product itself would be discussed.
 
In other words, you could multiply a collection of matrices in one order, then multiply a transposed copy of those same matrices, and yield the same result.
Except that the result itself is also transposed: Yes, you are right. See above.
 
I'd always rotate and scale my objects from the node's local origin. If I wanted to rotate or scale my object from a point, I'd translate it relative to its parent, and just rotate or scale the parent to achieve that effect. Will this work for non-uniform scaling, though?
If you want to define a specific center C for rotation R, you have to apply (now using column vectors)
    C1* R1* C1-1
as the modified rotation. If you say "my transform node allows to have an arbitrary rotation center", then the overall transform would be
    T1C1 * R1 * C1-1 * S1
When you instead say "I apply a standard parent transform on top of the standard local transform", you have
    T3 * R3 * S3 * T2 * R2 * S2
You need to enforce
    S2 = S1
    R2 = I
    T2 = C1-1
    S3 = I
    R3 = R1
    T3 = T1 * C1
to yield an equivalence. Is that what you are doing/mean?
 
If so, then the similar approach for scaling would look like
    T1 * R1 * C1 * S1 * C1-1
    T3 * R3 * S3 * T2 * R2 * S2
what cannot be solved the same way (it would require that R3 == I and hence forbid a general local transform, because R1 == I must be valid then). At best a common center like so
    T1 * C1R1 * S1 * C1-1
would be possible with
    S2 = I
    R2 = I
    T2 = C1-1
    S3 = S1
    R3 = R1
    T3 = T1 * C1
 
Because S1 is not restricted to something special, it would work with non-uniform scaling as well.
Edited by haegarr

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this