2
\$\begingroup\$

first of all i would like to say that i read everything out there and still did not figure it out quite yet.

In my project i need to implement a skinning technique, but before going there i need to get the standar Linear Blend Skinning working.

I'm using assimp to load my model. For testing purposes my model is a cylindre with a single mesh and 2 bones. There are no animations, the goal of my demo is to manually move the bones.

I read every tutorial/article outhere about assimp and skeletal animation. So i think i got the loading part right. I got the mTransformation from the nodes and got the offsetMatrix from the meshes with the same name (i used a map to store the bones/names).

i did some printing to verify my structure and i'm loading the correct info, i'm getting identity matrices everywhere (wich i think is normal since there is no animation and it's just a simple mesh).

The part i could not figure out still , is how i apply these transformations to my vertices. This point affects the loading too ,when i apply a scale/translation on a mesh in blender i can't get it in my renderer.

in my bones i store 2 matrices and i have a third one that represents the final transformation. I did not understand how i would compute this one, i saw some people using and inverse of the root transform and i have a hard time coding something i don't understand.

struct Weights
{
    uint vertexID;
    float weight;
};

struct Bone
{
    Bone():parent(nullptr){}
    ~Bone()
    {
        for(auto p: children)
                delete p;
    }


    std::string name; // bone name

    QMatrix4x4 nodeTransform; //Transformation of the node
    QMatrix4x4 offsetMatrix;    // Going into the bone's space
    QMatrix4x4 finalTransfrom; // where we store the final transfrom

    QVector<Weights> boneWeights; // bone wheights


    Bone *parent; // pointer to the parent node nullptr if root
    QVector<Bone*> children; // storing pointers to the children
};

thank you for help !

\$\endgroup\$
1
  • \$\begingroup\$ Posting this as comment because I'm not sure if I understand you right. But when you want to know in which order you have to calculate the matrixes of parent bones to get the final matix this tutorial might help you tutorial. If it was helpful I can post it as an answer. \$\endgroup\$
    – Skalli
    Mar 17, 2016 at 15:59

2 Answers 2

1
\$\begingroup\$

I was implementing the skeletal animation code recently, while not for Collada, I did it for GLTF, which is similar to Collada (also from Khronos), although it uses JSON + binary data rather than XML.

The understanding is the most important part. Once you understand the theory, the code will flow naturally. For skeletal animation, you need a mesh and a set of bones. Each vertex of the mesh has to include N weights (floats) and N bone indices (ints). Value of N depends on your usecase, usually 4 is used. You pass the weights and bone indices to your vertex shader as vertex attributes (vec4 weights and ivec4 bone indices, for example).

As for the bones themselves, they have matrices. For skinning, each bone needs to know it's "inverse bind pose" matrix (mOffsetMatrix in assimp) and global pose matrix.

In the end, you will do a transformation like this in the vertex shader:

for (int i = 0; i < 4; i++) position += weights[i] * globalPose[bone_indices[i]] * invBindMatrix[bone_indices[i]] * vertex.position;

Global pose matrix can be calculated from local pose matrices. Each bone has one, local pose matrix is the matrix that you obtain from currently running animation, it describes the translation/rotation of the bone in bone local space. To get global pose matrices, you just start with the root bone. Set the root bone global pose matrix to local pose matrix (because it has no parent). Go down the bone hierarchy, and calculate global pose matrix as parent global pose matrix multiplied with the local pose matrix.

Skeletal animation is one of those things you randomly write code, try to piece together samples from tutorials, until suddenly you have the eureka moment, grasp the theoretical part and order of transformations and suddenly it all seems obvious to you.

\$\endgroup\$
0
\$\begingroup\$

I'm just starting to piece this together myself, but I've managed to apply transformations to bones by exporting them as collada files. Nothing else works AFAIK. This does not work for me with fbx.

Here is the "formula" as I understand it:

bone_transform = inverse(mRootNode->mTransformation) * aiBoneNode->parent->mTransformation * animation->RST matrix * aiBone->mOffsetMatrix; 

gl_Position = MVP * bone_transform * vertex;

I'll try and elaborate, minus all of the code I used to traverse aiScene... First, you need to get the offset matrix from the bone:

scene->mMeshes[1]->mBones[idx]->mOffsetMatrix)

Next, look up the transform matrices:

parentTransform(transform, scene->mAnimations[idx]->mChannels[cIdx]->mNodeName);
inline void parentTransform(glm::mat4 &result, aiNode* node) {
  if (node->mParent != 0x0) {
    glm::mat4 tmp = aiMatrix4x4ToGlm(&node->mParent->mTransformation);
    result = tmp  * result;
    parentTransform(result, node->mParent);
  }
}

Then, get the keyframe transformation:

                            glm::quat key_rotate(
                                scene->mAnimations[idx]->mChannels[cIdx]->mRotationKeys[0].mValue.w,
                                scene->mAnimations[idx]->mChannels[cIdx]->mRotationKeys[0].mValue.x,
                                scene->mAnimations[idx]->mChannels[cIdx]->mRotationKeys[0].mValue.y,
                                scene->mAnimations[idx]->mChannels[cIdx]->mRotationKeys[0].mValue.z
                            );

                            glm::mat4 key_translate = glm::translate(
                                glm::mat4(1.0f), 
                                glm::vec3(
                                    scene->mAnimations[idx]->mChannels[cIdx]->mPositionKeys[0].mValue.x,
                                    scene->mAnimations[idx]->mChannels[cIdx]->mPositionKeys[0].mValue.y,
                                    scene->mAnimations[idx]->mChannels[cIdx]->mPositionKeys[0].mValue.z
                                )
                            );

Next, mash it all together:

                                m_scenes[mIdx]->m_bones[bIdx].keyframe_transformation = 

                                glm::inverse(aiMatrix4x4ToGlm(&scene->mRootNode->mTransformation)) * 
                                //aiMatrix4x4ToGlm(&scene->mRootNode->mTransformation) * 
                                transform * 
                                //aiMatrix4x4ToGlm(&scene->mRootNode->mChildren[0]->mTransformation) *
                                key_translate *
                                glm::mat4_cast(key_rotate) *
                                m_scenes[mIdx]->m_bones[bIdx].offsetMatrix;

Send it to your vertex shader:

gl_Position = proj_matrix * mv_matrix * bones[boneId] * vec4(position, 1.0);

And voilà.

\$\endgroup\$
1
  • \$\begingroup\$ hi i just noticed your answer i'll get into it and get back to you ASAP thanks :) ! \$\endgroup\$
    – Bob Maza
    Dec 21, 2016 at 13:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .