# pouët.net

## Tangent and Bitangent from gl_NormalMatrix?

category: general [glöplog]

Hi people.

I have this problem here. I'm writing shaders that need to use tangtents and bitangents/binormals (e.g. for normalmapping), but can not pass these attributes from the application (don't ask why). Now I was thinking I could be clever and get/or calculate those values from the glNormalMatrix.

I looked around the internet and I tried (GLSL vertex shader):
Code:``` vec3 normal = normalize(gl_NormalMatrix * gl_Normal); vec3 tangent; vec3 binormal; vec3 c1 = cross(gl_Normal, vec3(0.0, 0.0, 1.0)); vec3 c2 = cross(gl_Normal, vec3(0.0, 1.0, 0.0)); if(length(c1) > length(c2)) { tangent = c1; } else { tangent = c2; } tangent = normalize(tangent); binormal = cross(gl_Normal, tangent); binormal = normalize(binormal); ```

and:
Code:``` vec3 normal = normalize(gl_NormalMatrix * gl_Normal); vec3 tangent; vec3 binormal; vec3 c1 = normalize(gl_NormalMatrix[0]); vec3 c2 = normalize(gl_NormalMatrix[1]); ```

If I have a cube always get only ONE side that is correctly lit...
added on the 2009-03-19 12:23:00 by raer
uhm...
Code:``` vec3 normal = normalize(gl_NormalMatrix * gl_Normal); vec3 tangent = normalize(gl_NormalMatrix[0]); vec3 binormal = normalize(gl_NormalMatrix[1]); ```

as you might have guessed already.
added on the 2009-03-19 12:24:35 by raer
got it now:
Code:``` vec3 normal = normalize(gl_NormalMatrix * gl_Normal); mat3 trans = transpose(gl_NormalMatrix); vec3 tangent = gl_NormalMatrix * trans[0]; vec3 binormal = gl_NormalMatrix * trans[1]; ```
added on the 2009-03-19 12:50:26 by raer
well, to calculate binormals and tangents you need not only to have the vertices, but also the UVs which you would like to use.
added on the 2009-03-19 13:38:26 by bartman
IMHO it's better to precalculate them on processor side to save your precious gfx card cycles for other use. So you should rather solve the problem of passing them to your shaders.
added on the 2009-03-19 13:44:05 by pommak
bartman:
I have texture coordinates. Or what do you mean?

pommak:
Short explanation: can't.
Long explanation: I use Coin3D with custom shape types and would need to change a lot of code in our application for it to work.
added on the 2009-03-19 14:20:51 by raer
added on the 2009-03-19 15:25:49 by bartman
Yes, but it doesn't help much, as in the shader I can only acess one vertex...

Btw. Now I have the problem that I don't know which texture units Coin3D uses... sucks.
added on the 2009-03-19 15:46:14 by raer
Coin3D uses Dollars for X and Euros for Y coordinates
added on the 2009-03-19 21:24:53 by Joghurt
I have now found a way calculating the TBN-matrix in the pixelshader. See here or (faster version) in ShaderX5.
added on the 2009-09-17 13:54:30 by raer
And to share my wisdom with you here's the normalmapping shader (I will add parallax mapping later). It uses an additional glossmap, but I think you can figure that one out...

vertex:
Code:``` varying vec3 lightDir; varying vec3 viewDir; varying vec3 normal; void main(void) { gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; normal = normalize(gl_NormalMatrix * gl_Normal); //note: this is a directional light! lightDir = normalize(gl_LightSource[0].position.xyz); vec3 eyeVec = vec3(gl_ModelViewMatrix * gl_Vertex); viewDir = normalize(-eyeVec); gl_Position = ftransform(); } ```

fragment:
Code:``` uniform sampler2D colorMap; uniform sampler2D normalMap; uniform sampler2D glossMap; uniform float glossMax; uniform float glossNoise; uniform vec4 glossColor; varying vec3 lightDir; varying vec3 viewDir; varying vec3 normal; //calculates tangent space matrix from normal, vector in plane and texture coordinates //needs normalized normal and position! mat3 computeTangentFrame(vec3 normal, vec3 position, vec2 texCoord) { vec3 dpx = dFdx(position); vec3 dpy = dFdy(position); vec2 dtx = dFdx(texCoord); vec2 dty = dFdy(texCoord); vec3 tangent = normalize(dpx * dty.t - dpy * dtx.t); vec3 binormal = normalize(-dpx * dty.s + dpy * dtx.s); return mat3(tangent, binormal, normal); } float rand(vec2 coordinate) { return fract(sin(dot(coordinate.xy, vec2(12.9898,78.233))) * 43758.5453); } void main (void) { vec2 texCoord = gl_TexCoord[0].st; vec3 v = normalize(viewDir); vec3 l = normalize(lightDir); vec3 n = normalize(normal); vec3 bump = normalize(texture2D(normalMap, texCoord).xyz * 2.0 - 1.0); mat3 TBN = computeTangentFrame(n, v, texCoord); n = normalize(TBN * bump); float nDotL = clamp(dot(l, n), 0.0, 1.0); vec4 ambient = gl_LightSource[0].ambient; vec4 diffuse = gl_LightSource[0].diffuse * gl_FrontMaterial.diffuse * nDotL; vec4 specular; vec4 glossAdditional; if (nDotL >= 0.0) { vec4 glossFromMap = texture2D(glossMap, texCoord); float glossMaskValue = (glossFromMap.x + glossFromMap.y + glossFromMap.z) * 0.333333; if (glossMaskValue > 0.0) { float nDotV = clamp(dot(reflect(-l, n), v), 0.0, 1.0); float glossAtPosition = clamp(glossMax * glossMaskValue, 0.0, glossMax); glossAtPosition += glossNoise * rand(texCoord); specular = gl_LightSource[0].specular * gl_FrontMaterial.specular * pow(nDotV, glossAtPosition); glossAdditional = nDotV * glossColor * glossAtPosition; } } vec4 color = gl_FrontLightModelProduct.sceneColor + ambient + diffuse; gl_FragColor = color * texture2D(colorMap, texCoord) + specular + glossAdditional; } ```

added on the 2009-09-17 15:19:12 by raer
What do you need binormal for? I hope you're not doing lines or ribbons
added on the 2009-09-17 16:00:47 by 216
you shouldn't need the extra normalization at line
n = normalize(TBN * bump);
since TBN is orthonormal and bump is normalized.

Moreover, one of the two vectors in computeTangentSpace can be computed by cross producting the other two, avoiding another normalization.
added on the 2009-09-17 17:39:56 by pan
Quote:
Moreover, one of the two vectors in computeTangentSpace can be computed by cross producting the other two, avoiding another normalization.

Assuming the two vectors are orthogonal.
added on the 2009-09-17 18:06:18 by imbusy
Thanks for the hint. I also changed the computeTangentFrame function to:

Code:``` mat3 computeTangentFrame(vec3 normal, vec3 position, vec2 texCoord) { vec3 dpx = dFdx(position); vec3 dpy = dFdy(position); vec2 dtx = dFdx(texCoord); vec2 dty = dFdy(texCoord); vec3 tangent = normalize(dpy * dtx.t - dpx * dty.t); vec3 binormal = cross(tangent, normal); return mat3(tangent, binormal, normal); } ```

and it is "if (nDotL > 0.0) {"
added on the 2009-09-17 18:11:31 by raer
Quote:
Quote:
Moreover, one of the two vectors in computeTangentSpace can be computed by cross producting the other two, avoiding another normalization.

Assuming the two vectors are orthogonal.

They're supposed to be, otherwise the matrix wouldn't be orthonormal.

Actually, double checking, in this code there's nothing saying that "normal" is orthogonal to any of the other vectors (normal is interpolated, while tangent and binormal lie on the face's plane), and it's very likely to be NOT orthogonal, so actually the matrix wasn't orthonormal in the first place (even if, probably, nobody will ever notice the difference).

Good point. So, actually you have two choices to make the matrix orthonormal:

1) using the face's normal by crossing tangent and binormal (I doubt this is a good idea)
2) re-computing tangent by crossing binormal and normal just before the return statement in your latest post (you don't need normalization here, but you actually need it for binormal, since tangent and normal are not orthogonal in general).

added on the 2009-09-17 18:41:22 by pan
pan:

How I understand it is this: dFdx/y(position) interpolates the vertex (world coordinates) in the plane of the triangle, we scale those vectors by the texture coordinate and thus they must still lie in the plane of the triangle. The normal is orthogonal to that. So the cross-product should still be orthogonal.
I'm not sure why the other texture-coordinate is not involved though. This might still not be right, but atm it seems to work fine.
added on the 2009-09-21 09:52:54 by raer
You're absolutely right for dfdx/dfdy, but not, in general, for "normal".

"normal" is a varying vector, so is evaluated at vertices and then interpolated. It coincides with the face's normal only if you provide:

- Independent normals for the same vertex shared among multiple faces
- The same vector as normal for all vertex in a face, and that vector is actually the face's normal

So, it really depends on what you wrote on the other side, but in general <<interpolated gl_Normal>> != <<face's normal>>.
added on the 2009-09-21 11:03:01 by pan
You're right...
added on the 2009-09-21 14:33:52 by raer