# Skeletal animation in OpenGL

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:

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++)
{

_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 += "]";

}

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).

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

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

Thanks!

• 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 – Panda Pajama May 13 '15 at 9:44
• What's the bind matrix? I'm using the offsetMatrix that is also called the inverse bind pose matrix. Is that it? – Pilpel May 13 '15 at 9:48
• Are you transposing the inverse bind matrix (root->offsetMatrix) somewhere? Collada has them in row major and your multiplication order suggests column major. – rudy May 26 '15 at 14:26

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.