Tangent space and animation

Hi,

I have a bumpmapped model which uses tangent space to render the bumps. Now if I want to animate my model, do I have to recalculate tangent space for each frame? I guess the must be a faster way. Anyone know?

//Regards, Paw

Your tangent space basis would turn the normal into pre-animated space. You then have to skin the normal you get out of the normal map, just like you skin the position.

Because this is done per fragment, you could skin the tangent bases and interpolate and normalize each basis vector. This makes it no more expensive in the fragment shader, and only a little bit more expensive in the vertex shader.

Ok, so I have to do this for each vector in the tangent basis?

v.x += (b.rot[0] * w.x + b.rot[3] * w.y + b.rot[6] * w.z + b.pos[0]) * w.t;
v.z += (b.rot[1] * w.x + b.rot[4] * w.y + b.rot[7] * w.z + b.pos[1]) * w.t;
v.y += (b.rot[2] * w.x + b.rot[5] * w.y + b.rot[8] * w.z + b.pos[2]) * w.t;

-paw

Do not add the translation part of the matrices. Only use the rotational part.

Oh, and this won’t work right if you use shear or nonlinear scale in your matrix; if so, then you have to use the transpose of the inverse of the matrix instead.

Ok, you mean like this:

void CMD5::BuildVertices(CVector3 *tangents, CVector3 *normals)
{
for (int i = 0; i < m_nMeshes; i++)
{
Mesh& mesh = m_pMeshes[i];

  for (int j = 0; j &lt; mesh.nb_vertices; j++)
  {
     Vertex& v = mesh.vertices[j];

     v.x = 0;
     v.y = 0;
     v.z = 0;

	 CVector3 &tan  = tangents[j];
	 CVector3 &norm = normals[j];

     for (int k = 0; k &lt; v.weight_count; k++)
     {
        Weight& w = mesh.weights[v.weight_index + k];

        Bone& b = m_Bones[w.bone];

        v.x += (b.rot[0] * w.x + b.rot[3] * w.y + b.rot[6] * w.z + b.pos[0]) * w.t;
        v.z += (b.rot[1] * w.x + b.rot[4] * w.y + b.rot[7] * w.z + b.pos[1]) * w.t;
        v.y += (b.rot[2] * w.x + b.rot[5] * w.y + b.rot[8] * w.z + b.pos[2]) * w.t;

		tan.m_vertex[0] += (b.rot[0] * tan.m_vertex[0] + b.rot[3] * tan.m_vertex[1] + b.rot[6] * tan.m_vertex[2])  * w.t;
        tan.m_vertex[1] += (b.rot[1] * tan.m_vertex[0] + b.rot[4] * tan.m_vertex[1] + b.rot[7] * tan.m_vertex[2]) * w.t;
        tan.m_vertex[2] += (b.rot[2] * tan.m_vertex[0] + b.rot[5] * tan.m_vertex[1] + b.rot[8] * tan.m_vertex[2])  * w.t;

		norm.m_vertex[0] += (b.rot[0] * norm.m_vertex[0] + b.rot[3] * norm.m_vertex[1] + b.rot[6] * norm.m_vertex[2]) * w.t;
        norm.m_vertex[1] += (b.rot[1] * norm.m_vertex[0] + b.rot[4] * norm.m_vertex[1] + b.rot[7] * norm.m_vertex[2]) * w.t;
        norm.m_vertex[2] += (b.rot[2] * norm.m_vertex[0] + b.rot[5] * norm.m_vertex[1] + b.rot[8] * norm.m_vertex[2]) * w.t;


		
     }
	 tan.Normalize();
	 norm.Normalize();
  }

}
}

Something or wrong here.

-Paw

Sorry, I mean like this:

void CMD5::BuildVertices(CVector3 *tangents, CVector3 *normals)
{
for (int i = 0; i < m_nMeshes; i++)
{
Mesh& mesh = m_pMeshes[i];

  for (int j = 0; j &lt; mesh.nb_vertices; j++)
  {
     Vertex& v = mesh.vertices[j];

     v.x = 0;
     v.y = 0;
     v.z = 0;

	 CVector3 &tang  = tangents[j];
	 CVector3 &norm = normals[j];

	 CVector3 newNormal  = CVector3(0, 0, 0);
	 CVector3 newTangent = CVector3(0, 0, 0);

     for (int k = 0; k &lt; v.weight_count; k++)
     {
        Weight& w = mesh.weights[v.weight_index + k];

        Bone& b = m_Bones[w.bone];

        v.x += (b.rot[0] * w.x + b.rot[3] * w.y + b.rot[6] * w.z + b.pos[0]) * w.t;
        v.z += (b.rot[1] * w.x + b.rot[4] * w.y + b.rot[7] * w.z + b.pos[1]) * w.t;
        v.y += (b.rot[2] * w.x + b.rot[5] * w.y + b.rot[8] * w.z + b.pos[2]) * w.t;

		newTangent.m_vertex[0] += (b.rot[0] * tang.m_vertex[0] + b.rot[3] * tang.m_vertex[1] + b.rot[6] * tang.m_vertex[2])  * w.t;
        newTangent.m_vertex[1] += (b.rot[1] * tang.m_vertex[0] + b.rot[4] * tang.m_vertex[1] + b.rot[7] * tang.m_vertex[2])  * w.t;
        newTangent.m_vertex[2] += (b.rot[2] * tang.m_vertex[0] + b.rot[5] * tang.m_vertex[1] + b.rot[8] * tang.m_vertex[2])  * w.t;

		newNormal.m_vertex[0] += (b.rot[0] * norm.m_vertex[0] + b.rot[3] * norm.m_vertex[1] + b.rot[6] * norm.m_vertex[2]) * w.t;
        newNormal.m_vertex[1] += (b.rot[1] * norm.m_vertex[0] + b.rot[4] * norm.m_vertex[1] + b.rot[7] * norm.m_vertex[2]) * w.t;
        newNormal.m_vertex[2] += (b.rot[2] * norm.m_vertex[0] + b.rot[5] * norm.m_vertex[1] + b.rot[8] * norm.m_vertex[2]) * w.t;


		
     }
	 newNormal.Normalize();
	 newTangent.Normalize();

	 norm = newNormal;
	 tang = newTangent;
  }

}
}

Do you see whats wrong here?

-paw

your variable v is set to a reference to the mesh position, and then cleared. Hopefully, the mesh position you clear is not also one you also use for input.

Also, I’m assuming “tangents” and “normals” are also part of the “mesh” struct?

I don’t understand why the “weight” has z, y and z parts that you use as input for position. Is that special to the file format you’re working on?

Originally posted by jwatte:
I don’t understand why the “weight” has z, y and z parts that you use as input for position. Is that special to the file format you’re working on?

This is the doom 3 md5 format.

-SirKnight

Here is a page I just found that may be usefull: http://www.gamasutra.com/features/20030325/fernando_05.shtml

Notice in the vertex program close to the bottom of the page where they do this:

netPosition += weight[i] * bonePosition;
netNormal += weight[i] * boneNormal;

This is the same technique you would use to also skin the tangent and binormal. So it would perhaps look something like this:

netPosition += weight[i] * bonePosition;
netNormal += weight[i] * boneNormal;
netTangent += weight[i] * boneTangent;
netBinormal += weight[i] * boneBinormal;

Of course you would have to compute boneTangent and boneBinormal just like boneNormal was done.

-SirKnight

netPosition += weight[i] * bonePosition;netNormal += weight[i] * boneNormal;netTangent += weight[i] * boneTangent;netBinormal += weight[i] * boneBinormal;

This is done in the code above. The w.t are the weight[i].

-paw