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.
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" >
