7
\$\begingroup\$

We are 'on final' when it comes to finishing the project, but just before implementing the animation system.

Our Client decided to choose "Bones Animation" - which is that I should export each Transformation Matrix ( matrix4x4 rotation+translation ) for every frame and for every bone that this animated object has.

Objects in our game are animated with 3DS Max Physique Modifier, so we will have bones / weighting data per vertex. But I will simplify things here just to get a bit of light on this subject.

I would like to split this post into 2 points, where :

  1. Exporting bones matrices for every frame

    • treats about the correct method of exporting the bones positions for later animation purposes, where i have to 'move' and 'rotate' every vertex influenced by this bone to bone position at frame X.
  2. Calculating final vertex position

    • treats about proper matrices operations to calculate new vertex position according to bone transformation in frame X.

1. EXPORTING BONES MATRICES FOR EVERY FRAME

Do I understand correctly that while exporting the animated object I should:

  1. Grab BONE transformation matrix at frame 0 and Invert this matrix

  2. Grab BONE transformation matrix at FRAMEx

  3. Multiply 1 * 2 to get the transformation offset of the BONE at FRAMEx

[pseudocode]
// Animation export

// For each frame, export bone transformation offset
for(int iFrame = 0; iFrame < vFrames.size(); iFrame++)
{
    // For every bone in the object
    for(int iBone = 0; iBone < vBones.size(); iBone++)
    {
        // Grab transformation matrix for this bone at frame 0 and inverse it
        Matrix3 matBoneMatrixAtStart = pNode->GetObjectTMAfterWSM( 0 );
        matBoneMatrixAtStart.Inverse();

        // Grab transformation matrix for this bone at frame iFrame
        Matrix3 matBoneMatrixAtCurrentFrame = pNode->GetObjectTMAfterWSM( iFrame );

        // Multiply Inversed Transformation Matrix of this bone at frame 0 - with
        //  current frame transformation matrix
        Matrix3 matBoneTransformationOffset = matBoneMatrixAtStart
                                              * matBoneMatrixAtCurrentFrame ;

        // Save matBoneTransformationOffset - vertex will be multiplied by this
        //  matrix for animation purposes
        fwrite(.....)
    }

}
[/pseudocode]

Will that be enough ? Or there is something I am missing here?

2. CALCULATING NEW VERTICES POSITIONS ( FINAL VERTEX POSITION AT FRAME X)

Later on, when rendering, object vertices are going to be multiplied by exported bone transformation matrix for actual animation frame, and then multiplied by this whole model transformation matrix to place the object in correct position inside the level :

[pseudocode]

Update()
{
    // The model transformation matrix describing the position of
    //  the model in the level
    matModelTransformationMatrix


    // Calculate new vertex position according to it's bone transformation offset
    NewVertexPosition = (OriginalVertexPosition * matBoneTransformationOffset[iFrame])
                           * matModelTransformationMatrix;

    // Increment the frame for testing purposes
    iFrame++;
}

[/pseudocode]

Am I thinking correct here? So, Having bone transformation offset for frame X, multiplying every vertex affected by this bone by this offset should result in a vertex transformed exactly as this bone right?

\$\endgroup\$

1 Answer 1

8
\$\begingroup\$

It's best you read nVidia's pages containing some GPU Gems articles. There's the key formula which I will briefly explain to you in the following pseud-answer: enter image description here

This is where you'll find the complete article, and it's a classic resource by now. I will only assume you want an explanation of that process, done in a simplistic manner (as much as I can).

The formula and its meaning:

You start with a mesh of vertices, i.e. a set {Pk} of vertices with connectivity. You don't care about the topology that much, so disregard the connectivity (that's something skinning shouldn't deteriorate, it should keep manifolds looking like manifolds, preserve topology, etc.). In that formula, vBindpose gives you the rest position of the character's vertex. This is in object space, i.e. the "world' space of that character's frame. Now, each bone has its own frame, and the way to transform a vector written in bone's bone[i] frame into the object's global frame is by multiplying it with matrixBindpose[i]. (imagine you have to assemble a robot, and its forearm vertices are given to you in its elbow frame of reference.. and the elbow is connected to the humerus/shoulder bone.. that's why you need these transformations, mainly to assemble vertex sets into a mesh)

So, what does INV(matrixBindpose[i]) x vBindpose mean then? It means that you get the coordinates of the v vertex in the local frame of that bone[i] bone. Why? Because each bone can memorize its own version of where a vertex it affects is with respect to its own frame of coordinates. That means, each bone can provide you with its own relative view of where a point it affects is, regardless of how other bones see that point.

Now, what happens when you multiply with the new bone transformation matrix matrix[i]? Recall from the last point that you now have a local version of the v vertex, i.e. how the bone[i] thinks that vertex looks like with respect to its own frame. By multiplying with matrix[i], you end up with a vector/vertex situated in the global/object coordinate frame.

Next, you multiply that vertex with the bone's scalar contribution/weight. What do you have then? You end up with a weighted sum of vectors that lie in the same coordinate frame. That's why you can add them/i.e. take that sum over all bones that influence a vertex.

Your code doesn't really reflect that you completely understood the procedure. The nVidia article is focused more on how to do that via shaders, that's why they assume there are at most four bones that can affect one vertex of the initial/bindpose mesh.

As you said, if you take your starting pose, the bindpose, then matrix[i] x matrix[i]^-1 is the identity matrix. That's ok because the sum will be equal to _sum(w_i)*vBindpose_. Since vBindpose is the vector written in the global/object frame, it's already assembled and in position. The sum of those weights, by construction is 1 (convex combination resulting from a weight normalization process - typically done by the system after weights are assigned to each vertex). So that formula can be used to check whether a model can be assembled correctly (it was normally done by the MD5 Doom3 animation model loaders if I remember correctly). That's it, in a nutshell.. it means that at each step you need to optimize a bit the computations so that you compute the result of that lil'ol formula there. Happy coding :)

\$\endgroup\$
2
  • \$\begingroup\$ Oh that's a lot of stuff that i have to read few times now - i didn't suspect it is so complicated.. Thank You for Your help, this should get me running ... \$\endgroup\$
    – PeeS
    Commented Apr 26, 2012 at 13:21
  • \$\begingroup\$ Welcome, it's actually a rather simple topic, I did a tutorial on this, but since it wasn't in English, it won't help giving you the pdf of that. I intend to do it in a video, in pure English and post it on an youtube channel, but I currently don't have time. Phrase your questions on this topic here in the near future.. it's nice to debate such topics. \$\endgroup\$
    – teodron
    Commented Apr 26, 2012 at 13:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .