Sign in to follow this  

Skinning - bones are bouncing - quaternion->matrix conversion

Recommended Posts

Posted (edited)

The issue of the day is that my skinning bones are bouncing from pose to pose, instead of rotating in place.

 

I export skinning matrices from 3ds max like in this article: https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/skinned-mesh-animation-using-matrices-r3577

 

At least I think I do.

 

The animation works as expected if I interpolate between matrices, but interpolation between matrices does not look nice. So before doing the interpolation I convert the matrices to quaternions and interpolate between quaternions using nlerp, then I convert back to a matrix. I interpolate the position part of the matrices separately and add that back to the matrix generated from the quaternion interpolation. This is where things go wonky I believe. Something is off with positions, making the bones bounce from pose to pose. Is this procedure not a valid way to do things? Perhaps I can adjust the positions in some way after the quaternion interpolation?

Edited by CarlML

Share this post


Link to post
Share on other sites

Perhaps my question was not clear. Should it be possible to convert a "compound" matrix that has several transformations multiplied into one (up and down the bone chain) into quaternion rotation and vector translation, interpolate them separately and put them together again? Or do I need to handle translation completely separate?

 

Here is some code showing what I do:

 

At load time:

bones[i].rotation.MakeQuat(matrix);
bones[i].pos = D3DXVECTOR3(matrix._41, matrix._42, matrix._43);

In the render loop:

Quat quat = bonesCurrent[i].rotation;
quat.Lerp(bonesNext[i].rotation, timeAspect);
quat.Normalize();

D3DXVECTOR3 pos = bonesCurrent[i].pos + (bonesNext[i].pos - bonesCurrent[i].pos) * timeAspect;

matrix[i] = quat.MakeMatrix();
matrix[i]._41 = pos.x;
matrix[i]._42 = pos.y;
matrix[i]._43 = pos.z;

In the vertex shader:

float4x4 anim = animMatrix[bones.x] * boneWeights.x;
anim += animMatrix[bones.y] * boneWeights.y;
anim += animMatrix[bones.z] * boneWeights.z;
outPos = mul(anim, position);

Share this post


Link to post
Share on other sites
Posted (edited)

I'm pretty sure you need to do the quat/interpolation stuff before taking transforms through the hierarchy.

The thing is that the animation behaves correctly if I skip the quat conversion and just inperpolate matrices (although matrix interpolation won't look good). After the quat conversion the rotations are correct but the positions are off (inbetween keyframes) so I don't think there is an issue with the quat->matrix conversion code. From what I can gather from the article I linked only one composite matrix is exported from the 3d package for each bone and keyframe. Do you mean I should export bind poses and animation poses and calculate the whole transformation chain each render loop?

 

Edit: Also, If I export a keyframe every frame from 3ds max (60 frames/sec) the animation looks correct since the bouncing inbetween keyframes is not noticable but that is not a solution due to the large memory impact and it shouldn't be needed.

Edited by CarlML

Share this post


Link to post
Share on other sites
Posted (edited)

I'm pretty sure you need to do the quat/interpolation stuff before taking transforms through the hierarchy.

Hmm I think you may be right. Im going to test to implement the skinning code in c++ instead of maxscript so I have the hierarchy available in the render loop.

 

Although recalculating the translation each frame could mean quite a few matrix multiplications and that might limit performance since I want to have many hundreds of characters on screen. Perhaps in that case it would be worth it to use more memory and have keyframes every frame or every other frame instead.

 

There might be some mathematical way to scale the position to take into account the arc of the quaternion interpolation, which I'm guessing is what is causing the "bouncing". That sounds awfully complicated but I'm open to ideas. :)

Edited by CarlML

Share this post


Link to post
Share on other sites
Posted (edited)

Yea, it can be quite a lot of computation! Fortunately it's also a problem that fits data oriented design and parallelism very well! Done right, your skinning data structures can be packed very tightly to reduce cache misses to a negligible amount. Depending on your physics needs, you can also probably calculate many of those characters in parallel.

 

For example, in our skinning code I cache the prev/next keys in a tightly packed array so that in the common case it's a very fast linear traversal over the bone list (which is sorted by dependency, so the parent is already calculated by the time a child needs it). In the uncommon case that we need to advance to a new keyframe it'll take a (slightly) slower path to fetch the new key pair and put that into the tighter cache.

Edited by ShaneYCG

Share this post


Link to post
Share on other sites
Posted (edited)

I finally got it to work by calculating the transformation from bone space to root space each frame after the quaternion interpolation. Thanks ShaneYCG for your suggestion, it put me on the right path!

 

Oh man, I've been working on this for several days now, the feeling when seeing it all come together was just amazing. Cheers. :)

Edited by CarlML

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this