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++)
{
_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).
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!