How To Convert Tangent Space Normal Map to World Space?

I’m trying to convert my tangent space normal map to world space. Why, you might ask? Because my tangent space normal map produces seams when placed next to an adjacent mesh with a continuation of the texture. I’m not sure if converting from tangent to world would work, but I have to try.

I know that to compute the TBN matrix, you need three vectors: the tangent, bitangent, and normal, as stated in OpenGL Tutorial 13 : Normal Mapping

Three.js has a method that computes the tangent for you. It is already defined here and sets it as an attribute to be passed to the shader. This makes my life a lot easier. I can then compute the TBN matrix like this:

Vertex Shader

attribute vec4 tangent;
uniform sampler2D tx;
varying vec3 vn;
varying vec3 vp;
varying vec2 vUv;
varying vec4 vt;

void main() {
  vUv = uv;
  vp = position;
  vn = normal;
  vt = tangent;
  vec4 worldPosition = modelMatrix * vec4(position, 1.0);
  gl_Position  = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

Fragment Shader

varying vec3 vn;
varying vec3 vp;
varying vec4 vt;
varying vec2 vUv;

uniform sampler2D tx;


float light(vec3 normalMap, vec3 lightPosition, vec3 cP) {

    vec3 lightDirection = normalize(lightPosition - normalMap.xyz);
    vec3 viewDirection = normalize(cP - normalMap.xyz);
    vec3 ambientColor = vec3(0.2, 0.2, 0.2);  // Ambient light color
    vec3 diffuseColor = vec3(0.2, 0.2, 0.2);  // Diffuse light color
    vec3 specularColor = vec3(0.2, 0.2, 0.2); // Specular light color
    float shininess = 0.0;  // Material shininess factor

    // Ambient lighting calculation
    vec3 ambient = ambientColor;

    // Diffuse lighting calculation
    float diffuseIntensity = max(dot(normalMap.xyz, lightDirection), 0.0);
    vec3 diffuse = diffuseColor * diffuseIntensity;

    // Specular lighting calculation
    vec3 reflectionDirection = reflect(-lightDirection, normalMap.xyz);
    float specularIntensity = pow(max(dot(reflectionDirection, viewDirection), 0.0), shininess);
    vec3 specular = specularColor * specularIntensity;

    // Final lighting calculation
    vec3 finalColor = ambient + diffuse + specular;
    return clamp(dot(normalMap.xyz, lightDirection), 0.0, 1.0) * max(max(finalColor.r, finalColor.g), finalColor.b);
}





void main() {
vec3 nMap = texture2D(tx,vUv).rgb;

vec4 tangent = vt;
vec3 bitangent = normalize(cross(tangent.rgb,vn));
mat3 TBN = mat3(tangent.rgb,bitangent,normalize(vn));

vec3 worldSpaceNormal = normalize(TBN * nMap);

//uncomment it add light
/*
vec3 lightDirection = vec3(0.,0.,100.);
vec3 cameraPosition = vec3(0.,0.,0.);
float finalColor = light(worldSpaceNormal,lightDirection,cameraPosition);
*/
gl_FragColor = vec4(vec3(worldSpaceNormal), 1.0);

}

The results are not as expected. If this were correct, it should look more like this image. This is the normal I’m trying to produce.

enter image description here

CODE EXAMPLE

I didnt knew three has a method for computing the tangent, good catch.

This could be handy for others, so try sharing an online fiddle or similar for us to try ideas.

In the meantime I would go replacing this:

With this

gl_FragColor = vec4(worldSpaceNormal, 1.0);

Since wsn is already a vec3, and you don’t want to flatten it with the exact same values for three components

There’s a example link under the image