5
\$\begingroup\$

I'm using Assimp to do skeletal animation in my OpenGL application.

I used Blender to export this one-boned model to a COLLADA file: one-boned model

The model has only one bone, called arm bone, that controls the arm mesh. All the other meshes are static.

I made several structures and classes that help me play animations. All the nodes are added to an std::vector of Node objects. each Node contains aiNode data and a toRoot matrix. The bone hierarchy is encapsulated in a Skeleton class, and the animation matrix (T * R) are updated for each bone in a class called Animation.

My Model::Draw() function is this:

void Model::draw()
{
    //iterate through all animation sets. if the animation is running, update the bones it affects.
    for(size_t i = 0; i < _animations.size(); i++)
        if(_animations[i].running())
            _animations[i].updateAnimationMatrices(&_skeleton);

    //calculate Bone::finalMatrix for each bone
    _skeleton.calculateFinalMatrices(_skeleton.rootBone());

    //iterate through the nodes and draw their meshes.
    for(size_t i = 0; i < _nodes.size(); i++)
    {
        _shaderProgram.setUniform("ModelMatrix", _nodes[i].toRoot());

        _nodes[i].draw();
    }
}

To get the "animationMatrix" for each bone (the TR matrix) I call Animation::updateAnimationMatrices(). Here's what it looks like:

void Animation::updateAnimationMatrices(Skeleton *_skeleton)
{
    double time = ((double)_timer.elapsed() / 1000.0);
    while(time >= _animation->mDuration) time -= _animation->mDuration;

    //iterate through aiNodeAnim (called channels) and update their corresponding Bone.
    for(unsigned int iChannel = 0; iChannel < _animation->mNumChannels; iChannel++)
    {
        aiNodeAnim *channel = _animation->mChannels[iChannel];
        Bone *bone = _skeleton->getBoneByName(channel->mNodeName.C_Str(), _skeleton->rootBone());

        //rotation
        glm::mat4 R = ... //calculate rotation matrix based on time

        //translation
        glm::mat4 T = ... //calculate translation matrix based on time

        //set animation matrix for the bone
        bone->animationMatrix = T * R;
        bone->needsUpdate = true;
    }
}

Now in order to calculate the "finalMatrix" for each bone (based on animationMatrix, offsetMatrix etc..), and upload it to the vertex shader, I call Skeleton::calculateFinalMatrices().

void Skeleton::calculateFinalMatrices(Bone *root)
{
    if(root)
    {
        Node *node = _getNodeByName(root->name->C_Str());
        if(node == nullptr) {
            std::cout << "could not find corresponding node for bone: " << root->name->C_Str() << "\n";
            return;
        }

        if(root->needsUpdate) //update only the bones that need to be updated (their animationMatrix has been changed)
        {
            root->finalMatrix = root->animationMatrix * root->offsetMatrix;

            //upload the bone matrix to the shader. the array is defined as "uniform mat4 Bones[64];"
            {
                std::string str = "Bones[";

                char buf[4] = {0};
                _itoa_s(root->index, buf, 10);

                str += buf;
                str += "]";

                _shaderProgram->setUniform(str.c_str(), root->finalMatrix);
            }

            root->needsUpdate = false;
        }

        for(unsigned int i = 0; i < root->numChildren; i++)
            calculateFinalMatrices(root->children[i]);
    }
}

Here's my bone structure, if it helps.

My glsl vertex shader is pretty standard. Here it is.

And finally, here's the result I get: (ignore the model's static legs. that must be some bug in the Blender exporter). enter image description here

And here's the result I should get: (using a 3d party software) enter image description here

It looks like there's something wrong with the bone's matrix calculation, although I don't know what. Any ideas or tips?

Thanks!

\$\endgroup\$
  • \$\begingroup\$ It seems like you're ignoring your bind matrix? This is a great tutorial that was fundamental in getting my own COLLADA based renderer to work \$\endgroup\$ – Panda Pajama May 13 '15 at 9:44
  • \$\begingroup\$ What's the bind matrix? I'm using the offsetMatrix that is also called the inverse bind pose matrix. Is that it? \$\endgroup\$ – Pilpel May 13 '15 at 9:48
  • \$\begingroup\$ Are you transposing the inverse bind matrix (root->offsetMatrix) somewhere? Collada has them in row major and your multiplication order suggests column major. \$\endgroup\$ – rudy May 26 '15 at 14:26
2
\$\begingroup\$

Read this through and my best guess is that there is something wrong here:

root->finalMatrix = root->animationMatrix * root->offsetMatrix;

How is offsetMatrix calculated? And what is it supposed to do (I know somekind of offset).

Would also like to see these calculations:

//rotation
glm::mat4 R = ... //calculate rotation matrix based on time

//translation
glm::mat4 T = ... //calculate translation matrix based on time

From what you have showed my guess is that root->offsetMatrix translates the arm away from origo to somewhere (maybe the position where u would like it to be). And then root->animationMatrix first takes the inverse of root->offsetMatrix and then puts the rotation you want on that.

For a quick try, see if this works:

root->finalMatrix =  root->offsetMatrix * root->animationMatrix * root->offsetMatrix;

Although this has some extra calucation atm (if it is like I think it might be), but you will quickly find out if it works.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.