Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

I love assimp but the animation part is a pain - anyone got it working with DirectX?

Started by Paul C Skertich Aug 19, 2015 at 6:31 PM 9 replies 15.2k views
Original Post
Paul C Skertich
Paul C Skertich

as I said I love assimp - it's a great utility but the animation is a pain. So far my animation is stretched out but plays fine in assimp viewer. I looked at assimp viewer and it's hard to follow because I can't find any header files containing some of the functions. Like one function uses a vector and a boost:tuple for storing the position keys, rotation keys, and scaling keys of the animation. Also the code animation part runs on CPU - the ATSPACE.CO.UK runs on GPU and that's where my code came from.

I narrowed it down it's due to the global transformation. the offset transform is fine, the parentnode matrix is fine the nodetransform is fine but the globaltransform martrix going into finaltransformation to be sent to gpu is messing things up.

I quickly grabbed ieDoc's MD5 Animation loader and the dancing pill works - just a pill shape mesh dancing back and forth then side to side. I don't like MD5 mesh model because I tried to make a simple vehicle and it only imported just the front chassis of the vehicle not the wheels unlike unreal engine or unity could do.

Ah the frustrations!

jpetrie
jpetrie

Moved to graphics programming.

Buckeye
Buckeye




I narrowed it down it's due to the global transformation. ... messing things up

Sounds like you're almost there. What global transformation do you have, and what transformation should you have? You should be able to compare the two, and, at least, determine if it's a scale problem, or orientation about the wrong axis (or axes), etc.

If you set the global to the identity matrix, how do things look? I.e., is the animation correct, but the mesh isn't oriented correctly? Is the scaling correct?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
ericrrichards22
ericrrichards22

Are you converting the Assimp matrices so that they are in the DirectX row-column orientation? I can never remember which way is which, but I do recall that the matrices which Assimp loaded needed to be transposed before using them in my DirectX animation code.

If another example would be helpful, I wrote up how I did it, with using SlimDX and Assimp.net. It may not be totally correct - I've got some reports of meshes that are a little weird from some people - but I think I worked out most of it.

Paul C Skertich
Paul C Skertich

I narrowed it down it's due to the global transformation. ... messing things up

Sounds like you're almost there. What global transformation do you have, and what transformation should you have? You should be able to compare the two, and, at least, determine if it's a scale problem, or orientation about the wrong axis (or axes), etc.

If you set the global to the identity matrix, how do things look? I.e., is the animation correct, but the mesh isn't oriented correctly? Is the scaling correct?

What's weird when going through the debugger - the Z Scale wasn't at 1.0 it was 0.99 or something. I stepped through over and over again - I'm not sure if this is normal but it seems the globalTransformation is multipled each runthrough.



void SkeletalMesh::readAnimationNode(float animationTime, const aiNode *pNode, XMMATRIX &ParentTransform) {

	std::string NodeName = pNode->mName.data;
	const aiAnimation* pAnimation = scene->mAnimations[0];
	XMMATRIX NodeTransformation;

	ConvertUtil::aiMatrix4x4ToXMMatrix(&pNode->mTransformation, &NodeTransformation);
	const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);

	if (pNodeAnim) {
		// Interpolate scaling and generate scaling transformation matrix
		aiVector3D Scaling;
		CalcInterpolatedScaling(Scaling, animationTime, pNodeAnim);
		XMMATRIX ScalingM;
		ScalingM = XMMatrixScaling(Scaling.x, Scaling.y, Scaling.z);

		// Interpolate rotation and generate rotation transformation matrix
		aiQuaternion RotationQ;
		CalcInterpolatedRotation(RotationQ, animationTime, pNodeAnim);
		XMMATRIX RotationM;
		ConvertUtil::aiMatrix3x3ToXMMatrix(&RotationQ.GetMatrix(), &RotationM);


		// Interpolate translation and generate translation transformation matrix
		aiVector3D Translation;
		CalcInterpolatedPosition(Translation, animationTime, pNodeAnim);
		XMMATRIX TranslationM;
		TranslationM = XMMatrixTranslation(Translation.x, Translation.y, Translation.z);

		// Combine the above transformations
		NodeTransformation = TranslationM * RotationM * ScalingM; 

	}

	XMMATRIX GlobalTransformation =  ParentTransform *  NodeTransformation; //-- this is where everything gets funky.
	
	if (m_Bonemapping.find(NodeName) != m_Bonemapping.end()) {
		UINT BoneIndex = m_Bonemapping[NodeName];
		XMMATRIX offset = m_BoneInfo[BoneIndex].boneOffset;
		XMMATRIX worldinv = XMLoadFloat4x4(&worldInverse);

		m_BoneInfo[BoneIndex].finalTransform = worldinv * GlobalTransformation * m_BoneInfo[BoneIndex].boneOffset; 
	}

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


void SkeletalMesh::EvaluateAnimation(float animationtime, std::vector<XMMATRIX> & transforms) {
	
	XMMATRIX Identity =  XMMatrixIdentity();
	float TicksPerSecond = (float)(scene->mAnimations[0]->mTicksPerSecond !=0.0f ? scene->mAnimations[0]->mTicksPerSecond : 25.0f);

	animationtime *= TicksPerSecond;
	float animationTick = fmod(animationtime, (float)scene->mAnimations[0]->mDuration);

	readAnimationNode(animationTick, scene->mRootNode, Identity);
	transforms.resize(numBones);

	for (unsigned int i = 0; i < numBones; i++) {
		transforms[i] = XMMatrixIdentity(); //-- maybe bad idea - original idea was to flatten then transpose it.
		transforms[i] = m_BoneInfo[i].finalTransform;
	}
}

If anyone was asking what was worldInv matrix inside the m_boneInfo[x].finalTransformation - it's when the model gets loaded it gets tranposed into a Float4x4.

The whole skeletal reading I got from this :

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

I looked at some others and even the assimp_viewer when you build assimp - i can't find the header files inside VStudio project files. It was in pieces and I kept on looking over it and over it.

Maybe if I get around to it - I'll send the data to CPU than GPU and see how it does. IF it goes through the CPU without no problem then I will definately know the globaltransform is to blame.

Another thing to mention while debugging - the global transform gave me some weird floating points like 5.484884e-05 for instance in one of the rows; i'm not entirely sure what the whole e-05 bit i'm guessing it's exponent -5? All I know is I never saw that before. Another to mention is I'm transposed before going to shader inside render loop.

The shader code inside the vertex shader:


float4 weights = input.boneWeight;
	float4 boneID = input.boneID;
	
	input.position.w = 1.0f;
	float4x4 animationmat = gBones[boneID.x] * weights.x;
			 animationmat +=   gBones[boneID.y] * weights.y;
			 animationmat +=   gBones[boneID.z] * weights.z;
			 animationmat +=   gBones[boneID.w] * weights.w;
						   
	float4 localPos = mul(input.position,mWorld);
	float4x4 viewProj = mul(mView, mProj);
	output.position = mul(float4(localPos.xyz,1.0f), viewProj);
	output.position = mul(output.position, animationmat);
	

There's only three bones or four - can't remember but gBones is configured to allow 100 bones. Which the gBones get fed into by m_boneInfo[x].finalTransform.

the input element description for the bonesID and boneweights are (BoneID : DXGI_FORMAT_R32G32B32_FLOAT; BoneWeight: DXGI_FORMAT_R32G32B32_FLOAT).

I just wanted to fill out possibly all what could be raising issue.

Buckeye
Buckeye

Trying to help you out here - but you didn't respond to questions asked. However, from the wall of code you did post in response, and your comments indicating a lot of confusion, it appears you need to take one thing at a time. You may want to take a look at this debugging method to help your focus. Don't use a shotgun approach, posting bunches of stuff to "to fill out possibly all what could be raising issue." You need to determine where the problem may lie, and that will require you to understand what data you should have, and what the code should do with it.

Take a breath, and, as suggested in the debugging link, start at one point in your code. Determine if both the data and the code at that point are correct. Continue slowly, one step at a time, until, at a minimum, you can post something (brief) like "At this point in my code, I have such-and-such data." Then ask a specific question about the code and/or the data that you don't understand.

EDIT: FYI, the link you posted about "skeletal reading" is an OpenGL tutorial. You have to be careful and ensure you understand the differences between implementations in OGL and DirectX - things like column- versus row-major matrices, etc.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
Paul C Skertich
Paul C Skertich

Trying to help you out here - but you didn't respond to questions asked. However, from the wall of code you did post in response, and your comments indicating a lot of confusion, it appears you need to take one thing at a time. You may want to take a look at this debugging method to help your focus. Don't use a shotgun approach, posting bunches of stuff to "to fill out possibly all what could be raising issue." You need to determine where the problem may lie, and that will require you to understand what data you should have, and what the code should do with it.

Take a breath, and, as suggested in the debugging link, start at one point in your code. Determine if both the data and the code at that point are correct. Continue slowly, one step at a time, until, at a minimum, you can post something (brief) like "At this point in my code, I have such-and-such data." Then ask a specific question about the code and/or the data that you don't understand.

EDIT: FYI, the link you posted about "skeletal reading" is an OpenGL tutorial. You have to be careful and ensure you understand the differences between implementations in OGL and DirectX - things like column- versus row-major matrices, etc.

I understand openGL and DirectX run things differently - so I can understand translating openGL to DX will be a challenge to some extent. I don't know how to validate if the matrixes I'm looking at are valid because I don't have a working one to validate with. I went through the debugging process but the matrices are confusing. So from telling apart from the matrices I'm unable to understand at all what the matrices are suppose to mean - what is correct vs what is not.

Buckeye
Buckeye




I don't have a working one to validate with.

You said the file you're using (unspecified) works in the assimp viewer, so you do have a model that "works." If you're recoding OGL to DirectX 11, you're definitely going to need known data to test your code, as, at this point, it appears you may be writing code you're not sure of, and hoping it works with data you don't understand. Not a good thing.

Others may have better suggestions, but writing code without being able to test it is definitely not the way to go.

1. Use a very basic model, perhaps something with just 1 or 2 bones, with a very simple animation, perhaps just 1 or 2 frames of simple rotation. That may require working with a modeling program, such as Blender. Export it in a format that Assimp accepts, and which you can examine in text format. I.e., look at the matrices in the file and see if that's what you import.

2. google for something like "directx 11 animation example" and find code that is likely to work (rather than writing your own).

Just my opinion, but debugging code without a way to tell if the code is good or bad, with data that you don't know is good or bad ... futile.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly. You don't forget how to play when you grow old; you grow old when you forget how to play.
Paul C Skertich
Paul C Skertich

I don't have a working one to validate with.

You said the file you're using (unspecified) works in the assimp viewer, so you do have a model that "works." If you're recoding OGL to DirectX 11, you're definitely going to need known data to test your code, as, at this point, it appears you may be writing code you're not sure of, and hoping it works with data you don't understand. Not a good thing.

Others may have better suggestions, but writing code without being able to test it is definitely not the way to go.

1. Use a very basic model, perhaps something with just 1 or 2 bones, with a very simple animation, perhaps just 1 or 2 frames of simple rotation. That may require working with a modeling program, such as Blender. Export it in a format that Assimp accepts, and which you can examine in text format. I.e., look at the matrices in the file and see if that's what you import.

2. google for something like "directx 11 animation example" and find code that is likely to work (rather than writing your own).

Just my opinion, but debugging code without a way to tell if the code is good or bad, with data that you don't know is good or bad ... futile.

The assimp viewer program plays back the animation perfectly - so I know it's my app that's messing it up. You're right on that. I'll be exporting the offset transformations from the assimp imported animation to txt file then comparing the two.Also will be explorting global transformation, parent transformation and offset matrices as well.

The dancing pill only has three bones I believe that just goes side to side then back to front. That's as simple as it gets actually inside 3DS Max. I'll keep you posted.

Waaayoff
Waaayoff

Some time ago I went through the whole problem of getting animation to work from Assimp. I ended up getting it right and I explained how here but I didn't test it vigorously, it might help you though. You're probably not setting up your final transform correctly because Assimp isn't very clear whether it is storing a specific matrix absolutely or relatively to its parent.

I also suggest you use a very simple model as Buckeye suggested (I used a cuboid subdivided into 2 cubes, each cube having one bone, basically a very low poly arm).

"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. "

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.