Work at SourceForge, help us to make it a better place! We have an immediate need for a Support Technician in our San Francisco or Denver office.

Close

help with animation/skinning

2012-11-20
2013-06-03
  • Hi,

    If you looked at the pics, you would have noticed that the skinning is working, the animation is working, but both are not working together.  When I try to put them together, the lamp/hat goes off somewhere else.

    Please help me understand what is going on.  I am at my wit's end at figuring out what I'm mis-using for assimp.  My bone computation, drawing, and glsl code are posted below.

    Thank you for your future help and insights,
    Phi

    void AnimatedAssimpModel::computeInverseBindPoseTransforms(float time,
            aiNode *node, const Matrix4x4 &parent_transform,
            std::vector<Matrix4x4> &bm) {
        //node to update
        const aiNodeAnim *na = findNodeAnimation(node->mName.data);
        //interpolate between animation key frames
        Matrix4x4 node_transform;
        if(nullptr != na) {
            calcInterpolatedKeyFrame(time, na, node->mTransformation);
            copyAssimp(node->mTransformation, node_transform);
        }
        //scene graph matrix of current node
        Matrix4x4 mesh_matrix = parent_transform * node_transform;
        //compute the bone matrices used for skinning
        auto it = mBones.find(node->mName.data);
        if(mBones.end() != it) {
            //converting objects in mesh space to bone space
            bm[it->second.index] = mesh_matrix *
                it->second.inverseBindPoseTransform;
        }
        for(uint32_t i = 0; i < node->mNumChildren; ++i)
            computeInverseBindPoseTransforms(time, node->mChildren[i],
                mesh_matrix, bm);
    }
    
    void AnimatedAssimpModel::draw(const aiNode *node, const Matrix4x4 &parent_transform) {
        Matrix4x4 node_transform;
        AnimatedAssimpModel::copyAssimp(node->mTransformation, node_transform);
        Matrix4x4 model_matrix = parent_transform * node_transform;
        if(node->mNumMeshes > 0) {
            UniformBlockVariable_t ubv;
            mDrawProgram->getUniformBlockVariable("gModelMatrix", ubv);
            mTransformations->bind();
            mTransformations->update(ubv.offset, sizeof(Matrix4x4), &model_matrix);
            mTransformations->unbind();
        }
        for(uint32_t idx = 0; idx < node->mNumMeshes; ++idx) {
            const MeshData_t &cur = mMeshes.at(node->mMeshes[idx]);
            mDrawProgram->setBuffer("Material", *cur.material);
            cur.vf->bind();
            if(cur.texture)
                mDrawProgram->activateTextureUnits({{mSampler, cur.texture}});
            glDrawElements(GL_TRIANGLES, cur.numberFaces * 3, GL_UNSIGNED_INT, 0);
            cur.vf->unbind();
        }
        for(uint32_t idx = 0; idx < node->mNumChildren; ++idx)
            draw(node->mChildren[idx], model_matrix);
    }
    
    void main() {
    #if 0
        vec4 vp_start = vec4(0,0,0,0);
        vec4 vp_end = vec4(0,0,0,0);
        vec4 ls_pos_start = vec4(viPosition, 1.0);
        vec4 ls_pos_end = vec4(viPosition + viNormal, 1.0);
        for(int i = 0; i < 4; ++i) {
            vp_start += viBoneWeights[i] * gIBPT[viBoneIDs[i]] * ls_pos_start;
            vp_end += viBoneWeights[i] * gIBPT[viBoneIDs[i]] * ls_pos_end;
        }
    #else
        mat4 bone_transform = mat4(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);
        vec4 vp_start = bone_transform * vec4(viPosition, 1.0);
        vec4 vp_end = bone_transform * vec4(viPosition + viNormal, 1.0);
    #endif
        vec4 ws_start = gModelMatrix * vp_start;
        vec4 ws_end = gModelMatrix * vp_end;
        vec4 n_normal = normalize(ws_end - ws_start);
        vs_out.normal = n_normal.xyz;
        vs_out.texCoord = viTexCoord;
        vs_out.lightDir = normalize(vec4(gEyeWorldPos, 1.0) - ws_start).xyz;
        vec4 cs_pos = gViewMatrix * ws_start;
        gl_Position = gProjectionMatrix * cs_pos;
    }
    
     
  • Can't really tell you what's wrong. If both mesh parts look and animate correctly when rendering separately, the basic math seems to be working. Maybe your shader uses wrong bone indices when animating both mesh parts together?

     
  • I have one more question then since that could be it.  In your second post about skinning "const aiMesh* mesh = theMeshYouWantToRender();", you compute the bone matrix for each mesh you want to render.  There is no way to do that in one pass?

    What I'm doing is computing the bone matrix once by traversing the root node and uploading the bone matrices.  I then draw the meshes via root traversal using the already uploaded bone matrix.

     
  • Hi ulfjorensen,

    Sorry about that, I was trying to merge three questions into one so it ended up in nonsense.

    Q.1) Let's say there are 32 bone matrices.  I traverse the node hierarchy (as in the posted code),compute the entire 32 bone matrices, and upload it to the shader.  By doing this traversal, I was able to merge your animation and skinning into one tree traversal step.  I was able to get skinning (with missing hat and lamp) in this manner, so I wanted to check if this is equivalent to your 2nd post.

    Q.2) Could you verify if the following is the correct interpretation of animation and skinning.  For the animation portion, I update each node's mTransform with the interpolated key frame values.  When I do the drawing, I concatenate each mTransform as I go down the root node.  This concatenated matrix is the gModelMatrix in the shader code.  Thus far, I see animation working. 

    When I compute the bone matrices (gIBPT in the shader code), I also do the root traversal passing down concatenated mTransform.  At a particular bone, I compute the bone using the mOffsetMatrix.  These are sent to the shader AND when I render, I still pass in the gModelMatrix I described earlier.

    Q.3) I noticed that the animation of the lamp and hat works with the bone matrices (gIBPT) set to identity.  Assuming my description earlier is correct, how does gModelMatrix * (gIBPT=root * intermediate_mesh * bone1 * bone2 * offset) work out?  Are the lamp/hat not supposed to have bone matrices?  I think that's my only confusion because skinning works and that is using the same gModelMatrix but gIBPT is not identity.

    Thank you very much for your time,
    Phi

     
  • Q1) After traversing the node hierarchy and concatenating the node transformations you DONT have a bone matrix, just a simple global node transformation matrix. The bone's offset matrix is missing in that sequence, and each mesh can have individual bone offset matrices. Your approach only works if all meshes use the same bone offset matrix for all bones. Which, apparently, isn't the case for the lamp and hat.

    Q2) I'm not fluent in OpenGL terminology, so please correct me if I'm wrong. If you concatenate mTransform-s down to the root, you get a glModelMatrix for that node. This is the transformation you'd use to draw a static mesh in that node, and it should correctly move around when applying a node animation to that node.

    The second part of Q2 is also correct, except for a constant offset to all bones in case your mesh is not located at the root node. Imagine it that way: you concatenate mTransform from a bone node down to the root node. You mesh also has a transformation - the glModelMatrix. This transformation also consists of mTransforms concatenated from the mesh node down to the root node. Your bone matrix (root * meshnode * bone1 * bone2 * offset) does contain this mesh transformation, too. The offset matrix only compensates for the partial sequence from bone2 down to meshnode, but not for the part from meshnode to root. If you have a transformation there (usually, you haven't), you get your skinned mesh still animated correctly, but possibly offset and/or rotated as a whole.

    Q3) Again, this is caused by the different bone offset matrices that all meshes specify for a bone. It should look correctly if you calculate the bone matrices for each mesh individually, using each mesh's offset matrices. If you collapse the calculations into one, you might end up using bone matrices calculated for the body when rendering the lamp. That might explain the weird animation of the lamp and hat.

     
  • Hi ulfjorensen,

    Thank you very much for your clarifications!  Your terminology is correct :)  That was the problem.

    I was trying to do

      while( tempNode)
      {
        boneMatrices[a] *= tempNode->mTransformation;   // check your matrix multiplication order here!!!
        tempNode = tempNode->mParent;
      }
    

    in one tree traversal each frame.  I guess you have to do this or opt out for precomputing & storing everything.  Everything is working beautifully.