Jump to content
  • Advertisement
Sign in to follow this  

Inverse Transpose and transforming normals and skeletal anim

This topic is 3157 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 get correct normals in a skeletal animation system. Because the matrices i;m using contain only translation and rotation i can get away and treat normals as points, and do the same as for the vertices. I know that the matrices contain only translation and rotation because i verified this by extracting the scale: it's always a vector of one's. Here is the code i'm using to perform skeletal animation in software:
int i,j;
for (i=0; i<interpolatedVerts->size(); i++)
	(*interpolatedVerts) = Math::Vector3<float>();
	(*interpolatedNormals) = Math::Vector3<float>();

	for (j=0; j<(*vertexToJointMapping).joints.size(); j++)
		int jointIndex = (*vertexToJointMapping).joints[j];	

		(*interpolatedVerts) +=  skeleton->getJoint(jointIndex).getSampledMatrix() * 
		 (skeleton->getJoint(jointIndex).getInvBindMatrix() * (*vertices)) * (*vertexToJointMapping).influences[j];
		(*interpolatedNormals) += skeleton->getJoint(jointIndex).getSampledMatrix() * 
		 (skeleton->getJoint(jointIndex).getInvBindMatrix() * (*normals)) * (*vertexToJointMapping).influences[j];			

The animated skin looks ok., but the illumination is wrong(it flashes in a weird pattern). Is there something wrong with the code?

Share this post

Link to post
Share on other sites
Original post by Deliverance
... Because the matrices i;m using contain only translation and rotation i can get away and treat normals as points, and do the same as for the vertices. ...

That is a wrong conclusion. Normals are a sub-kind of direction vectors, and direction vectors cannot be handled like position vectors (a.k.a. points), because direction vectors don't know about the concept of locations. Hence they must not be translated but only rotated.

If you would use homogeneous co-ordinates, then the w co-ordinate would make the distinction between positions and directions, so that translation would be suppressed for direction vectors. But you use affine vectors, so you have to suppress translation manually.

Share this post

Link to post
Share on other sites
Why not just calculate a separate matrix for normal transformation? Say the model transformation matrix is M, and the one used for normal transformation is Mn. You compute the following:

Mn = (M-1)MT

Then find the transformed normal N':

N' = MnN

You must convert N' to a unit vector.

The tricky (and potentially expensive bit) is to find the inverse matrix, M-1. The Laplace Expansion Theorem described by Eberly does the job, source is also available on geometrictools.com.

Share this post

Link to post
Share on other sites
Thanks guys! Treating normals as points was bad due to the translation in the matrices. I rectified that! Although it still did not work. I had to use the inverse transpose of the matrix affecting the vertices, to be more precise this:

((getSampledMatrix() * getInvBindMatrix())-1)T * normal

Computing this is VERY slow. Here's the code i'm using for taking the inverse of some matrix:

Matrix4x4 Matrix4x4::inverse()
float fA0 = matrix[ 0]*matrix[ 5] - matrix[ 1]*matrix[ 4];
float fA1 = matrix[ 0]*matrix[ 6] - matrix[ 2]*matrix[ 4];
float fA2 = matrix[ 0]*matrix[ 7] - matrix[ 3]*matrix[ 4];
float fA3 = matrix[ 1]*matrix[ 6] - matrix[ 2]*matrix[ 5];
float fA4 = matrix[ 1]*matrix[ 7] - matrix[ 3]*matrix[ 5];
float fA5 = matrix[ 2]*matrix[ 7] - matrix[ 3]*matrix[ 6];
float fB0 = matrix[ 8]*matrix[13] - matrix[ 9]*matrix[12];
float fB1 = matrix[ 8]*matrix[14] - matrix[10]*matrix[12];
float fB2 = matrix[ 8]*matrix[15] - matrix[11]*matrix[12];
float fB3 = matrix[ 9]*matrix[14] - matrix[10]*matrix[13];
float fB4 = matrix[ 9]*matrix[15] - matrix[11]*matrix[13];
float fB5 = matrix[10]*matrix[15] - matrix[11]*matrix[14];

float fDet = fA0*fB5-fA1*fB4+fA2*fB3+fA3*fB2-fA4*fB1+fA5*fB0;
if (fabs(fDet) <= EPSILON)
Matrix4x4 matrix;
memset(&matrix._m11, 0, sizeof(float)*16);

return matrix;

Matrix4x4 kInv;
kInv.matrix[ 0] =
+ matrix[ 5]*fB5 - matrix[ 6]*fB4 + matrix[ 7]*fB3;
kInv.matrix[ 4] =
- matrix[ 4]*fB5 + matrix[ 6]*fB2 - matrix[ 7]*fB1;
kInv.matrix[ 8] =
+ matrix[ 4]*fB4 - matrix[ 5]*fB2 + matrix[ 7]*fB0;
kInv.matrix[12] =
- matrix[ 4]*fB3 + matrix[ 5]*fB1 - matrix[ 6]*fB0;
kInv.matrix[ 1] =
- matrix[ 1]*fB5 + matrix[ 2]*fB4 - matrix[ 3]*fB3;
kInv.matrix[ 5] =
+ matrix[ 0]*fB5 - matrix[ 2]*fB2 + matrix[ 3]*fB1;
kInv.matrix[ 9] =
- matrix[ 0]*fB4 + matrix[ 1]*fB2 - matrix[ 3]*fB0;
kInv.matrix[13] =
+ matrix[ 0]*fB3 - matrix[ 1]*fB1 + matrix[ 2]*fB0;
kInv.matrix[ 2] =
+ matrix[13]*fA5 - matrix[14]*fA4 + matrix[15]*fA3;
kInv.matrix[ 6] =
- matrix[12]*fA5 + matrix[14]*fA2 - matrix[15]*fA1;
kInv.matrix[10] =
+ matrix[12]*fA4 - matrix[13]*fA2 + matrix[15]*fA0;
kInv.matrix[14] =
- matrix[12]*fA3 + matrix[13]*fA1 - matrix[14]*fA0;
kInv.matrix[ 3] =
- matrix[ 9]*fA5 + matrix[10]*fA4 - matrix[11]*fA3;
kInv.matrix[ 7] =
+ matrix[ 8]*fA5 - matrix[10]*fA2 + matrix[11]*fA1;
kInv.matrix[11] =
- matrix[ 8]*fA4 + matrix[ 9]*fA2 - matrix[11]*fA0;
kInv.matrix[15] =
+ matrix[ 8]*fA3 - matrix[ 9]*fA1 + matrix[10]*fA0;

float fInvDet = ((float)1.0)/fDet;
kInv.matrix[ 0] *= fInvDet;
kInv.matrix[ 1] *= fInvDet;
kInv.matrix[ 2] *= fInvDet;
kInv.matrix[ 3] *= fInvDet;
kInv.matrix[ 4] *= fInvDet;
kInv.matrix[ 5] *= fInvDet;
kInv.matrix[ 6] *= fInvDet;
kInv.matrix[ 7] *= fInvDet;
kInv.matrix[ 8] *= fInvDet;
kInv.matrix[ 9] *= fInvDet;
kInv.matrix[10] *= fInvDet;
kInv.matrix[11] *= fInvDet;
kInv.matrix[12] *= fInvDet;
kInv.matrix[13] *= fInvDet;
kInv.matrix[14] *= fInvDet;
kInv.matrix[15] *= fInvDet;

return kInv;

(code taken from the internet - can't remember where it was :D)

Any ideas of improvement here? How do normals for skeletal animations get tranformed in todays engines?

Would The Laplace Expansion Theorem help hasting things up?

Share this post

Link to post
Share on other sites
The term i believe you should be using is "inverse transform" not transpose. Transpose means to mirror the matrix along it's diagonal, to "flip it." This is not what you're doing as you can see by your large inverse function which does not just "flip it."

The reason this term is commonly mis understood is because in a 3x3 orthogonal transform, the inverse is often just the transpose. You can't just transpose the translation though.

So for a quick and dirty inverse, you can transpose the inner 3x3 orientation component, and then pre multiply it by the negative translation component.

//psuedo code
Matrix4x4::InverseCheap( )
Matrix3x3 r = GetOrientation( );
r.Transpose( );

return Matrix4x4(r) * Matrix4x4( -GetTranslation( ) );

You see combining the result with another matrix will first translate inversely, and then rotate inversely.

Not sure how this method works with scaling, but i'm sure there's a quick and dirty way. You'd probably just break out the scale into and handle it as a separate component like the translation. Also you'd obviously want to optimize this function and not do all the type conversion i used for demonstration.

Share this post

Link to post
Share on other sites
I nailed it down! I had two matrices there: sampledMatrix and inverseBindMatrix, sampledMatrix was the bone space to world space transform and also it changed every frame. This matrix was an orthogonal one and that means it's inverse is just it's transpose(very fast computation). The second matrix was the inverseBindMatrix, i'ts inverse can be precalculated because it never changes. I needed the following matrix for normal computation: ((sampledMatrix * inverseBindMatrix)-1)T = (inverseBindMatrix-1*sampleMatrix-1)T = (precalculatedBindMatrix * sampleMatrixT)T = sampleMatrix * precalculatedBindMatrixT. This computation is very fast.

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!