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\$
3
  • \$\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\$ Commented May 13, 2015 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\$
    – McLovin
    Commented May 13, 2015 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
    Commented May 26, 2015 at 14:26

1 Answer 1

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\$

You must log in to answer this question.

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