Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Results 1 to 9 of 9

Thread: Need help with skeletal animation

  1. #1
    Junior Member Newbie
    Join Date
    Jun 2012
    Posts
    15

    Need help with skeletal animation

    I am new to skeletal animation and I have read that, to animate a mesh using skeletal I would have to use a hierarchy of bones which I did making each bone node in my scene graph and then to deform the mesh I have to get the inverse absolute matrix of the bone before interpolation (which I believe is what they call the pose) multiply that for the interpolated absolute matrix of the bone to get transition matrix and to deform the mesh I would have to multiply the vertex times the transition matrix times the weight and sum all this result with any other bone that deform the same vertex.

    to be more specific to get the absolute of each bone I do this

    Code cpp:
    void CNode::update()
    {
       calculateAnimation();        //update the relativeMatrix with the interpolate keyfrmas
       if(this->Parent!=null)
          absMatrix = Parent->absMatrix * relativeMatrix;
       else
         absMatrix = RelativeMatrix;
     
       for(int n=0; n<child.count; n++)
       {
          child[n]->update();      //keep going with the childrens
       }
    }

    now I get the inverse of this absMatrix matrix before any interpolation that change the relativeMatrix one time only so to do the vertex deformation in this function

    Code cpp:
    void CMesh::skinMesh(){
         CVec3 vec;
         for(int n=0; n < vertex.count; n++)
         {        
            vec.clear();
            for(int i; i < vertex[n].weight.count && vertex[n].bindBone[n] ; i++)
            {
                vec+= vertex[n].bindBone[n]->transitionMatrix * vertex[n].weight[n]
            }
            outVertex[n] = vec;
    }

    that doesn't work for me because every vertex rotate around the center of the mesh axis instead of the parent of bone that deform the vertices, I thought it was logical considering that transition = InverAbs *absoulteMatrix will get me the the amount of rotation the bone has gain due to the interpolation so assuming it rotate 20 degrees the vertices will rotate 20 deg from the vertices origin so I guess I am missing something to make the vertices rotation around the parent of the bone which is deforming them,
    the only way I could make it work was to transform the vertex with the inverse of the bone absolute matrix before interpolation and the with the interpolate absolute matrix, but that make it to slow since two multiplication happened instead of one, please help me.
    Last edited by Dark Photon; 10-27-2012 at 02:57 PM.

  2. #2
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,183
    Please use [ code ] ... [ /code ] or [ highlight=cpp ] ... [ /highlight ] tags (without spaces) to mark code blocks so that the indentation is kept and the code is more readable. I've inserted them for you.

  3. #3
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,183
    Quote Originally Posted by jmanuelexe View Post
    I am new to skeletal animation...doesn't work...please help me.
    Ok. It seems you're a confused on how the basic transforms work, and the terminology you're using to describe things I've not seen used anywhere else. Where did you pick it up?

    To get "unconfused", let me highly recommend you read Chapter 11 of Jason Gregory's Game Engine Architecture-- or if you're impatient, just Sections 11.1 - 11.5 (you can pick up the rest later). For a short-but-good tutorial on the transforms specifically (Gregory covers this well also), read Chapter 4.2: Filling the Gaps: Advanced Animation Using Stitching and Skinning from Game Programming Gems (also in Best of Game Programming Gems).

    More on your specific problem in a bit. Got to go.
    Last edited by Dark Photon; 10-27-2012 at 07:02 PM.

  4. #4
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,183
    Quote Originally Posted by jmanuelexe View Post
    ...the only way I could make it work was to transform the vertex with the inverse of the bone absolute matrix before interpolation and the with the interpolate absolute matrix, but that make it to slow since two multiplication happened instead of one, please help me.
    Ok, I've read over your note again and I'm not sure exactly what your problem is, partly because of the non-standard terminology you're using as well as some of what you're doing.

    However, your quoted text above sounds very close to what you need to be doing to build the skinning matrices (aka joint final transforms (F), which you're calling the "transition matrix"), though it's worded a bit ambiguously. So let's work with that.

    First off, what you're calling the "inverse bone absolute matrix before interpolation" (if you're talking about what should be used here) is commonly called the "inverse bind pose" transform (B-1). It is the product of the inverse orientation transforms (O) for each bone (aka joint) in the mesh bind pose:

    B-1N = ON-1 ... * OC-1 * OB-1 * OA-1

    (Note that I'm writing the transforms as you did (like OpenGL does) where you have to read them right-to-left to read them in order of application.)

    It takes you from the OBJECT-SPACE of the mesh in the bind pose down to the local space of a particular bone -- aka BONE-SPACE (or JOINT-SPACE -- same thing). Note that each bone orientation transform (e.g. OB) takes you from that bone's space to it's parent bone space (or to OBJECT-SPACE) in the case of the root bone (OA), so you can see how if we invert them and apply them in order that we walk down from OBJECT-SPACE to the local BONE-SPACE of each individual bone for the mesh in the bind pose.

    Now once we're down to the space of each bone, we need to apply that bone's animation transform (A). This animates the bone from the orientation it is in in the bind pose to some new animated pose. Then we apply the joint's orientation transform (O) to move from that local bone's BONE-SPACE to its parent bone's BONE-SPACE. Then we do the exact same thing for the parent bone (O*A*), and for its parent, and for its parent, etc. up through the root bone. The product of all these (O*A*O*A*O*A) pairs for each joint up to root is called the "joint (or bone) global transform" (G). Prepend the inverse bind pose, and this gives us the full formula for computing the "skinning matrices" (aka joint (or bone) final transforms (F)):

    final = global * inverse bind pose
    F = G * B-1
    F = ( OA*AA*OB*AB*OC*AC * ... ) * ( ... * OC-1 * OB-1 * OA-1 )

    so for instance, for bone/joint B specifically:

    F = ( OA*AA*OB*AB ) * ( OB-1 * OA-1 )

    So, reading from right-to-left, go from OBJECT-SPACE to BONE-SPACE of bone A, then BONE-SPACE of bone A to BONE-SPACE of bone B, then apply animation transform for bone B, then move up to the parent space of bone B (BONE-SPACE of bone A), then apply animation transform for bone A, then move up to the parent space of bone A (OBJECT-SPACE).

    So if your words didn't make it clear and this actually was what you're trying to do, then you're on the right track.
    Your mention of using the "inverse of the bone absolute matrix before interpolation" and "the interpolate absolute matrix" didn't much sense to me but had a vague resemblance to what I just described.

    ...but that make it to slow since two multiplication happened instead of one
    When are you trying to do this and what about it makes it too slow?

    In case this is what's tripping you up, note that once you have the joint/bone final transforms (F) for each joint for each keyframe, if you just want to render that single animation track with keyframe interpolation, you "do not" need to recompute anything. Just blend the joint final transforms for adjacent keyframes together on the GPU.

    You're using basic Linear Blend Skinning (LBS), and that has problems with joint collapse (folding, twisting, candy wrapper effects), but those can be solved with a different skinning algorithm or more TLC on the modeling side.
    Last edited by Dark Photon; 10-27-2012 at 07:12 PM.

  5. #5
    Junior Member Newbie
    Join Date
    Jun 2012
    Posts
    15
    ups sorry about the [code] and thank you for fixing it for me, and sorry about the terminology, I have been learning all this stuff on my own and reading that (not school) that have make me a little ignorant about that the right terminologies use and I guess some gaps too but I am great full to know the proper way now, any away.
    the way have been doing it the skinning was like this code
    Code :
    void CMesh::skinMesh(){
         CVec3 vec;
         for(int n=0; n < vertex.count; n++)
         {            
            outVertex[n].clear();
            for(int i=0; i < vertex[n].weight.count && vertex[n].bindBone[i] ; i++)
            {
                vec = vertex[n].vec * vertex[n].bindBone[i]->inversePose; 
                vec = vec * vertex[n].bindBone[i]->absMatrix * vertex[n].weight[i];
                outVertex[n]+= vec;
            }
    }

    that works fine but I notice everyone was using just one matrix that they have been sending to the shader for each joint in this method of my I multiply it first by the joint inverse pose to make the vertex go to the joint space and then with joint animate pose that cause to multiplication for each frame I thought if they only send one final matrix(what I was calling absolute matrix) the they are doing it more efficient than I have been, can you please take a look at this last method I believe you'll see dilemma show.

  6. #6
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,183
    Quote Originally Posted by jmanuelexe View Post
    ups sorry about the [code] and thank you for fixing it for me, and sorry about the terminology, I have been learning all this stuff on my own and reading that (not school) that have make me a little ignorant about that the right terminologies use and I guess some gaps too
    No problem! Yeah, I know what you're going through! A lot of skeletal reference material on the net doesn't explain the details very well and you get the feeling the authors sometimes don't really understand it all. Plus they all use different terminology, different symbology, different math orderings, add their own mix of mistakes and implicit assumptions, etc.

    It took me a while to get my head in it and find a few good "gems" too, and then to get solid enough to realize they were really the gems. Lots of picking through papers to get there though. That said, I'm no super guru there yet.

    ...but I am great full to know the proper way now, any away.
    While that might be the proper "transformations", I don't want you to come away with that being standard "terminology". That's just one terminology set I've seen used (in the Games Programming Gems article I reference IIRC). Pretty well the only transforms that have a semi-standard name across sources is the "inverse bind pose" (B-1) and the "skinning matrices/transforms". Beyond that, there isn't standardization on terminology at all AFAICT. So I guess it's incumbent on us for now to clarify which transform we're talking about.

    Here's what I use:

    * joint animation transform (A) = (animate within a joint's space)
    * joint orientation transform (O) = (joint space to parent joint space, in the BIND POSE)
    * joint local transform (L=O*A) = (Gregory:) joint local pose transform (joint space to parent joint space, in the CURRENT POSE)
    * joint global transform (G=L*L*L*...) = (Gregory:) joint's current pose (joint space to object space, in the CURRENT POSE)
    * joint final transform (F=G*B-1) = skinning matrices/transforms (object space to object space -- BIND POSE to CURRENT POSE)
    * inverse bind pose (B-1=O-1*O-1*O-1*...) (object space to joint space, in the BIND POSE)
    * joint space = bone space

    The above equations again follow the GLSL math order of: v2 = M3*M2*M1*v1

    the way have been doing it the skinning was like this code
    Code cpp:
    void CMesh::skinMesh(){
    CVec3 vec;
    for(int n=0; n < vertex.count; n++)
    {
       outVertex[n].clear();
       for(int i=0; i < vertex[n].weight.count && vertex[n].bindBone[i] ; i++)
       {
         vec = vertex[n].vec * vertex[n].bindBone[i]->inversePose;
         vec = vec * vertex[n].bindBone[i]->absMatrix * vertex[n].weight[i];
         outVertex[n]+= vec;
       }
    }
    that works fine but I notice everyone was using just one matrix that they have been sending to the shader for each joint in this method of my I multiply it first by the joint inverse pose to make the vertex go to the joint space and then with joint animate pose that cause to multiplication for each frame I thought if they only send one final matrix(what I was calling absolute matrix) the they are doing it more efficient than I have been, can you please take a look at this last method I believe you'll see dilemma show.
    Right. Most sources just send one transform down to the shader per joint per keyframe per animation track (whether that transform is encoded in matrix form, quaternion/translation form, or dual quaternion form), which is more efficient.

    The first two lines inside the loop from your source code above is of course trivially equivalent (by substituting the first into the second) to:

    Code cpp:
    vec = ( vertex[n].vec
             * ( vertex[n].bindBone[i]->inversePose * vertex[n].bindBone[i]->absMatrix )
             * vertex[n].weight[i] );

    ...so yeah, you can pre-multiply the "inverse bind pose" (B-1, which you term "inversePose") by the joint global transform (G, which you term absMatrix) -- that is, the 2nd line above -- to get the joint final transform (F, aka "skinning transform") and pass that into the shader instead to save a matrix-vector multiply per vertex joint influence, not to mention uploading only 1 float4x3 transform per bone/keyframe/animation_track as uniform input data instead of 2.

    Just our of curiosity: why are you skinning on the CPU?

    Also, noticed that you flipped math order on us here to: v2 = v1*M1*M2*M3. I'm presuming that was intentional.
    Last edited by Dark Photon; 10-29-2012 at 04:26 PM.

  7. #7
    Junior Member Newbie
    Join Date
    Jun 2012
    Posts
    15
    I don't want to sound to dumb but doing this ( vertex[n].bindBone[i]->inversePose * vertex[n].bindBone[i]->absMatrix ) what I get is the amount of rotation from the pose to the absolute matrix after the animation so if it is for example 15deg it is no different than if it was not joint at all and I would be applying 15deg to the vertex which rotate but not around the joint axis but the mesh axis, at least that happened in the code below so I guess there is something I am missing.
    Code :
       // where the transitionMatrix = ( vertex[n].bindBone[i]->inversePose * vertex[n].bindBone[i]->absMatrix )
       //I guess I forgot to put it in the code from the begging created more confusion, sorry about that
        vec+= vertex[n].bindBone[n]->transitionMatrix * vertex[n].weight[n]

    if you think you have time I can email you the project
    Last edited by jmanuelexe; 10-29-2012 at 02:12 PM.

  8. #8
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,183
    Quote Originally Posted by jmanuelexe View Post
    I don't want to sound to dumb but doing this:
    Code cpp:
    ( vertex[n].bindBone[i]->inversePose * vertex[n].bindBone[i]->absMatrix )
    what I get is the amount of rotation from the pose to the absolute matrix after the animation.

    So if it is for example 15deg it is no different than if it was not joint at all and I would be applying 15deg to the vertex which rotate but not around the joint axis but the mesh axis, at least that happened in the code below so I guess there is something I am missing.
    Hey, don't worry about how it sounds (doesn't sound dumb BTW). We all make our share of mistakes in the process of learning. You've obviously done some reading on this and are clearly trying to get to the root of your problem by asking specific questions. You're doing great.

    Now, to your point...:

    First, I'm having trouble reconciling what you said above (i.e. it's broken), with what you said before:

    Quote Originally Posted by jmanualexe
    Code cpp:
    ...
    vec = vertex[n].vec * vertex[n].bindBone[i]->inversePose;              
    vec = vec * vertex[n].bindBone[i]->absMatrix * vertex[n].weight[i];             
    outVertex[n]+= vec;
    ...
    that works fine

    These are mathematically equivalent! So it can't break, unless the product of these matrices was computed incorrectly. So please check that.

    Second, to the problem you describe above specifically (15 deg rot appears to rotate about the OBJECT-SPACE origin, not the JOINT-SPACE origin), the only way I can conceive of this easily happening is if at the point you compute the joint final transform (F, which you call the "transitionMatrix"), the inverse bind pose transform for the joint (B-1, which you call "inversePose") is the identity matrix. That would cause the animation transform for your joint to be applied in OBJECT-SPACE and not BONE-SPACE and thus potentially produce the effect you describe. There could also be something whacked about the parent's joint global transform matrix (G, which you call "absMatrix") at that time as well.

    In summary, both of my points suggest the same thing: double check how you are computing the joint final transform matrix, F ("transitionMatrix"). Evidence suggests that's being computed incorrectly. Using your most recent v2 = v1 * M1*M2*M3 math ordering, verify that these are being set as follows for each joint, starting with the root joint:

    F = B-1*G
    F = ( O1-1*O2-1*O3-1* ... On-1) * ( Ln*...*L3*L2*L1 )
    F = ( O1-1*O2-1*O3-1* ... On-1) * ( An*On*...*A3*O3*A2*O2*A1*O1 )

    where the "1" subscript is the root joint and the "n" subscript is the current joint.

    Say, which scene graph are you using? Assimp? OpenSceneGraph? etc.
    Last edited by Dark Photon; 10-29-2012 at 05:16 PM.

  9. #9
    Junior Member Newbie
    Join Date
    Jun 2012
    Posts
    15
    sorry I couldn't replay back since last time, but I didn't have electricity here, I found out that switching my matrices instead of skinmatrix = inverPose * absoulutematrix I did
    skinmatrix = absoulutematrix * inverPose work perfectly fine so I am guessing my multiplication order is incorrect, eitherway I learned more about changing space like in this case from bone space to model space, thank you very much
    this is my multiplication function

    Code :
    void CMatrix::Mult(CMatrix &m1, CMatrix &m2)
    	{
    		m11 = m1.m11 * m2.m11 + m1.m21 * m2.m12 + m1.m31 * m2.m13 + m1.m41 * m2.m14;
    		m12 = m1.m12 * m2.m11 + m1.m22 * m2.m12 + m1.m32 * m2.m13 + m1.m42 * m2.m14;
    		m13 = m1.m13 * m2.m11 + m1.m23 * m2.m12 + m1.m33 * m2.m13 + m1.m43 * m2.m14;
    		m14 = m1.m14 * m2.m11 + m1.m24 * m2.m12 + m1.m34 * m2.m13 + m1.m44 * m2.m14;
     
    		m21 = m1.m11 * m2.m21 + m1.m21 * m2.m22 + m1.m31 * m2.m23 + m1.m41 * m2.m24;
    		m22 = m1.m12 * m2.m21 + m1.m22 * m2.m22 + m1.m32 * m2.m23 + m1.m42 * m2.m24;
    		m23 = m1.m13 * m2.m21 + m1.m23 * m2.m22 + m1.m33 * m2.m23 + m1.m43 * m2.m24;
    		m24 = m1.m14 * m2.m21 + m1.m24 * m2.m22 + m1.m34 * m2.m23 + m1.m44 * m2.m24;
     
    		m31 = m1.m11 * m2.m31 + m1.m21 * m2.m32 + m1.m31 * m2.m33 + m1.m41 * m2.m34;
    		m32 = m1.m12 * m2.m31 + m1.m22 * m2.m32 + m1.m32 * m2.m33 + m1.m42 * m2.m34;
    		m33 = m1.m13 * m2.m31 + m1.m23 * m2.m32 + m1.m33 * m2.m33 + m1.m43 * m2.m34;
    		m34 = m1.m14 * m2.m31 + m1.m24 * m2.m32 + m1.m34 * m2.m33 + m1.m44 * m2.m34;
     
    		m41 = m1.m11 * m2.m41 + m1.m21 * m2.m42 + m1.m31 * m2.m43 + m1.m41 * m2.m44;
    		m42 = m1.m12 * m2.m41 + m1.m22 * m2.m42 + m1.m32 * m2.m43 + m1.m42 * m2.m44;
    		m43 = m1.m13 * m2.m41 + m1.m23 * m2.m42 + m1.m33 * m2.m43 + m1.m43 * m2.m44;
    		m44 = m1.m14 * m2.m41 + m1.m24 * m2.m42 + m1.m34 * m2.m43 + m1.m44 * m2.m44;
    	}

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •