Tangent Space Vector for my Terrain mesh

Ok, I have looked at Eric’s code and since my math skills are long but gone been 5 years since Calculus and not sure what I can edit out of his code to just calculate tangent space from my vertex xyz and texture coordinate st… Or can I get away with calling my tangent vector 1,0,0 due to my terrain mesh normals are 0,1,0? I would like to make a function to calculate the tangent space vectors but as of now can’t figure out what I need and don’t need with Eric’s code. Thanks for all the help.

Here is a sample code from 3dsmax sdk. It calculates the tangent vectors for a single triangle (using 3 vertices and 3 texcoords as input).

So you first compute the tangent vectors for each trangle in your mesh (and normalize as max code does not return them normalized). Then for each vertex, average together the tangent vectors for all triangles using that vertex.

void ComputeBumpVectors(
	const Point3 tv[3],	// texture coords 
	const Point3 v[3],	// vertex pos
	Point3 tvec[2])		// result tangent vectors
{
	float uva,uvb,uvc,uvd,uvk;
	Point3 v1,v2;

	uva = tv[1].x-tv[0].x;
	uvb = tv[2].x-tv[0].x;

	uvc = tv[1].y-tv[0].y;
	uvd = tv[2].y-tv[0].y;

	uvk = uvb*uvc - uva*uvd;

	v1 = v[1]-v[0];
	v2 = v[2]-v[0];

	if (uvk!=0) 
	{
		tvec[0] = (uvc*v2-uvd*v1)/uvk;
		tvec[1] = (uva*v2-uvb*v1)/uvk;
	}
	else 
	{
		if (uva!=0) 
			tvec[0] = v1/uva;
		else if (uvb!=0) 
			tvec[0] = v2/uvb;
		else 
			tvec[0] = Point3(0.0f,0.0f,0.0f);

		if (uvc!=0) 
			tvec[1] = v1/uvc;
		else if (uvd!=0) 
			tvec[1] = v2/uvd;
		else 
			tvec[1] = Point3(0.0f,0.0f,0.0f);
	}
}

Thanks for the reply FPO but I am still working on the calculations and was wondering what tangent values should I be seeing if I have a normal of 0,1,0 or (0, .9, 0)?? Thanks

You probably have noticed that it fully depends on your texture coordinates. If I am not mistaken, for normals like (0, 1, 0) you will get a tangent like t2.u - t1.u, there t1.u, t2.1 are first texture coordinates of different vertices of your triangle.

There are certain assumptions you can make about terrain height map grids, etc.
When doing my ‘projected grid’ water using texture sampling in the vertex shader, I had to calculate the tangent/binormal vectors in the vertex shader itself.
Here’s the relevant bit of GLSL vertex shader code, in case you’re interested:

const vec3 rightVec = vec3(1.0, 0.0, 0.0);
vec3 N = worldNormal;
vec3 T = cross(rightVec, N);
vec3 B = cross(T, N);

So N is the normal, T is the tangent, B is the binormal.

Here is my code so far, I am not getting the correct values as far as I can tell.

	unsigned int x = 0;
	unsigned int z = 0;
	float deltaT0 = 0.0f;
	float deltaT1 = 0.0f;
	float deltaS0 = 0.0f;
	float deltaS1 = 0.0f;
	Vector3D sTangent = Vector3D(0.0f, 0.0f, 0.0f);
	Vector3D tTangent = Vector3D(0.0f, 0.0f, 0.0f);
	Vector3D side0 = Vector3D(0.0f, 0.0f, 0.0f);
	Vector3D side1 = Vector3D(0.0f, 0.0f, 0.0f);
	Vector3D tangentCross = Vector3D(0.0f, 0.0f, 0.0f);	
	Vector3D normal = Vector3D(0.0f, 0.0f, 0.0f);

	for(z = 0; z < mapData.map_Z - 1; z++)
	{
		for(x = 0; x < mapData.map_X - 1; x++)
		{
			side0.x = terrain.mesh[(z + 1) * mapData.map_Z + x].vx - terrain.mesh[z * mapData.map_Z + x].vx;
			side0.y = terrain.mesh[(z + 1) * mapData.map_Z + x].vy - terrain.mesh[z * mapData.map_Z + x].vy;
			side0.z = terrain.mesh[(z + 1) * mapData.map_Z + x].vz - terrain.mesh[z * mapData.map_Z + x].vz;
			
			side1.x = terrain.mesh[z * mapData.map_Z + x].vx - terrain.mesh[z * mapData.map_Z + x + 1].vx;
			side1.y = terrain.mesh[z * mapData.map_Z + x].vy - terrain.mesh[z * mapData.map_Z + x + 1].vy;
			side1.z = terrain.mesh[z * mapData.map_Z + x].vz - terrain.mesh[z * mapData.map_Z + x + 1].vz;

			deltaT0 = (terrain.mesh[(z + 1) * mapData.map_Z + x].t * mapData.tileFactorTerrain) - (terrain.mesh[z * mapData.map_Z + x].t * mapData.tileFactorTerrain);
			deltaT1 = (terrain.mesh[z * mapData.map_Z + x].t * mapData.tileFactorTerrain) - (terrain.mesh[z * mapData.map_Z + x + 1].t * mapData.tileFactorTerrain);

			sTangent.x = deltaT1 * side0.x - deltaT0 * side1.x;
			sTangent.y = deltaT1 * side0.y - deltaT0 * side1.y;
			sTangent.z = deltaT1 * side0.z - deltaT0 * side1.z;

			sTangent = sTangent.Normalize();

			deltaS0 = (terrain.mesh[(z + 1) * mapData.map_Z + x].s * mapData.tileFactorTerrain) - (terrain.mesh[z * mapData.map_Z + x].s * mapData.tileFactorTerrain);
			deltaS1 = (terrain.mesh[z * mapData.map_Z + x].s * mapData.tileFactorTerrain) - (terrain.mesh[z * mapData.map_Z + x + 1].s * mapData.tileFactorTerrain);

			tTangent.x = deltaS1 * side0.x - deltaS0 * side1.x;
			tTangent.y = deltaS1 * side0.y - deltaS0 * side1.y;
			tTangent.z = deltaS1 * side0.z - deltaS0 * side1.z;

			tTangent = tTangent.Normalize();

			tangentCross = sTangent.CrossProduct(tTangent);

			normal.x = terrain.mesh[z * mapData.map_Z + x].nx;
			normal.y = terrain.mesh[z * mapData.map_Z + x].ny;
			normal.z = terrain.mesh[z * mapData.map_Z + x].nz;

			if(tangentCross.DotProduct(normal, tangentCross) < 0.0f)
			{
				sTangent.x *= -1.0f;
				sTangent.y *= -1.0f;
				sTangent.z *= -1.0f;
				tTangent.x *= -1.0f;
				tTangent.y *= -1.0f;
				tTangent.z *= -1.0f;
			}

            terrain.mesh[z * mapData.map_Z + x].tx = tangentCross.x;
			terrain.mesh[z * mapData.map_Z + x].ty = tangentCross.y;
			terrain.mesh[z * mapData.map_Z + x].tz = tangentCross.z;

			fout << "Normal x " << terrain.mesh[z * mapData.map_Z + x].nx << endl << 
			"Normal y " << terrain.mesh[z * mapData.map_Z + x].ny << endl <<
			"Normal z " << terrain.mesh[z * mapData.map_Z + x].nz << endl <<
			"Tangent x " << terrain.mesh[z * mapData.map_Z + x].tx << endl << 
			"Tangent y " << terrain.mesh[z * mapData.map_Z + x].ty << endl << 
			"Tangent z " << terrain.mesh[z * mapData.map_Z + x].tz << endl;
		}
	}

Ok, ok, I’ll rephrase my answer using your weird maths library functions and structures:-

normal.x = terrain.mesh[z * mapData.map_Z + x].nx;
normal.y = terrain.mesh[z * mapData.map_Z + x].ny;
normal.z = terrain.mesh[z * mapData.map_Z + x].nz;

Vector3D tangent = Vector3D(1.0, 0.0, 0.0).CrossProduct(normal);
Vector3D binormal = tangent.CrossProduct(normal);

If you don’t need the binormal, just delete the binormal line.

Originally posted by knackered:
[b]Ok, ok, I’ll rephrase my answer using your weird maths library functions and structures:-

normal.x = terrain.mesh[z * mapData.map_Z + x].nx;
normal.y = terrain.mesh[z * mapData.map_Z + x].ny;
normal.z = terrain.mesh[z * mapData.map_Z + x].nz;

Vector3D tangent = Vector3D(1.0, 0.0, 0.0).CrossProduct(normal);
Vector3D binormal = tangent.CrossProduct(normal);

If you don't need the binormal, just delete the binormal line.[/b]

And this way is going to work for terrain that is rough? Now what I am seeing for values is if my normal is 0,1,0 my tangent is 0,0,1 shouldn’t the tangent be 1,0,0 and the bitangent be 0,0,1 Thanks for the help

Tangent, bitangent, binormal, they’re all just words. There’s no rule that says the tangent should travel along the x axis or the z axis - in the end, they’re just vectors to put in the upper 3x3 of the matrix.
If it bothers you, just rename the ‘tangent’ variable to ‘bitangent’, and rename ‘binormal’ to ‘tangent’.
Oh, and of course it will work if the terrain is rough - why would I waste my/your time answering you if I was talking about a flat grid?!
If your normals are smooth (ie. vertex normals, not face normals), then your tangent vectors will be smooth too.
It works, trust me - I’ve used it lots of times.

I think you should always make the tangent vector aligned with the U texture space direction and the binormal aligned with the V texture direction. There are infinite number of valid tangent/binormal vectors at a vertex but only one will match the texture mapping axis.

A few texture based effects requires tangent vectors to be aligned to the texture space axis. Like parallax mapping and relief mapping for example.

Originally posted by fpo:
[b]I think you should always make the tangent vector aligned with the U texture space direction and the binormal aligned with the V texture direction. There are infinite number of valid tangent/binormal vectors at a vertex but only one will match the texture mapping axis.

A few texture based effects requires tangent vectors to be aligned to the texture space axis. Like parallax mapping and relief mapping for example.[/b]
I am using it for bumpmapping, parallax mapping and probably spherical harmonics eventually. But I am only planning on doing bumpmapping to the terrain and spherical harmonics. I know for my water I have one quad and the Normal is 0,1,0 and tangent is 1,0,0 and the binormal is 0,0,1. I am confused on what I am to be doing because Eric’s tangent space calculations are for arbitrary meshs. So when would I need to use his example then if I don’t need it for my terrain rendering? Sorry if I seem dense on this just it’s not clicking…

for arbitary meshes, of course.
That is, meshes where the topology of the surface is defined by arbitarily placed texture coordinates, placed by an artist, or a lod algorithm.
For a terrain, you know the topology of the surface.

I’m not sure if I’m using the word ‘topology’ in the correct context here, but you get the gist.

Originally posted by knackered:
[b]for arbitary meshes, of course.
That is, meshes where the topology of the surface is defined by arbitarily placed texture coordinates, placed by an artist, or a lod algorithm.
For a terrain, you know the topology of the surface.

I’m not sure if I’m using the word ‘topology’ in the correct context here, but you get the gist.[/b]
Ok, if I understand you correctly I should do this

normal.x = terrain.mesh[z * mapData.map_Z + x].nx;normal.y = terrain.mesh[z * mapData.map_Z + x].ny;normal.z = terrain.mesh[z * mapData.map_Z + x].nz;Vector3D tangent = Vector3D(1.0, 0.0, 0.0).CrossProduct(normal);

just take my normals per vertex and use the 1,0,0 for all my vertices in my terrain mesh and use the returned value from the cross product as my tangent vectors. :slight_smile: Thanks for all the help and patience with me.

Sorry, i was busy earlier.
I haven’t read the entire post so please forgive me if this has been mentioned in some earlier post. This method is applicable to regular meshes (e.g. terrains).
The ideal way of calculating terrain normals for any vertex matching a particular index within the height map is to take a cross product between two vectors which are calculated as follows:
Lets assume as h being the height map index of the vertex whose normal needs to be calculated. Let r be the height map value on the right of h (along +u tex-coord axis) and l be the value on the left of h (along -u tex-coord axis). Similarly let t be the height map value above (along +v) and b be the height map value below (along -v). Now since the mesh is regular therefore we have fixed offsets between the current vertex and the vertices on its four sides (left, top, right and bottom). Lets call the fixed distance between adjacent vertices along x-axis be x and along y-axis y. Then the non-normalized tangent will become (assuming x-axis points in the u direction and y-axis points in the v direction):

tangent = (x, 0, r - l)

Similarly the bitangent will become

bitangent = (0, y, t - b)

A cross between the normalized versions of these vectors will give you the normal. This way you can easily calculate normalized normals, tangents and bitangents for regular terrain meshes (using heightmaps).
Its quite easy to notice from the above equations that x and y are constants and the only variable factor is the z-axis. For a “well behaved” height map the difference between adjacent height values is never too large (assuming that there are no sudden spikes in the heightmap). You can actually calculate a look-up table (LUT) that pre-calculates normals, tangents and bitangents for different heightmap differences. This table doesn’t take much space and can be arranged such that a quick difference in height map values gives an index to the array which contains the normals, tangents and bitangents.

Mars, your method of assuming (1, 0, 0) to be the bitangent is wrong and will definitely fail most of the time. It is quite evident that a normal such as (x, y, z) where x, y, z != 0 will give a vector that is not orthagonal to your bitangent and will thus give the wrong tangent.
In the equations i mentioned above my tangent is actually your bitangent, which i will re-iterate over here
tangent = (x, 0, r - l)
Your technique seems to work sometimes because your x = 1 (regular mesh) the difference between r and l is negligible i.e. not too large which gives a vector
tangent = (1, 0, diff) where diff -> 0
However on steep slopes, the difference will not be negligible and your method will fail to give proper normals.

Originally posted by Zulfiqar Malik

Your technique seems to work sometimes because your x = 1 (regular mesh) the difference between r and l is negligible i.e. not too large which gives a vector
tangent = (1, 0, diff) where diff -> 0

Sorry i meant that x = n (where n is some constant) therefore
tangent = (n, 0, diff) {diff -> 0}
normalizing it gives
tangent = (1, 0, diff) {diff -> 0}

Thanks Zulfiqar Malik for the reply. So when you say I am assuming 1,0,0 for my parameter to send to my cross procduct function with my normal to get the tangent is wrong?

const vec3 rightVec = vec3(1.0, 0.0, 0.0);
vec3 N = worldNormal;
vec3 T = cross(rightVec, N);
vec3 B = cross(T, N);

knackered posted that and assuming I can send 1,0,0 to a cross product with my normal and get a tangent vector for that vertex then… THanks

Yes, that’s the wrong method as i explained earlier. But seems to work sometimes because of general terrain behavior. Consider this, your right vector is (1, 0, 0) and lets suppose your normal of a vertex that’s part of a vertical surface is (-1, 0, 0). Taking a cross product gives a (0, 0, 0) tangent, which is clearly wrong.
Use the method i mentioned and your life will be a lot easier and the LUT speeds things up a lot. Btw, how are you calculating normals in the first place? Encoded in the texture?
If you are calculating normals at run-time, then the tangent and bitangent cross (as mentioned earlier) is the fastest method to my knowledge.

Originally posted by Zulfiqar Malik:
Yes, that’s the wrong method as i explained earlier. But seems to work sometimes because of general terrain behavior. Consider this, your right vector is (1, 0, 0) and lets suppose your normal of a vertex that’s part of a vertical surface is (-1, 0, 0). Taking a cross product gives a (0, 0, 0) tangent, which is clearly wrong.
Use the method i mentioned and your life will be a lot easier and the LUT speeds things up a lot. Btw, how are you calculating normals in the first place? Encoded in the texture?
If you are calculating normals at run-time, then the tangent and bitangent cross (as mentioned earlier) is the fastest method to my knowledge.

I am calculating them like the “OpenGL Game Programming” book shows how to by the staff at gamedev… I am away from my computer right now so I can’t do much of anything till Sunday night. Thanks and I will try it out later this weekend.

Well, of course there’s the safe-guard of calculating the dot product between the normal and the 1,0,0, but I’d have come to that once he’d actually tried the simple bit.
However, the circumstance where the normal is aligned with the 1,0,0 vector is the circumstance where your texture will be stretched to the point where it would be unusable as a bumpmap anyway.