Transformation pipeline questions!

Started by
11 comments, last by SandoSan 15 years, 9 months ago
Hi guys, What do you store to represent a node/object's orientation and position transformations? My initial thoughts were to simply use a rotation matrix and a position matrix. Updating them both when needed and simply multiplying them together each frame to pass in for rendering/next node... Is this a "good practice"? I have seen many people advocate using quaternions for orientation representation but doesn't this involve more work per frame? Since surely you have to create the quaternion for your new rotation, multiply current rotation by this new rotation and then you still have to convert to a matrix to combine with the position transform to pass in for rendering... Are the precision advantages of quaternions really that much of a win? My intention here is not to start a new flame war on the subject - I really want to know what you guys would do...
Advertisement
I'm not completely sure what's common for most projects, but speaking for myself here I usually just store things in one transformation matrix (world matrix) that I update as an object changes position or orientation. That's certainly adequate for storing your rotation and and translation information, and as long as scaling isn't involved it's very easy to extract either part from the matrix.

As far as quaternions, I really only use them when I need to interpolate between two orientations. In those cases I just use a function that can derive a quaternion from a matrix, do the interpolation, then convert back to a matrix. I tend to see a lot of people who are somewhat confused as to what quaternions are actually useful for (with the term "gimbal lock" thrown around quite a bit when it really shouldn't be).
Quote:Original post by MJP
I'm not completely sure what's common for most projects, but speaking for myself here I usually just store things in one transformation matrix (world matrix) that I update as an object changes position or orientation. That's certainly adequate for storing your rotation and and translation information, and as long as scaling isn't involved it's very easy to extract either part from the matrix.


So you have one internal matrix to each node/object that gets passed on as the "world matrix" for further nodes or rendering? How do you go about updating this matrix? What do you do to ensure the matrix still represents an accurate rotation, or has this never become a problem?

Quote:Original post by MJP
As far as quaternions, I really only use them when I need to interpolate between two orientations. In those cases I just use a function that can derive a quaternion from a matrix, do the interpolation, then convert back to a matrix. I tend to see a lot of people who are somewhat confused as to what quaternions are actually useful for (with the term "gimbal lock" thrown around quite a bit when it really shouldn't be).


Yes. This is exactly what I mean. Many places on the net seem to suggest all orientations should be stored in a quaternion - is it just so that you can then easily do slerp if required? Because if you don't explicitly need that it seems like more operations per frame per object/node creating and converting from the quaternions...
It's needed for interpolation, imagine you have 10 frames of animation, but your system is running at 60 fps, you could use those 10 frames of animation in the 60 second time frame and your animation would appear choppy. Or you can create a 60 frames of animation and your animation would looks smooth. Doing this way would use up more memory but reduce compute time, not only that it require you to create more keyframes and not flexible for other frame rate (e.g 30 fps, 100 fps etc...). So most animation system will store a quat for each node and compute the world matrix as the result.
Quote:Original post by nhatkthanh
It's needed for interpolation, imagine you have 10 frames of animation, but your system is running at 60 fps, you could use those 10 frames of animation in the 60 second time frame and your animation would appear choppy. Or you can create a 60 frames of animation and your animation would looks smooth. Doing this way would use up more memory but reduce compute time, not only that it require you to create more keyframes and not flexible for other frame rate (e.g 30 fps, 100 fps etc...). So most animation system will store a quat for each node and compute the world matrix as the result.


Ok, thats what I figured. So you would go with a quaternion internal representation even for the simple system I have in mind. In that way it would be well placed to support key framed interpolations, etc... in the future.

How does this fit in with storing the position as a matrix? Do both the position transforms and the orientation quats get calculated seperately down the hierarchy?

Quote:Original post by SandoSan
Ok, thats what I figured. So you would go with a quaternion internal representation even for the simple system I have in mind. In that way it would be well placed to support key framed interpolations, etc... in the future.


Well, even simple systems can benefit from the slerp interpolation. If you want to orient a non-animated object to a given heading over a number of frames (looks better than instantenouos direction flips), it's typically more straightforward to slerp between quaternions than to mess around with axis angles and Atan2.

Quote:Original post by SandoSan
How does this fit in with storing the position as a matrix? Do both the position transforms and the orientation quats get calculated seperately down the hierarchy?


You could also simply store the postion in a Vector3 and contruct a world matrix from that and the quaternion orientation as needed. Saves you a bit of space and the position is generally easier to modify as a Vector3. Whatever you pick for representing the transforms, I'd store them seperately since chances are you'll need to have them decomposed (ie not combined in a world matrix) for many calculations anyway.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
I either use a 4x3/4x4 matrix for orientation and position, or just a vector and euler angles if all I routinely use is yaw. I keep scale as a seperate vector and only multiply it in when rendering if it's not (1,1,1).

Quaternions are good for storing animation data because you can interpolate and they take less memory, but those generally don't matter for the orientation of a game entity. Also nice is that you never have to orthogonalize them, but if you're going to write logic to normalize occasionally anyway that's not such a big advantage.
Matrices are so much easier to visualize, I prefer to use them when there's no major reason to go one way or the other.

EDIT: remigius makes a good point. I forgot about interpolating toward orientations for easy smoothing of turning around. I did that in one game that used euler angles and it was nice being able to keep it fairly transparent to the rest of the game.

Also, for the matrix style, provided you're using OpenGL element order, you can access the position portion of the matrix as a normal vector3. If you can't do that, then it would definitely be better to keep it as a aseperate vector.
Quote:Original post by DekuTree64
I either use a 4x3/4x4 matrix for orientation and position, or just a vector and euler angles if all I routinely use is yaw. I keep scale as a seperate vector and only multiply it in when rendering if it's not (1,1,1).


Well two 4x4 matrices for both position and orientation were my initial thoughts since I do require rotation about arbitrary axes and I don't want to have to muck about trying to compose euler angle style rotations to minimise gimbal lock.

I would update these matrices as and when their values need changing and combine them into one before sending to the vertex shader.

Quote:Original post by DekuTree64
Quaternions are good for storing animation data because you can interpolate and they take less memory, but those generally don't matter for the orientation of a game entity. Also nice is that you never have to orthogonalize them, but if you're going to write logic to normalize occasionally anyway that's not such a big advantage.
Matrices are so much easier to visualize, I prefer to use them when there's no major reason to go one way or the other.


At the minute I think I am leaning toward using matrices as being able to orient the entire object toward another vector isn't something I can envision having to do at the moment. This way I can keep the representations the same and not have to bother about constructing the intermediate rotation vector from the quaternion before composing it with the position...

Or would starting with matrices be shortsighted when/if I come to require high level slerp's and complicated object hierarchies/bone keyframing...?
Well I think I have lost sight of what should be done with this relatively simple piece of engineering. At this stage all I need to do is set arbitrary orientations, scales and positions for my objects (though I would like it to be extensible to hierarchial setups in the future).

I am currently storing all this for each entity:

Matrix44 mPostion;
Matrix44 mScale;
Matrix44 mOrientationMat;
Quaternion mOrientation;

Matrix44 mWorld;

When the orientation is updated I first recreate the matrix representation and stuff it in mOrientationMat. I then use the matrices to recreate the mWorld matrix for the next draw call. Each update recreates the mWorld as it didn't feel right to continually update that every render call.

My initial implementation only stored position, orientation and scale (as Vector3, Matrix44 and float) and calculated the required matrices to compose for mWorld upon each draw call. This felt like I was doing too much work per draw call - or is this what all people do for each object? Doing the same with quaternion representations seems to be even more work creating the quaternions to compose and the resulting matrix forms to update mWorld each draw call...

Bah!

Quote:Original post by SandoSan
My initial implementation only stored position, orientation and scale (as Vector3, Matrix44 and float) and calculated the required matrices to compose for mWorld upon each draw call. This felt like I was doing too much work per draw call - or is this what all people do for each object? Doing the same with quaternion representations seems to be even more work creating the quaternions to compose and the resulting matrix forms to update mWorld each draw call...


Reconstructing the world matrix each frame may sound computationally expensive, but it shouldn't be even a blip on the performance radar. FWIW, I always reconstruct the world matrix each frame for each object and haven't run into any issues with that. If you'd really want to, you could only update the world matrix when something (pos/scale/rot) changes, but at some point you'll want your objects moving along smoothly and you'll need to update it practically every frame anyway. To put it into perspective, if you should move on to animation/skinning, you'll be constructing and setting tons of matrices each frame and this is nothing to worry about.

As for storing your objects properties, you're obviously completely free to do whatever fits your needs. I personally found it useful to have all transforms around seperately, ie

Vector3 positionQuaternion rotationfloat scale; // or Vector3 if you want non-uniform scaling


From here you can construct your world matrix and/or change the various seperate transforms as needed without having to decompose the matrix (which as I recall is quite a bit more expensive than constructing one) and you don't have to worry about redundant transforms being 'in sync'. It may help to realize the quaternion is a robust presentation for expessing any rotations, not just some gimmick to do interpolations. If you want to set a new orientation, simply construct a quaternion from axis angle or euler and set that on the object (or of course slerp to the new target rotation). That way all internal rotation information is uniformly stored as quaternions and you can convert back and forth as needed.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!

This topic is closed to new replies.

Advertisement