How To Convert Tangent Space Normal Map to World Space?

186 Views Asked by At

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

The library I'm using is Three.js, which 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

let camera,scene,mesh,renderer


//----- get the src and set to texture----
const nmap  = document.getElementById('nmap' ).src;
const texture1 = new THREE.TextureLoader().load(nmap);

// Create a custom shader material
const vertexShader = `
attribute vec4 tangent;
uniform sampler2D tx;
varying vec3 vn;
varying vec3 vp;
varying vec2 vUv;
varying vec4 vt;

vec3 orthogonal(vec3 n){
 return normalize(
 abs(n.x)>abs(n.z) ? vec3(-n.y,n.x,0.) : vec3(0.,-n.z,n.y)
 );
}


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);
}
`;




const fragmentShader = `
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);

}
`;


// animation
init()


function init(){
//-----------Basic setUp
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animation );
document.body.appendChild( renderer.domElement );
renderer.setClearColor( 'white' )

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 1000 );
camera.position.z = 20;
var controls = new THREE.OrbitControls(camera, renderer.domElement);
scene = new THREE.Scene();

//------------front
let frontUnifrom = {tx:{value:texture1}}
let front = createPlaneMesh(0,0,0,0,0,0,frontUnifrom)
scene.add( front );

}


//--------build mesh
function createPlaneMesh(x, y, z, rotationX, rotationY, rotationZ, uniforms) {
const planeGeometry = new THREE.PlaneGeometry(10, 10, 50, 50);
planeGeometry.computeTangents()
const planeMaterial = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
});
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.position.set(x, y, z);
planeMesh.rotation.set(rotationX, rotationY, rotationZ);
planeMesh.frustumCulled = false
return planeMesh;
}

//-----------
function animation( time ) {
    renderer.render( scene, camera );

}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> 
<img id='nmap' src="https://i.imgur.com/fURjYnD.jpg" width="100" height="100" >

0

There are 0 best solutions below