Assimp Bone Transformation

I have been working on importing bone animation recently, so I made a three-dimensional model similar to minecraft, with some IK methods for testing the import of Assimp animation. The Ouput format is COLLADA (*. Dae), and the tool I used is Blender. On the programming side, my environment is opengl / glm / assimp. I think this information is enough for my problem. One thing, the animation of the model, I just record 7 frames to immediately play the animation of the allip.

Firstly, I think that my transformation, besides the local part of the transformation, is correct, so let the function return only glm::mat4(1.0f) , and the result shows the model of the binding representation (not sure). (see image below)

Secondly, return the value of glm::mat4(1.0f) to bone->localTransform = transform * scaling * glm::mat4(1.0f); , then the model is deformed. (see image below)

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

The code is 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; // TSR 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 idea behind the code refers to Matrix calculations for gpu skins and Skeletal animation with Assimp .

In general, I do all the information from the allip to the MeshModel and save it to the bone structure, so I think the information is ok?

Last, 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 is above, the lack of code or description is vague, please let me know, thanks!

Edit: (1)

In addition, my bone information like this (part of the code extraction):

 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 transform vector, so my code above is glm::mat4 transform = GetInterpolateTransform(time, bone->transformKeys); , Absloutely, gets the same meaning . I'm not sure I did a nomove keyframe animation that provides true or not conversion values โ€‹โ€‹(of course, it has 7 keyframes).

Keyframe contents like this (debugging on the head bone): keyframe from head 7 different keyframes, one and the same vector meaning.

Edit: (2)

If you want to test my dae file, I put it in jsfiddle , go in and get it :). Another thing is that in Unity my file works correctly, so I think that maybe there is a problem not in my local conversion, it seems that the problem may be different, such as parentTransform or bone-> transform ... etc .? I also add a local transformation matrix with all the bone, but I canโ€™t understand why COLLADA contains these values โ€‹โ€‹for my instant animation ...

+6
source share
1 answer

For the number of tests, and finally, the problem lies in the UpdateBone() .

Before I point out my problem, I need to say that a series of matrix multiplication allows me to be confused, but when I found a solution, it just made me fully (maybe only 90%) implement the whole matrix.

The problem comes from the article Matrix calculations for gpu skins . I suggested that the response code is absolutely right and you no longer need to think about the matrix. Thus, the misuse of the matrix terribly takes my view of the local transformation matrix. To return to the resulting image in the section of my question - binding, when I change the local transformation matrix to return glm::mat4(1.0f) .

So the question is, why has it changed by creating a binding? I suggested that the problem should be a local transformation in bone space, but I'm wrong. Before giving an 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; // TSR 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 am making the changes as shown 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 assimp_node->mTransformation gave me, but only the description of "Conversion relative to the parent node" in the assimp documentation. For some testing, I found that mTransformation is a call sign matrix that corresponds to the current node relative to the parent if I use them on the costa node. Let me give you a picture that captured the matrix on the head bone.

To prove

The left side is transform , which is extracted from assimp_node->mTransformation . The right part is my unmove localTransform animation, which is calculated using the keys from nodeAnim->mPositionKeys , nodeAnim->mRotationKeys and nodeAnim->mScalingKeys .

Take a look back at what I did, I did the snap conversion twice , so the image in my question section looks just separate, but not spaghetti :)

In the latter case, let me show what I did before disabling the test animation and fixing the animation result.

Result

(For everyone, if my concept is incorrect, please indicate to me! Thanks.)

+4
source

All Articles