Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assimp animation bone transformation

Recently I'm working on bone animation import, so I made a 3d minecraft-like model with some IK technique to test Assimp animation import. Ouput format is COLLADA(*.dae),and the tool I used is Blender. On the programming side, my enviroment is opengl/glm/assimp. I think these information for my problem is enough.One thing, the animation of the model, I just record 7 unmove keyframe for testing assimp animation.

First, I guess my transformation except local transform part is correct, so let the function only return glm::mat4(1.0f), and the result show the bind pose(not sure) model. (see below image)

Second, Turn back the value glm::mat4(1.0f) to bone->localTransform = transform * scaling * glm::mat4(1.0f);, then the model deform. (see below image)

Test image and model in blender: test and origin (bone->localTransform = glm::mat4(1.0f) * scaling * rotate; : this image is under ground :( )

The code here:

void MeshModel::UpdateAnimations(float time, std::vector<Bone*>& bones)
{
    for each (Bone* bone in bones)
    {
        glm::mat4 rotate = GetInterpolateRotation(time, bone->rotationKeys);
        glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);
        glm::mat4 scaling = GetInterpolateScaling(time, bone->scalingKeys);
        //bone->localTransform = transform * scaling * glm::mat4(1.0f);
        //bone->localTransform = glm::mat4(1.0f) * scaling * rotate;
        //bone->localTransform = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f));
        bone->localTransform = glm::mat4(1.0f);
    }
}

void MeshModel::UpdateBone(Bone * bone)
{
    glm::mat4 parentTransform = bone->getParentTransform();
    bone->nodeTransform = parentTransform
        * bone->transform  // assimp_node->mTransformation
        * bone->localTransform; // T S R matrix

    bone->finalTransform = globalInverse
        * bone->nodeTransform 
        * bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) {
        UpdateBone(bone->children[i]);
    }
}

glm::mat4 Bone::getParentTransform()
{
    if (this->parent != nullptr)
        return parent->nodeTransform;
    else
        return glm::mat4(1.0f);
}

glm::mat4 MeshModel::GetInterpolateRotation(float time, std::vector<BoneKey>& keys)
{
    // we need at least two values to interpolate...
    if ((int)keys.size() == 0) {
        return glm::mat4(1.0f);
    }
    if ((int)keys.size() == 1) {
        return glm::mat4_cast(keys[0].rotation);
    }

    int rotationIndex = FindBestTimeIndex(time, keys);
    int nextRotationIndex = (rotationIndex + 1);
    assert(nextRotationIndex < (int)keys.size());
    float DeltaTime = (float)(keys[nextRotationIndex].time - keys[rotationIndex].time);
    float Factor = (time - (float)keys[rotationIndex].time) / DeltaTime;
    if (Factor < 0.0f)
        Factor = 0.0f;
    if (Factor > 1.0f)
        Factor = 1.0f;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const glm::quat& startRotationQ = keys[rotationIndex].rotation;
    const glm::quat& endRotationQ = keys[nextRotationIndex].rotation;
    glm::quat interpolateQ = glm::lerp(endRotationQ, startRotationQ, Factor);
    interpolateQ = glm::normalize(interpolateQ);
    return glm::mat4_cast(interpolateQ);
}

glm::mat4 MeshModel::GetInterpolateTransform(float time, std::vector<BoneKey>& keys)
{
    // we need at least two values to interpolate...
    if ((int)keys.size() == 0) {
        return glm::mat4(1.0f);
    }
    if ((int)keys.size() == 1) {
        return glm::translate(glm::mat4(1.0f), keys[0].vector);
    }

    int translateIndex = FindBestTimeIndex(time, keys);
    int nextTranslateIndex = (translateIndex + 1);
    assert(nextTranslateIndex < (int)keys.size());
    float DeltaTime = (float)(keys[nextTranslateIndex].time - keys[translateIndex].time);
    float Factor = (time - (float)keys[translateIndex].time) / DeltaTime;
    if (Factor < 0.0f)
        Factor = 0.0f;
    if (Factor > 1.0f)
        Factor = 1.0f;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const glm::vec3& startTranslate = keys[translateIndex].vector;
    const glm::vec3& endTrabslate = keys[nextTranslateIndex].vector;
    glm::vec3 delta = endTrabslate - startTranslate;
    glm::vec3 resultVec = startTranslate + delta * Factor;
    return glm::translate(glm::mat4(1.0f), resultVec);
}

The code idea is referenced from Matrix calculations for gpu skinning and Skeletal Animation With Assimp.

Overall, I fectch all the information from assimp to MeshModel and save it to the bone structure, so I think the information is alright?

The last thing, my vertex shader code:

#version 330 core 
#define MAX_BONES_PER_VERTEX 4

in vec3 position;
in vec2 texCoord;
in vec3 normal;
in ivec4 boneID;
in vec4 boneWeight;

const int MAX_BONES = 100;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat4 boneTransform[MAX_BONES];

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
out float Visibility;

const float density = 0.007f;
const float gradient = 1.5f;

void main()
{
    mat4 boneTransformation = boneTransform[boneID[0]] * boneWeight[0];
    boneTransformation += boneTransform[boneID[1]] * boneWeight[1];
    boneTransformation += boneTransform[boneID[2]] * boneWeight[2];
    boneTransformation += boneTransform[boneID[3]] * boneWeight[3];


    vec3 usingPosition = (boneTransformation * vec4(position, 1.0)).xyz;
    vec3 usingNormal = (boneTransformation * vec4(normal, 1.0)).xyz;

    vec4 viewPos = view * model * vec4(usingPosition, 1.0);
    gl_Position =  projection * viewPos;
    FragPos = vec3(model * vec4(usingPosition, 1.0f));
    Normal = mat3(transpose(inverse(model))) * usingNormal;
    TexCoords = texCoord;
    float distance = length(viewPos.xyz);
    Visibility = exp(-pow(distance * density, gradient));
    Visibility = clamp(Visibility, 0.0f, 1.0f);
}

If my question above, lack of code or describe vaguely, please let me know, Thanks!

Edit:(1)

In additional, my bone information like this(code fetching part):

for (int i = 0; i < (int)nodeAnim->mNumPositionKeys; i++)
{
    BoneKey key;
    key.time = nodeAnim->mPositionKeys[i].mTime;
    aiVector3D vec = nodeAnim->mPositionKeys[i].mValue;
    key.vector = glm::vec3(vec.x, vec.y, vec.z);
    currentBone->transformKeys.push_back(key);
}

had some transformation vector, so my code above glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys);,Absloutely, get the same value from it. I'm not sure I made a nomove keyframe animation that provide the transform values is true or not (of course it has 7 keyframe).

A keyframe contents like this(debug on head bone): keyframe from head 7 different keyframe, same vector value.

Edit:(2)

If you want to test my dae file, I put it in jsfiddle, come and take it :). Another thing, in Unity my file work correctly, so I think maybe not my local transform occurs the problem, it seems the problem could be some other like parentTransform or bone->transform...etc? I aslo add local transform matrix with all bone, But can not figure out why COLLADA contains these value for my unmove animation...

like image 203
Tokenyet Avatar asked Sep 22 '16 07:09

Tokenyet


1 Answers

For amounts of testing, and finally found the problem is the UpdateBone() part.

Before I point out my problem, I need to say the series of matrix multiplication let me confuse, but when I found the solution, it just make me totally (maybe just 90%) realize all the matrix doing.

The problem comes from the article,Matrix calculations for gpu skinning. I assumed the answer code is absolutely right and don't think any more about the matrix should be used. Thus, misusing matrix terribly transfer my look into the local transform matrix. Back to the result image in my question section is bind pose when I change the local transform matrix to return glm::mat4(1.0f).

So the question is why the changed make the bind pose? I assumed the problem must be local transform in bone space, but I'm wrong. Before I give the answer, look at the code below:

void MeshModel::UpdateBone(Bone * bone)
{
    glm::mat4 parentTransform = bone->getParentTransform();
    bone->nodeTransform = parentTransform
        * bone->transform  // assimp_node->mTransformation
        * bone->localTransform; // T S R matrix

    bone->finalTransform = globalInverse
        * bone->nodeTransform 
        * bone->inverseBindPoseMatrix; // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) {
        UpdateBone(bone->children[i]);
    }
}

And I make the change as below:

void MeshModel::UpdateBone(Bone * bone)
{
    glm::mat4 parentTransform = bone->getParentTransform();
    if (boneName == "Scene" || boneName == "Armature")
    {
        bone->nodeTransform = parentTransform
            * bone->transform // when isn't bone node, using assimp_node->mTransformation
            * bone->localTransform;  //this is your T * R matrix
    }
    else
    {
        bone->nodeTransform = parentTransform // This retrieve the transformation one level above in the tree
            * bone->localTransform;  //this is your T * R matrix
    }

    bone->finalTransform = globalInverse // scene->mRootNode->mTransformation 
        * bone->nodeTransform  //defined above
        * bone->inverseBindPoseMatrix;  // ai_mesh->mBones[i]->mOffsetMatrix

    for (int i = 0; i < (int)bone->children.size(); i++) {
        UpdateBone(bone->children[i]);
    }
}   

I don't know what the assimp_node->mTransformation give me before, only the description "The transformation relative to the node's parent" in the assimp documentation. For some testing, I found that the mTransformation is the bind pose matrix which the current node relative to parent if I use these on bone node. Let me give a picture that captured the matrix on head bone.

Prove

The left part is the transform which is fetched from assimp_node->mTransformation.The right part is my unmove animation's localTransform which is calculated by the keys from nodeAnim->mPositionKeys, nodeAnim->mRotationKeys and nodeAnim->mScalingKeys.

Look back what I did, I made a bind pose tranformation twice, so the image in my question section look just seperate but not spaghetti :)

On the last, let me show what I did before the unmove animation testing and correct animation result.

Result

(For everyone, If my concept is wrong , please point me out! Thx.)

like image 149
Tokenyet Avatar answered Oct 21 '22 23:10

Tokenyet