SOLVED Calculating Tangent from Normal

Hi, everyone

First, is this the appropriate place to post this, as I wasn’t too sure.

I have googled this, however a lot of the answers were either too complicated due to the Math terminologies used and/or lack of easily understood example, or I wasn’t sure how I would implement the answer myself.

So I’m asking here. So I can hopefully understand and implement what I need.

From what I understand, the tangent is perpendicular from the normal.

So here’s a square, where two corners are raised slightly.

v -1.000000 0.516746 1.000000
v 1.000000 0.000000 1.000000
v -1.000000 0.000000 -1.000000
v 1.000000 0.504169 -1.000000

vt 0.943613 0.001434
vt 0.999903 0.941847
vt 0.057807 0.941847
vt 0.000097 0.000097

vn 0.003003 0.999991 -0.003003
vn -0.237447 0.941933 0.237447
vn 0.242680 0.939262 -0.242680

f 2/1/1 4/2/2 3/3/1
f 1/4/3 2/1/1 3/3/1

So with the first triangle


// Position
v 1.000000 0.000000 1.000000
v 1.000000 0.504169 -1.000000
v -1.000000 0.000000 -1.000000
// UV
vt 0.943613 0.001434
vt 0.999903 0.941847
vt 0.057807 0.941847
// Normal
vn 0.003003 0.999991 -0.003003
vn -0.237447 0.941933 0.237447
vn 0.242680 0.939262 -0.242680

How would I calculate the tangent?

Also, when implementing normal mapping, do I need, one tangent per triangle, or one tangent per vertex.

Thanks in advance for any help.

lets call the tangent vector Vt and the normal vector Vn

Vt dot Vn = Magnitude(Vt) * Magnitude(Vn) * cos(angle)

if they are tangent the angle is 90 degrees. Since angle is 90 degrees cos(90) = 0. This turns the right hand side to 0.

so

Vt dot Vn = 0.

The dot product means multiply similar components and add them. Ex(X1, Y1) dot (X2, Y2) = X1 * X2 + Y1 * Y2

so

VtX * VnX + VtY * VnY + VtZ * VtZ = 0

this becomes the equation of a plane. Any vector on this plane is tangent to point.

-simpler way using the face-

If you just want a vector that is “tangent” to the triangle, just take any two points on the triangle and subtract one from the other.

EDIT:
if you have a high poly model (a model with lots of polygons) use the 2nd way otherwise you’ll probably want to use the first way.

I’m having difficulty understanding, it’s just not clicking in. Stupid brain

So at the beginning, is Vt known? Where as Vn is. (In my case anyways)

If not, will following what you wrote get the value of Vt?
If it is, how do I get the value?

And for the simpler way, would that just be as simple as Vt1 - Vt2 or some other combination?
Or would it be all the data in the vertices taken away from each other? (Position, normal etc)

I know it may seem lazy on my end, but it would help me understand, could you perhaps solve it for the first triangle, so I get an idea of
it in practice.

Imagine your normal pointing up, along the Z axis:

There are infinite tangent vectors for this normal, all on the XY plane.

In conclusion: solely a normal (or a triangle) is not enough to determine a single tangent vector.

Ah, ok, so what information do I need in order to calculate a single tangent vector?

A point along that vector.

What are you trying to create, draw a picture in paint if you have to.

I’m trying to get a tangent vector so I can implement normal mapping, I know there’s object or world space based methods, but I would rather use tangent.

Unfortunately, the Wavefront file format doesn’t have a tangent, so I need to create one with what ever information I can.

I figured I would need to use the normal vectors in order to try and create a tangent vector, but I unsure on how I go about doing this.

Do you understand what normal and tangents are??

I know what they are, but I don’t fully understand them, other-wise I wouldn’t be asking for help.

I drew you this.

Both a tangent and normal require a given point along a circle or line. A tangent means that it is parallel to the circle and only touches/crosses the circle once. A normal is as you say a perpendicular line. So for example you can have a tangent to a circle/curve and then also figure out the normal to that tangent when it touches the circle.

Could you post pictures or code of what your trying to achieve, sorry if I’m miss understanding something.

Also look here, http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/#Computing_the_tangents_and_bitangents

I am guessing this is what you are trying to do.

You use your texture coordinates.

Bearded Cow, yes, I think that link you posted is what I need. I’ll get back to say if it works out or not.

All I was after was a method to calculate the tangent when I have limited information.

As for a picture to help, I don’t really know what I could show, an algorithm, what kind of picture would you use to show that you’re trying to find the method of calculating a tangent from, as I said before, limited information.
I have position, normal, and uv vectors, what do I do with them?
How can I manipulate them to come out with a tangent use values are easier to use, since I learned from Riven, that there are an infinite number of tangents.

Of course there are probably numerous ways I could explain myself better, but every now and then, my brain fails me :slight_smile:

But, with that said, I think that link is sufficient.

Well, I managed to get the tangents, I’m pretty sure they’re right, created a geometry shader so I could visualize them. And they all appear to be perpendicular to the normal.

But now there’s the issue of making use of the values, when I try and implement the same method as shown in Opengl Super Bible 6th Edition, everything is black. Though after messing around, I think I accidentally made it so it uses the texture co-ordinates like Roquen suggested.

Should I stick with what I have, since it appears to work, or make it use the calculated tangents.

You can compute tangents without extra information, but since as Riven pointed out, there are an infinite number of them to choose from you must use some “extra” information to make the coherent with each other. Since you’re almost insured to have texture coordinates available that’s one possible choice.

As roquen pointed out, you have to use the gradient of the texture coordinates to identify the direction of the tangent. You need to do this if you want your normal maps to work.

I dug out some code I used to do this task:




//the three position vectors creating a triangle
        Vector3 pos1,  pos2, pos3;
//the three texture coordinate vectors of the same three vertices
        Vector2 uv1,  uv2, uv3;
//the normal of the vector we want to calculate the tangent for (pos1, uv1 int this case)
        Vector3 n;

        Vector3 v2v1 = pos2.sub(pos1);
        Vector3 v3v1 = pos3.sub(pos1);

        float c2c1b = uv2.getY() - uv1.getY();
        float c3c1b = uv3.getY() - uv1.getY();

        Vector3 t = v2v1.mult(c3c1b).sub(v3v1.mult(c2c1b));

        Vector3 b = n.cross(t);

//the final tangent
        Vector3 smoothTangent = b.cross(n).normalize();      

Ok, so I think I’ve got the tangent calculation down. (Or not)

But upon rendering, well it doesn’t look right.
It could be down to the tangents, or the shaders, I don’t know.
On top of that, any books you guys could recommend for understanding glsl and lighting techniques and the math behind it?

Here’s some images:
(Note the actual vector values for the light position, doesn’t change)

(With normal texture coords scaled several times and small changes)

As you can see, not quite right, also, how can I smooth out the lighting, I assume it’s because I have a tangent per vertex, and not per polygon.

The shaders:
Vertex

#version 430 core

in vec4 in_Position;
in vec2 in_TexCoord;
in vec3 in_Normal;
in vec3 in_Tangent;

out VS_OUT {
	vec2 pass_TexCoord;
	vec3 eyeDir;
	vec3 lightDir;
} vs_out;

uniform mat4 proj_matrix;
uniform mat4 view_matrix;
uniform mat4 model_matrix;
uniform vec3 lightPos = vec3(5.0, 0.0, 10.0);

void main(void) {	

	//  EDITED // "mv_matrix" is used in the book, I assume this is how it's calculated
       // Didn't know that the order of matrix multiplication would change things, reversing the order, made
       // the specular stop moving, one step closer to it looking better.
	mat4 mv_matrix = view_matrix * model_matrix;
	// Calculate vertex position in view space
	vec4 P = mv_matrix * in_Position;
	
	// Calculate normal (N) and tangent (T) vectors in view space from incoming
	// object space vectors
	vec3 N = normalize(mat3(mv_matrix) * in_Normal);
	vec3 T = normalize(mat3(mv_matrix) * in_Tangent);
	// Calculate the bitangent vector (B) from the normal and tangent vectors
	vec3 B = cross(N, T);
	
	// I think it was a little mistake, in the book, this part was calculate just above the
	// "eyeDir" calculation
	vec3 V = -P.xyz;
	
	// The light vector (L) is the vector from the point of interest to the light.
	// Calculate that and multiply it by the TBM matrix
	vec3 L = lightPos - P.xyz;
	vs_out.lightDir = normalize(vec3(dot(V, T), dot(V, B), dot(V, N)));
	
	// The view vector is the vector from the point of interest to the viewer,
	// which in view space is simply the negative of the position.
	// Calculate that and multiply it by the TBN matrix
	vs_out.eyeDir = normalize(vec3(dot(V, T), dot(V, B), dot(V, N)));
	
	// Pass the texture coordinate through unmodified so that the fragment shader
	// can fetch from the normal and color maps
	vs_out.pass_TexCoord = in_TexCoord;
	
	// Calculate clip coordinates by multiplying our view position by
	// the projection matrix
	//gl_Position = proj_matrix * P;
	
	// The above "gl_Position" just brings back the refresh color without any geometry
	gl_Position = proj_matrix * view_matrix * model_matrix * in_Position;
}

Fragment

#version 430 core
// Textures
uniform sampler2D albedo_Tex;
uniform sampler2D normal_Tex;
uniform sampler2D reflect_Tex;
uniform sampler2D micro_Tex;
// Environment Map
uniform sampler2D envMap_Tex;

in VS_OUT {
	vec2 pass_TexCoord;
	vec3 eyeDir;
	vec3 lightDir;
} fs_in;

out vec4 out_Albedo;
out vec4 out_Normal;
out vec4 out_Reflect;
out vec4 out_Micro;

void main(void) {	
	// Normalize our incoming view and light direction vectors
	vec3 V = normalize(fs_in.eyeDir);
	vec3 L = normalize(fs_in.lightDir);
	// EDITED // Read the normal from the normal map and normalize it
        // Scaling the texture coords made the base normal map much easier to see, as it is a 
        // 2048x2048 normal map with 4 squares, and a crease in between each square.
        // It now looks perfect on the cube, but not the sphere due to the noticeable triangles.
        // How do I go about smoothing it out? Would I have to "smooth" it before the 
        // data is sent to opengl? Or can I do that with the shaders?
	vec3 N = normalize(texture2D(normal_Tex, fs_in.pass_TexCoord * 4.0).rgb * 2.0 - vec3(1.0));
	// Calculate R ready for use in Phong lighting
	vec3 R = reflect(-L, N);
	
	// Fetch the diffuse albedo from the texture
	vec3 diffuse_albedo = texture2D(albedo_Tex, fs_in.pass_TexCoord).rgb;
	// Calculate diffuse color with the simple N dot L
	vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo;
	// Uncomment this to turn off diffuse shading
	// diffuse = vec3(0.0);
	
	// Assume that specular albedo is white - it could also come from a texture
	vec3 specular_albedo = vec3(1.0);
	// Calculate Phong specular highlight
	vec3 specular = max(pow(dot(R, V), 5.0), 0.0) * specular_albedo;
	// Uncomment this to turn off specular highlights
	// specular = vec3(0.0;
	
	// Finale color is diffuse + specular
	out_Albedo = vec4(diffuse + specular, 1.0);
}

This is pretty much what’s in the book itself
Also, when implementing basic Phong shading using the books code, it looks the way it’s supposed to, the sphere and dragon are smooth where as the Suzanne and the cube are not.

I’ve made some progress fixing the above issue.
Turned out there was just a minor type in my interpretation of Dannys snippet.

As I had this:

float c2c1b = uv2.getY() - uv1.getY();
        float c3c1b = uv3.getY() - uv1.getY();

Written as:

float c2c1b = uv2.getY() - uv1.getX();
        float c3c1b = uv3.getY() - uv1.getY();

So I fixed that and it looks much better but not perfect, where as the cube does.
Which I can kinda understand why. Only need to get it better on the rest.

So is there anything I’m missing, that could cause the sharpness seen?

Also, here’s interpretation, just in case the fault is there.

for(int i = 0; i < getVerts().length; i += 3) {
			Vector3f v0 = getVerts()[i].getVectorXYZ();
			Vector3f v1 = getVerts()[i + 1].getVectorXYZ();
			Vector3f v2 = getVerts()[i + 2].getVectorXYZ();
			
			Vector2f uv0 = getVerts()[i].getVectorUV();
			Vector2f uv1 = getVerts()[i + 1].getVectorUV();
			Vector2f uv2 = getVerts()[i + 2].getVectorUV();
			
			Vector3f n = getVerts()[i].getVectorNXYZ();
			
			Vector3f deltaPos1 = new Vector3f();
			Vector3f.sub(v1, v0, deltaPos1);
			Vector3f deltaPos2 = new Vector3f();
			Vector3f.sub(v2, v0, deltaPos2);
			
			float deltaUv1 = uv1.getY() - uv0.getY();
			float deltaUv2 = uv2.getY() - uv0.getY();
			
			// Tangent
			float tx = (deltaPos1.getX() * deltaUv2) - (deltaPos2.getX() * deltaUv1);
			float ty = (deltaPos1.getY() * deltaUv2) - (deltaPos2.getY() * deltaUv1);
			float tz = (deltaPos1.getZ() * deltaUv2) - (deltaPos2.getZ() * deltaUv1);
			Vector3f t = new Vector3f(tx, ty, tz);
			
			// Bitangent
			Vector3f b = new Vector3f();
			Vector3f.cross(n, t, b);
			
			// Final tangent
			Vector3f smoothTangent = new Vector3f();
			Vector3f.cross(b, n, smoothTangent);
			smoothTangent.normalise();
			
                       // These are where I store all the vertex data, so it's one tangent per vertex.
			getVerts()[i].setTXYZ(smoothTangent);
			getVerts()[i + 1].setTXYZ(smoothTangent);
			getVerts()[i + 2].setTXYZ(smoothTangent);
		}

Just to be the monkey tossing wrenches around:
http://www.thetenthplanet.de/archives/1180#more-1180

you only calculate one tangent per triangle, but you have to calculate it for each vertex of every triangle.

lets say that my code is a function which takes three vertices or indices into your vertex array. you would have to call it three times for each trinalge.


Vector3f calcTangent(int v1, int v2, int v3);
for(int i = 0; i < getVerts().length; i += 3) {
getVerts()[i].setTXYZ(calcTangent(i, i + 1, i +2);
getVerts()[i+1].setTXYZ(calcTangent(i+1, i + 2, i);
getVerts()[i+2].setTXYZ(calcTangent(i+2, i, i +1);
}


lol I’m confused.

So let me get this straight.

From that link. http://www.thetenthplanet.de/archives/1180#more-1180
I don’t need to pre-calculate on launch, I can do it in the shaders by “perturbing the normal” using the “co-tangent frame”?
So essentially is that getting the tangent from using uvs? Like what you stated before.

And from what I can tell by the shaders, I just need to feed the shader a normal, view vector and texture co-ordinates in order to get the tangent, right?