Use assimp for skeletal animation HELP!

Started by
16 comments, last by emil0 4 years, 3 months ago

 

Guys, I've spent a lot of time to load skeletal animation

but  It is very difficult...

I refer to http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

but I didn't get the results I wanted

 

Please Help Me

 

This is my codes

 


void LoadAnimation::BoneTransform(float time, vector<XMFLOAT4X4>& transforms)
{
	XMMATRIX Identity = XMMatrixIdentity();

	float TicksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ?
		m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);
	float TimeInTicks = time*TicksPerSecond;
	float AnimationTime = fmod(TimeInTicks, (float)m_pScene->mAnimations[0]->mDuration);


	ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);

	transforms.resize(m_NumBones);


	for (int i = 0; i < m_NumBones; ++i) {
		XMStoreFloat4x4(&transforms[i], m_Bones[i].second.FinalTransformation);
	}
}

void LoadAnimation::ReadNodeHeirarchy(float AnimationTime, const aiNode * pNode, const XMMATRIX& ParentTransform)
{
	string NodeName(pNode->mName.data);

	const aiAnimation* pAnim = m_pScene->mAnimations[0];

	XMMATRIX NodeTransformation = XMMATRIX(&pNode->mTransformation.a1);

	const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnim, NodeName);

	if (pNodeAnim) {
		aiVector3D scaling;
		CalcInterpolatedScaling(scaling, AnimationTime, pNodeAnim);
		XMMATRIX ScalingM = XMMatrixScaling(scaling.x, scaling.y, scaling.z);
		ScalingM = XMMatrixTranspose(ScalingM);

		aiQuaternion q;
		CalcInterpolatedRotation(q, AnimationTime, pNodeAnim);
		XMMATRIX RotationM = XMMatrixRotationQuaternion(XMVectorSet(q.x, q.y, q.z, q.w));
		RotationM = XMMatrixTranspose(RotationM);

		aiVector3D t;
		CalcInterpolatedPosition(t, AnimationTime, pNodeAnim);
		XMMATRIX TranslationM = XMMatrixTranslation(t.x, t.y, t.z);
		TranslationM = XMMatrixTranspose(TranslationM);

		NodeTransformation = TranslationM * RotationM * ScalingM;
	}

	XMMATRIX GlobalTransformation = ParentTransform * NodeTransformation;

	int tmp = 0;
	for (auto& p : m_Bones) {
		if (p.first == NodeName) {
			p.second.FinalTransformation = XMMatrixTranspose(
				m_GlobalInverse *  GlobalTransformation * p.second.BoneOffset);
			break;
		}
		tmp += 1;
	}

	for (UINT i = 0; i < pNode->mNumChildren; ++i) {
		ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);
	}
}

 

CalcInterp~ function and Find~ function are like a tutorial
(http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html)

 

I think that I'm doing the multiplication wrong

but I don't know where it went wrong

If you want, i wall post other codes.

 

 here is my result

(hands are stretched, legs are strange)

image.thumb.png.d1ec27dfcae0cd0cbe233a6d7553603c.png

 

and it is ideal result

image.png.b1d33522cada2607144b0e4f8f4cc95e.png

Advertisement

Implementing skeletal animation can be a hard task the first time around. For easier debugging I suggest you do a very simple animation for a very simple model. Create a test scene with a cylinder animate by two bones or something like this. Also drawing the bone matrices as lines is very helpful.

Agree with turanszkij, start with really simple cases. In addition, it can also be really helpful to 'go by the numbers', create the simple joint, maybe just with a few verts to be skinned, then output all the translates, quaternions for each bone and the transformed vert positions.

If you can also export the baked animation from blender you can know the 'ground truth' positions that the vertices should be in, if everything is going perfect. Watch for the order of your transforms, and also differences between 'polarities' of things that blender is spitting out compared to your code. Sometimes flipping an axis or similar can fix things. I spent ages debugging where blender had output quaternions as wxyz and I was expecting xyzw or vice versa lol! :)

I was able to get assimp animation to work in DirectX. I see you followed an opengl tutorial but in DX matrix multiplication order is reverse of opengl order. In DX if you want to first rotate a vertex by XMMATRIX R1 and then translate by XMMATRIX T1 you do 


XMVector3Transform(v, R1 * T1)

Your bone FinalTransformation should be 


p.second.FinalTransformation = p.second.BoneOffset * GlobalTransformation * m_GlobalInverse;

Also make sure you read correctly from aiMatrix4x4 row-major data structure into XMFLOAT4X4 also row-major before loading into XMMATRIX.

On 2018. 1. 28. at 11:02 AM, Ivan Terziev said:

I was able to get assimp animation to work in DirectX. I see you followed an opengl tutorial but in DX matrix multiplication order is reverse of opengl order. In DX if you want to first rotate a vertex by XMMATRIX R1 and then translate by XMMATRIX T1 you do 



XMVector3Transform(v, R1 * T1)

Your bone FinalTransformation should be 



p.second.FinalTransformation = p.second.BoneOffset * GlobalTransformation * m_GlobalInverse;

Also make sure you read correctly from aiMatrix4x4 row-major data structure into XMFLOAT4X4 also row-major before loading into XMMATRIX.

first Thank you for your answer

I have some questions for you.

 

you told me to count 'BoneOffset * GlobalTransformation * m_blobalInverse'

but if I calculate that equation, the model is completyely broken

image.thumb.png.a94be4eb224c5d4dda9620cf2fa7fa5f.png

 

And I use this function to load XMMATRIX from aiMatirx4x4

XMMATRIX(&pNode->mTransformation.a1);

'pNode->mTransformation' is type aiMatrix4x4

I think it is no ploblem

because XMMATRIX can put the argument values of the array

please, I want you to tell me what method you used

thank you

On 2018. 1. 26. at 12:20 AM, turanszkij said:

Implementing skeletal animation can be a hard task the first time around. For easier debugging I suggest you do a very simple animation for a very simple model. Create a test scene with a cylinder animate by two bones or something like this. Also drawing the bone matrices as lines is very helpful.

thank you for your answer

I'll take your idea and try to test it wiht a simple model

 

On 2018. 1. 26. at 1:36 AM, lawnjelly said:

Agree with turanszkij, start with really simple cases. In addition, it can also be really helpful to 'go by the numbers', create the simple joint, maybe just with a few verts to be skinned, then output all the translates, quaternions for each bone and the transformed vert positions.

If you can also export the baked animation from blender you can know the 'ground truth' positions that the vertices should be in, if everything is going perfect. Watch for the order of your transforms, and also differences between 'polarities' of things that blender is spitting out compared to your code. Sometimes flipping an axis or similar can fix things. I spent ages debugging where blender had output quaternions as wxyz and I was expecting xyzw or vice versa lol! :)

First thank for your Idea

 I don't know when quaternion is xyzw or wxyz

Many articles just tell them to be careful

It is very difficult... 

 

Generally there are three types of transformations scaling, rotation and translation and you should be consistent with the order of multiplication. Usually it is scaling, rotation and translation, which is expressed in DX math as multiplication order S * R * T. So your NodeTransformation should be


NodeTransformation = ScalingM * RotationM * TranslationM;

and you should not be transposing your matrices when your read them from assimp (depending on your hlsl setting you should transpose your world, view and projection matrices). Then calculating final transformation remains 
 


p.second.FinalTransformation = p.second.BoneOffset * GlobalTransformation * m_GlobalInverse;

 

The above equations says the final transformation of a vertex to world space is: transform the vertex to bone space, then transform the vertex to scene space (assuming assimp bone transformations are from bone space to scene space) and finally transform it to world space.

It will be simpler if you test with assimp sceneTransform = identity and without scaling and then add them after the bone hierarchy looks good.

I hope it works!

 

18 hours ago, Ivan Terziev said:

Generally there are three types of transformations scaling, rotation and translation and you should be consistent with the order of multiplication. Usually it is scaling, rotation and translation, which is expressed in DX math as multiplication order S * R * T. So your NodeTransformation should be



NodeTransformation = ScalingM * RotationM * TranslationM;

and you should not be transposing your matrices when your read them from assimp (depending on your hlsl setting you should transpose your world, view and projection matrices). Then calculating final transformation remains 
 



p.second.FinalTransformation = p.second.BoneOffset * GlobalTransformation * m_GlobalInverse;

 

The above equations says the final transformation of a vertex to world space is: transform the vertex to bone space, then transform the vertex to scene space (assuming assimp bone transformations are from bone space to scene space) and finally transform it to world space.

It will be simpler if you test with assimp sceneTransform = identity and without scaling and then add them after the bone hierarchy looks good.

I hope it works!

 

Thanks for your response

I have some questions

 

First you say that I shouldn't be transposing matrices when read them from assimp,

but I think that assimp is opengl format 

so would i have to do transpose to get the correct col/row ?

Actually, I tried removing transpos function when using assimp to read my model,

but the result is very bad (It was completely disassembled)

 

second I agree that you told me to do S * R * T

because I saw it in my book when i was studying

but strangely the result of S * R * T and T * R * S were same, so i didn't fix it

 

hmmm..... assimp is very useful library to load fbx format

but load anmation, it's very hardly...

 

I don't have enough English skills to read it

so you would be inconvenient to read it

Thank you for your faithful reply!

Yeah I understand, the problem with linear transformations is that you have to get both the equations and the data right and it is really hard to debug using the standard debugger and on top of this there are the opengl and directx differences. So in my case I only transpose when I read from aiMatrix4x4. I don't transpose when I read quaternions or vectors. 


const aiMatrix4x4& offset = bone.mOffsetMatrix;
XMMATRIX meshToBoneTransform = XMMatrixTranspose(
	XMMATRIX(offset.a1, offset.a2, offset.a3, offset.a4,
			 offset.b1, offset.b2, offset.b3, offset.b4,
			 offset.c1, offset.c2, offset.c3, offset.c4,
			 offset.d1, offset.d2, offset.d3, offset.d4));

Do the same for (transpose) XMMATRIX(&pNode->mTransformation.a1);

If you are sending FinalTransformation matrices to hlsl for skinning on the GPU you may have to transpose them depending on how you calculate your final vertex world position.

This topic is closed to new replies.

Advertisement