importing bone animation keyframes with assimp

by Luca   Last Updated April 16, 2018 06:13 AM

I'm writing a simple 3d engine with Direct3D11 and I'm into the processo of importing a rigged and animated model into my application with Assimp. I succesfully managed to import a static model and a hierarchy of bones/nodes. So now I have my vertex structure filled with the bone IDs and weight. The last step is to import the aiBoneAnim data from each aiAnimation structure in order to load all the local tranformations for each bone for each keyframe in the animation.

Problem is, each keyframe contains three data structure (two vectors for scaling and position and a quaternion for rotations), each containing a timestamp and the corresponding value. I assume that each data structure can have a different timestamp (and a different total number of keys) so I can't simply iterate over them and add all of them to the same keyframe. This is my function so far (in a sort of pseudo code, the for loop doesn't make much sense):

void AnimatedModel::LoadAnimations(const aiScene *scene)
/* for each animation */
for (int i = 0; i < scene->mNumAnimations; i++)
    aiAnimation *animation = scene->mAnimations[i];
    std::map<std::string, BoneAnimation> boneAnimations;

    /* for each bone */
    for (int j = 0; j < animation->mNumChannels; j++)
        aiNodeAnim *nodeAnimation = animation->mChannels[j];
        std::vector<KeyFrame> keyFrames;
        float time;
        XMFLOAT3 position;
        XMFLOAT3 scale;
        XMFLOAT4 rotation;

        /* load all keyframes */
        for (/* I need to store all keyframes, updating only the component that changes */)
            for (int p = 0; p < nodeAnimation->mNumPositionKeys; p++)
                position.x = nodeAnimation->mPositionKeys[p].mValue.x;
                position.y = nodeAnimation->mPositionKeys[p].mValue.y;
                position.z = nodeAnimation->mPositionKeys[p].mValue.z;
            for (int s = 0; s < nodeAnimation->mNumScalingKeys; s++)
                scale.x = nodeAnimation->mScalingKeys[s].mValue.x;
                scale.y = nodeAnimation->mScalingKeys[s].mValue.y;
                scale.z = nodeAnimation->mScalingKeys[s].mValue.z;
            for (int r = 0; r < nodeAnimation->mNumRotationKeys; r++)
                rotation.x = nodeAnimation->mRotationKeys[r].mValue.x;
                rotation.y = nodeAnimation->mRotationKeys[r].mValue.y;
                rotation.z = nodeAnimation->mRotationKeys[r].mValue.z;
                rotation.w = nodeAnimation->mRotationKeys[r].mValue.w;
            keyFrames.push_back(KeyFrame(time, position, rotation, scale));

        /* construct bone animations */
        BoneAnimation boneAnimation(keyFrames);
        boneAnimations[nodeAnimation->mNodeName.C_Str()] = boneAnimation;           

    /* construct animation */
    Animation skeletalAnimation(animation->mDuration, boneAnimations);

    /* add animation */
    mAnimations[animation->mName.C_Str()] = skeletalAnimation;        


how can I create all the keyframes, one keyframe per timestamp, where the components change on a keyframe basis and stay the same if the corresponding animation component don't change?

EDIT ok, I found a way to import scaling, position and rotation keyframes into three different vectors, each containing a list of scaleKey, PositionKey and RotationKey object (with the correspondent 3dVector/quaternion and timestamp). Now I can play the animation but I encountered a strange issue with the Vertex shader. When I load the bone IDs and weights for each vertex (taking only the most relevant fours bones/weights and normalizing the result) and pass them to the vertex shader the result is correct only if I calculate the bone transforms without the last weight (or when I set the weight to zero). Actually just 20 vertices on a couple hundreds have a fourth weight, so I assume that there must be some issues while sending the vertex data to the shader. Here's the shader's code:

#define MAX_BONES 30

struct Vertex
    float3 position : POSITION;  
    float3 normal : NORMAL;
    float2 textureCoords : TEXTURE_COORD;
    int4 boneID : BONE_ID;
    float4 boneWeight : BONE_WEIGHT;

cbuffer wvp : register(b0)
    float4x4 wvp;

cbuffer boneTransforms : register(b1)
    float4x4 transforms[MAX_BONES];

float4 main(Vertex vertex, out float3 normal : PS_NORMAL, out float2 text_coord : PS_TEXTURE) : SV_POSITION
    float4 position = float4(vertex.position, 1.0f);
    position = mul(position,transforms[vertex.boneID.x]) * vertex.boneWeight.x +
               mul(position,transforms[vertex.boneID.y]) * vertex.boneWeight.y +
               mul(position,transforms[vertex.boneID.z]) * vertex.boneWeight.z +
               mul(position,transforms[vertex.boneID.w]) * vertex.boneWeight.w;     
    position = mul(position, wvp);
    text_coord = vertex.textureCoords;
    normal = vertex.normal;
    return position;

I debugged the application and seems that the bone ids/weights are loaded correctly.

Answers 1

I faced the same problem with you, I noticed that you've already found a way to load Assimp's aiNodeAnim data to create keyframes, one keyframe per timestamp. Here is my class of KeyFrame.

class KeyFrame

	//pose per keyframe,name and transform in bone-space in relation to parent bone
	//in this condition, each key frame has a boneTransform for every bone in the model
	map<string, JointTransform> jointKeyFrames; 
	float timeStamp;
the map contains all the joints with their name and JointTransform data. The JointTransform including the position and rotation for this joint at this timeStamp.

Could you please give me some hint to figure it out?

Thank you!

Cheers, Jiechang

April 16, 2018 06:04 AM

Related Questions

What is considered bind pose in Assimp

Updated April 22, 2017 13:13 PM

3D Skeletal animation + sub animation

Updated September 05, 2017 00:13 AM

Using assimp to parse animation data

Updated March 06, 2019 06:13 AM