Jump to content
  • Advertisement
Sign in to follow this  
Waaayoff

Assimp/DX11 problem - Skinning won't work :( --- FIXED!

This topic is 2371 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying to render a skinned mesh. For now i can't even render one without any animation. I just calculate the combined matrix by multiplying the offset matrix of each bone with the combined local transformation, like this:

I call this function with the root joint and an identity matrix
void AnimationController::CombineTransforms(Joint* pJoint, const D3DXMATRIX& P)
{
D3DXMATRIX transf = pJoint->mLocalTransf /** pJoint->mAnimatedTransf*/ * P;

mFinalTransforms.push_back( pJoint->mOffsetTransf * transf );

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CombineTransforms(pJoint->mChildren, transf);
}
}


I get the matrices from assimp:
mLocalTransf = transformation in aiNode with the bone's name
mOffsetTransf = offset transformation in aiBone struct

Now in my understanding, mFinalTransforms should be filled with identity matrices. But it isn't! By sending this to my vertex shader, i get the following result:

cbuffer c_buffer
{
float4x4 World;
float4x4 WorldViewProj;
float4x4 FinalTransforms[100];
float3 Diffuse;
}

VS_OUT VShader(float4 position : POSITION, float4 normal : NORMAL, float2 texcoord : TEXCOORD, float4 weights : BLENDWEIGHT, int4 boneIndices : BLENDINDICES)
{
VS_OUT output;

// Apply skinning
float4 p = float4(0.0f, 0.0f, 0.0f, 1.0f);
float lastWeight = 0.0f;
int n = 3;

for(int i = 0; i < n; ++i)
{
lastWeight += weights;
p += weights* mul(position, FinalTransforms[boneIndices]);
}
lastWeight = 1.0f - lastWeight;
p += lastWeight * mul(position, FinalTransforms[boneIndices[n]]);
p.w = 1.0f;

output.position = mul(WorldViewProj, p);
output.color = float4(0.7, 0.7, 0.7, 1);
output.texcoord = texcoord;
return output;}

wtfzat.png

What is going on??
Oh and this is how it's supposed to look like:
goodk.png

Share this post


Link to post
Share on other sites
Advertisement
Okay i think i know what's wrong. The root bone's local transform != identity matrix. I think i'm supposed to multiply it with the aiNodes parents transforms (armature node, scene node, etc...). I'll get back to you.

Edit: Didn't work.. I was so sure it would :(

Share this post


Link to post
Share on other sites
Hidden
Okay so i ditched assimp's offset matrix and calculated it on my own:


void AnimationController::CalculateOffsets(Joint* pJoint, const D3DXMATRIX& P)
{
D3DXMATRIX combined = pJoint->mLocalTransf * P;

D3DXMatrixInverse(&pJoint->mOffsetTransf, 0, &combined);

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CalculateOffsets(pJoint->mChildren, combined);
}
}



And this is how i create the final transformation that's passed on to the shader:
void AnimationController::CombineTransforms(Joint* pJoint, const D3DXMATRIX& P)
{
D3DXMATRIX final = pJoint->mLocalTransf * pJoint->mAnimatedTransf * P;

mFinalTransforms.push_back( pJoint->mOffsetTransf * final );

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CombineTransforms(pJoint->mChildren, final);
}
}



When pJoint->mAnimatedTransf is equal to the identity matrix, as expected the model is rendered in its default T-pose. But if i change one bone's pJoint->mAnimatedTransf to a rotation matrix, it doesn't really animate well... I think its rotating globally, not locally...

This is how i calculate the matrix: D3DXMatrixRotationY(&transf, mTime/100);

Any help would be really appreciated....

Share this post


Link to post
Okay so i ditched assimp's offset matrix and calculated it on my own:


void AnimationController::CalculateOffsets(Joint* pJoint, const D3DXMATRIX& P)
{
D3DXMATRIX combined = pJoint->mLocalTransf * P;

D3DXMatrixInverse(&pJoint->mOffsetTransf, 0, &combined);

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CalculateOffsets(pJoint->mChildren, combined);
}
}



And this is how i create the final transformation that's passed on to the shader:
void AnimationController::CombineTransforms(Joint* pJoint, const D3DXMATRIX& P)
{
D3DXMATRIX final = pJoint->mLocalTransf * pJoint->mAnimatedTransf * P;

mFinalTransforms.push_back( pJoint->mOffsetTransf * final );

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CombineTransforms(pJoint->mChildren, final);
}
}



When pJoint->mAnimatedTransf is equal to the identity matrix, as expected the model is rendered in its default T-pose. But if i change one bone's pJoint->mAnimatedTransf to a rotation matrix, it doesn't really animate well... I think its rotating globally, not locally...


This is how i calculate the matrix: D3DXMatrixRotationY(&transf, mTime/100);

Any help would be really appreciated....

EDIT: How do i embed a youtube video?

Share this post


Link to post
Share on other sites
FINALLY! It worked.

Turns out Assimp generates animations relative to the parent joint, NOT the joint itself. So i don't have to multiply the animation transformation by the local transformation. Which i only use if there is no animation for a bone.. Also there was a problem with my shader. Instead of using the last bone to make sure a total weight of 1 is used, i use the first bone.. since there is no guarantee that the vertex will be influenced by 4 bones.

Oh and the Assimp's offset transformation works..

Note to Assimp/Directx users:
1) When loading a scene don't forget to use this flag: aiProcessPreset_TargetRealtime_Quality | aiProcess_ConvertToLeftHanded
2) You might also want to remove aiProcess_FindInvalidData. If you keep it, Assimp will remove redundant keys, leaving you with an uneven number of rotation/position/scale keys per bone.
3) Transpose both local and offset transformations.

If anyone's interested, here's the code:
//=====================================================================================================
// Recursively calculate the joints' combined transformations
//=====================================================================================================
void AnimationController::CombineTransforms(Joint* pJoint, const D3DXMATRIX& P)
{
// pJoint->mAnimatedTransf is initialized with Assimp's local transform (which you can find like this: g_Scene->mRootNode->FindNode(bone_name)->mTransformation)
// Then it can be replaced per frame by an interpolated animation transformation for every bone that has an animation
// pJoint->mOffsetTransf you can find in an aiBone struct
// DO NOT FORGET TO TRANSPOSE when you load them

D3DXMATRIX final = pJoint->mAnimatedTransf * P;

//mFinalTransforms contains the matrices that will be sent to the shader
mFinalTransforms.push_back( pJoint->mOffsetTransf * final );

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CombineTransforms(pJoint->mChildren, final);
}
}




Vertex Shader
VS_OUT VShader(float4 position : POSITION, float4 normal : NORMAL, float2 texcoord : TEXCOORD, float4 weights : BLENDWEIGHT, int4 boneIndices : BLENDINDICES)
{
VS_OUT output;

float4 p = float4(0.0f, 0.0f, 0.0f, 1.0f);
float lastWeight = 0.0f;
int n = 3;

for(int i = n; i > 0; i--)
{
lastWeight += weights;
p += weights * mul(FinalTransforms[boneIndices], position);
}

lastWeight = 1.0f - lastWeight;
p += lastWeight * mul(FinalTransforms[boneIndices[0]], position);
p.w = 1.0f;

output.position = mul(WorldViewProj, p);
output.color = float4(0.7, 0.7, 0.7, 1);
output.texcoord = texcoord;
return output;
}

Share this post


Link to post
Share on other sites

FINALLY! It worked.

Turns out Assimp generates animations relative to the parent joint, NOT the joint itself. So i don't have to multiply the animation transformation by the local transformation. Which i only use if there is no animation for a bone.. Also there was a problem with my shader. Instead of using the last bone to make sure a total weight of 1 is used, i use the first bone.. since there is no guarantee that the vertex will be influenced by 4 bones.

Oh and the Assimp's offset transformation works..

Note to Assimp/Directx users:
1) When loading a scene don't forget to use this flag: aiProcessPreset_TargetRealtime_Quality | aiProcess_ConvertToLeftHanded
2) You might also want to remove aiProcess_FindInvalidData. If you keep it, Assimp will remove redundant keys, leaving you with an uneven number of rotation/position/scale keys per bone.
3) Transpose both local and offset transformations.

If anyone's interested, here's the code:
//=====================================================================================================
// Recursively calculate the joints' combined transformations
//=====================================================================================================
void AnimationController::CombineTransforms(Joint* pJoint, const D3DXMATRIX& P)
{
// pJoint->mAnimatedTransf is initialized with Assimp's local transform (which you can find like this: g_Scene->mRootNode->FindNode(bone_name)->mTransformation)
// Then it can be replaced per frame by an interpolated animation transformation for every bone that has an animation
// pJoint->mOffsetTransf you can find in an aiBone struct
// DO NOT FORGET TO TRANSPOSE when you load them

D3DXMATRIX final = pJoint->mAnimatedTransf * P;

//mFinalTransforms contains the matrices that will be sent to the shader
mFinalTransforms.push_back( pJoint->mOffsetTransf * final );

for (unsigned i = 0; i < pJoint->mChildren.size(); i++)
{
CombineTransforms(pJoint->mChildren, final);
}
}




Vertex Shader
VS_OUT VShader(float4 position : POSITION, float4 normal : NORMAL, float2 texcoord : TEXCOORD, float4 weights : BLENDWEIGHT, int4 boneIndices : BLENDINDICES)
{
VS_OUT output;

float4 p = float4(0.0f, 0.0f, 0.0f, 1.0f);
float lastWeight = 0.0f;
int n = 3;

for(int i = n; i > 0; i--)
{
lastWeight += weights;
p += weights * mul(FinalTransforms[boneIndices], position);
}

lastWeight = 1.0f - lastWeight;
p += lastWeight * mul(FinalTransforms[boneIndices[0]], position);
p.w = 1.0f;

output.position = mul(WorldViewProj, p);
output.color = float4(0.7, 0.7, 0.7, 1);
output.texcoord = texcoord;
return output;
}


Hi, I'm trying to extract bones for the bind pose from assimp as well and I'd like to extract the animation frames as well. Right now I'm having a problem in the number of bone weights that influence each vertex. I tried to specify the flag aiProcessPreset_TargetRealtime_Quality which include also aiProcess_LimitBoneWeights which is supposed to cut the number to 4 (as stated in the assimp documentation). Thing is ... it doesn't work and I still get loads of weights per vertex when I iterate through the mBones array in the mesh.
On top of this if you have some link or tutorial or forum post that explain how to extract bind bose matrices from Assimp and how to animate the model I'll appreciate that.
In the meantime what I understand is that:

struct aiBone
{
//! The name of the bone.
C_STRUCT aiString mName;

//! The number of vertices affected by this bone
//! The maximum value for this member is #AI_MAX_BONE_WEIGHTS.
unsigned int mNumWeights;

//! The vertices affected by this bone
C_STRUCT aiVertexWeight* mWeights;

//! Matrix that transforms from mesh space to bone space in bind pose
C_STRUCT aiMatrix4x4 mOffsetMatrix;

...

};


mOffsetMatrix transform to bind pose ? so I should apply this straight away ? or what ?

But still, if limit bone weights doesn't cut to 4 I'm wasting time...

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!