Asmodeus

04-25-2015, 03:35 AM

A time has come when i had to implement an Animation to my Engine. I started with standart MD5 mesh/anim format since i find it easy to understand. I managed to implement a simple MD5 loader and i can load MD5 meshes along with their animations. Animations work fine , i am using GPU skinning and CPU interpolation. Here comes my Problem: Performance (you can say i am obsessed with it). Currently i use Tutorial from OglDev which implements MD5 Skeletal Animation Tutorial as an example. As i said animations work fine but performance is horrible. With one Single MD5 animated Model in my scene i get ~60-80 FPS. If i add more models (~3-4 models) FPS drops to ~12-20. I know what causes that FPS drop , since i am doing a lot of computations CPU side every frame: Interpolating, filling Bone Matrices etc. Here is The Hefty CPU side part which runs every frame to compute Each Bone Transformation Matrices , from there i upload this Bone transformation Matrices to The shader and apply the skinning.

void SkinnedMesh::BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms)

{

Matrix4f Identity;

Identity.InitIdentity();

float TicksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ? m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);

float TimeInTicks = TimeInSeconds * TicksPerSecond;

float AnimationTime = fmod(TimeInTicks, (float)m_pScene->mAnimations[0]->mDuration);

ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);

Transforms.resize(m_NumBones);

for (uint i = 0 ; i < m_NumBones ; i++) {

Transforms[i] = m_BoneInfo[i].FinalTransformation;

}

}

Function Above takes vector of transformation matrices for each bone (i am working with a model that has ~71 Bones)

This function calculates interpolations etc..

void SkinnedMesh::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform)

{

string NodeName(pNode->mName.data);

const aiAnimation* pAnimation = m_pScene->mAnimations[0];

Matrix4f NodeTransformation(pNode->mTransformation);

const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);

if (pNodeAnim) {

// Interpolate scaling and generate scaling transformation matrix

aiVector3D Scaling;

CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);

Matrix4f ScalingM;

ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);

// Interpolate rotation and generate rotation transformation matrix

aiQuaternion RotationQ;

CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);

Matrix4f RotationM = Matrix4f(RotationQ.GetMatrix());

// Interpolate translation and generate translation transformation matrix

aiVector3D Translation;

CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);

Matrix4f TranslationM;

TranslationM.InitTranslationTransform(Translation. x, Translation.y, Translation.z);

// Combine the above transformations

NodeTransformation = TranslationM * RotationM * ScalingM;

}

Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;

if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {

uint BoneIndex = m_BoneMapping[NodeName];

m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;

}

for (uint i = 0 ; i < pNode->mNumChildren ; i++) {

ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);

}

}

I wont upload the following functions which are being called in ReadNodeHeirarchy , but you get the idea . The CPU calculations per frame are A LOT. Thus i get terrible performance. I am Quite new at animation and skinning. Somewhere i was reading about using Quaternions insted of Bone Transformation matrices. I am open to any suggestions on how i may improve or even change my animating technique.

PS: Vertex Skinning Shader Code:

#version 330

layout (location = 0) in vec3 Position;

layout (location = 1) in vec2 TexCoord;

layout (location = 2) in vec3 Normal;

layout (location = 3) in ivec4 BoneIDs;

layout (location = 4) in vec4 Weights;

out vec2 TexCoord0;

out vec3 Normal0;

out vec3 WorldPos0;

const int MAX_BONES = 120;

uniform mat4 gWVP;

uniform mat4 gWorld;

uniform mat4 gBones[MAX_BONES];

void main()

{

mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];

BoneTransform += gBones[BoneIDs[1]] * Weights[1];

BoneTransform += gBones[BoneIDs[2]] * Weights[2];

BoneTransform += gBones[BoneIDs[3]] * Weights[3];

vec4 PosL = BoneTransform * vec4(Position, 1.0);

gl_Position = gWVP * PosL;

TexCoord0 = TexCoord;

vec4 NormalL = BoneTransform * vec4(Normal, 0.0);

Normal0 = (gWorld * NormalL).xyz;

WorldPos0 = (gWorld * PosL).xyz;

}

void SkinnedMesh::BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms)

{

Matrix4f Identity;

Identity.InitIdentity();

float TicksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ? m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);

float TimeInTicks = TimeInSeconds * TicksPerSecond;

float AnimationTime = fmod(TimeInTicks, (float)m_pScene->mAnimations[0]->mDuration);

ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);

Transforms.resize(m_NumBones);

for (uint i = 0 ; i < m_NumBones ; i++) {

Transforms[i] = m_BoneInfo[i].FinalTransformation;

}

}

Function Above takes vector of transformation matrices for each bone (i am working with a model that has ~71 Bones)

This function calculates interpolations etc..

void SkinnedMesh::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform)

{

string NodeName(pNode->mName.data);

const aiAnimation* pAnimation = m_pScene->mAnimations[0];

Matrix4f NodeTransformation(pNode->mTransformation);

const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);

if (pNodeAnim) {

// Interpolate scaling and generate scaling transformation matrix

aiVector3D Scaling;

CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);

Matrix4f ScalingM;

ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);

// Interpolate rotation and generate rotation transformation matrix

aiQuaternion RotationQ;

CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);

Matrix4f RotationM = Matrix4f(RotationQ.GetMatrix());

// Interpolate translation and generate translation transformation matrix

aiVector3D Translation;

CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);

Matrix4f TranslationM;

TranslationM.InitTranslationTransform(Translation. x, Translation.y, Translation.z);

// Combine the above transformations

NodeTransformation = TranslationM * RotationM * ScalingM;

}

Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;

if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {

uint BoneIndex = m_BoneMapping[NodeName];

m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;

}

for (uint i = 0 ; i < pNode->mNumChildren ; i++) {

ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);

}

}

I wont upload the following functions which are being called in ReadNodeHeirarchy , but you get the idea . The CPU calculations per frame are A LOT. Thus i get terrible performance. I am Quite new at animation and skinning. Somewhere i was reading about using Quaternions insted of Bone Transformation matrices. I am open to any suggestions on how i may improve or even change my animating technique.

PS: Vertex Skinning Shader Code:

#version 330

layout (location = 0) in vec3 Position;

layout (location = 1) in vec2 TexCoord;

layout (location = 2) in vec3 Normal;

layout (location = 3) in ivec4 BoneIDs;

layout (location = 4) in vec4 Weights;

out vec2 TexCoord0;

out vec3 Normal0;

out vec3 WorldPos0;

const int MAX_BONES = 120;

uniform mat4 gWVP;

uniform mat4 gWorld;

uniform mat4 gBones[MAX_BONES];

void main()

{

mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];

BoneTransform += gBones[BoneIDs[1]] * Weights[1];

BoneTransform += gBones[BoneIDs[2]] * Weights[2];

BoneTransform += gBones[BoneIDs[3]] * Weights[3];

vec4 PosL = BoneTransform * vec4(Position, 1.0);

gl_Position = gWVP * PosL;

TexCoord0 = TexCoord;

vec4 NormalL = BoneTransform * vec4(Normal, 0.0);

Normal0 = (gWorld * NormalL).xyz;

WorldPos0 = (gWorld * PosL).xyz;

}