I just put together my first skeletal animation system, and IT WORKS!!! Which was exciting to see. However I've noticed the performance is abysmal. After profiling I found that for the skinned mesh I'm am using to test it takes between 3 to 4.5ms to fully update all of the bones in the armature…0_0. So I feel like I'm doing something wrong as that seems like an awfully long time. Is there anything within my logic below that could be optimized to get this time down? Really curious now how engines like unreal can manage so many concurrent animations at once.
Below is the relevant code, full source can be found here (can also add other chunks upon request): https://github.com/useless3d/useless3d/tree/skeletal_animation
The “updateCurrentAnimation” method is invoked from my primary loop:
glm::vec3 Armature::calcTranslation(const double& time, const usls::scene::animation::Channel& channel)
{
if (channel.positionKeys.size() == 1)
{
return channel.positionKeys[0].second;
}
size_t currentKeyIndex = 0;
for (size_t i = 0; i < channel.positionKeys.size() - 1; i++)
{
if (time < channel.positionKeys[i + 1].first)
{
currentKeyIndex = i;
break;
}
}
size_t nextKeyIndex = currentKeyIndex + 1;
double deltaTime = channel.positionKeys[nextKeyIndex].first - channel.positionKeys[currentKeyIndex].first;
double factor = time - channel.positionKeys[currentKeyIndex].first / deltaTime;
glm::vec3 start = channel.positionKeys[currentKeyIndex].second;
glm::vec3 end = channel.positionKeys[nextKeyIndex].second;
glm::vec3 delta = end - start;
glm::vec3 returnVal = start + (float)factor * delta;
return returnVal;
}
glm::quat Armature::calcRotation(const double& time, const usls::scene::animation::Channel& channel)
{
if (channel.rotationKeys.size() == 1)
{
return channel.rotationKeys[0].second;
}
size_t currentKeyIndex = 0;
for (size_t i = 0; i < channel.rotationKeys.size() - 1; i++)
{
if (time < channel.rotationKeys[i + 1].first)
{
currentKeyIndex = i;
break;
}
}
size_t nextKeyIndex = currentKeyIndex + 1;
double deltaTime = channel.rotationKeys[nextKeyIndex].first - channel.rotationKeys[currentKeyIndex].first;
double factor = time - channel.rotationKeys[currentKeyIndex].first / deltaTime;
glm::quat start = channel.rotationKeys[currentKeyIndex].second;
glm::quat end = channel.rotationKeys[nextKeyIndex].second;
glm::quat delta = glm::slerp(start, end, (float)factor);
delta = glm::normalize(delta);
return delta;
}
glm::vec3 Armature::calcScale(const double& time, const usls::scene::animation::Channel& channel)
{
if (channel.scalingKeys.size() == 1)
{
return channel.scalingKeys[0].second;
}
size_t currentKeyIndex = 0;
for (size_t i = 0; i < channel.scalingKeys.size() - 1; i++)
{
if (time < channel.scalingKeys[i + 1].first)
{
currentKeyIndex = i;
break;
}
}
size_t nextKeyIndex = currentKeyIndex + 1;
double deltaTime = channel.scalingKeys[nextKeyIndex].first - channel.scalingKeys[currentKeyIndex].first;
double factor = time - channel.scalingKeys[currentKeyIndex].first / deltaTime;
glm::vec3 start = channel.scalingKeys[currentKeyIndex].second;
glm::vec3 end = channel.scalingKeys[nextKeyIndex].second;
glm::vec3 delta = end - start;
glm::vec3 returnVal = start + (float)factor * delta;
return returnVal;
}
void Armature::updateBone(size_t index, double time, glm::mat4 parentMatrix)
{
auto& bone = this->bones[index];
usls::scene::animation::Channel channel;
for (auto& c : this->currentAnimation->channels)
{
if (c.name == bone.name)
{
channel = c;
break;
}
}
auto boneMatrix = glm::mat4(1.0f);
boneMatrix = glm::translate(boneMatrix, this->calcTranslation(time, channel));
boneMatrix = boneMatrix * glm::toMat4(this->calcRotation(time, channel));
boneMatrix = glm::scale(boneMatrix, this->calcScale(time, channel));
boneMatrix = parentMatrix * boneMatrix;
bone.matrix = boneMatrix;
for (auto& c : bone.children)
{
this->updateBone(c, time, boneMatrix);
}
}
void Armature::updateCurrentAnimation(double runTime)
{
double timeInTicks = runTime * this->currentAnimation->tps;
double animationTime = fmod(timeInTicks, this->currentAnimation->duration);
this->updateBone(0, animationTime, this->transform.getMatrix());
}