Jump to content

  • Log In with Google      Sign In   
  • Create Account


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


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
4 replies to this topic

#1 Waaayoff   Members   -  Reputation: 779

Like
0Likes
Like

Posted 30 October 2011 - 01:13 PM

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[i], 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[i];
		p += weights[i]* mul(position, FinalTransforms[boneIndices[i]]);
	}
	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;}
Posted Image

What is going on??
Oh and this is how it's supposed to look like:
Posted Image
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "

Sponsor:

#2 Waaayoff   Members   -  Reputation: 779

Like
0Likes
Like

Posted 30 October 2011 - 03:12 PM

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 :(
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "

#3 Waaayoff   Members   -  Reputation: 779

Like
0Likes
Like

Posted 31 October 2011 - 02:52 PM

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[i], 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[i], 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?
"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "

#4 Waaayoff   Members   -  Reputation: 779

Like
3Likes
Like

Posted 01 November 2011 - 10:38 AM

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[i], 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[i];
		p += weights[i] * mul(FinalTransforms[boneIndices[i]], 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;
}

"Spending your life waiting for the messiah to come save the world is like waiting around for the straight piece to come in Tetris...even if it comes, by that time you've accumulated a mountain of shit so high that you're fucked no matter what you do. "

#5 MegaPixel   Members   -  Reputation: 227

Like
0Likes
Like

Posted 20 January 2012 - 05:35 AM

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[i], 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[i];
		p += weights[i] * mul(FinalTransforms[boneIndices[i]], 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...






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS