I'm trying to learn about normal mapping using Three.js, but I just can't seem to get it right.
Here is my code:
Javascript:
var bufferGeometry = new THREE.BufferGeometry();
bufferGeometry.fromGeometry( new THREE.BoxGeometry(2,2,2) );
var positionAttributes = bufferGeometry.getAttribute('position');
var uvAttributes = bufferGeometry.getAttribute('uv');
var realVertices = [];
var realUvs = [];
for (var i = 0; i < positionAttributes.array.length ; i += 3){
realVertices.push(new THREE.Vector3(positionAttributes.array[i+0], positionAttributes.array[i+1], positionAttributes.array[i+2] ));
}
for (var i = 0; i < uvAttributes.array.length; i+= 2){
realUvs.push (new THREE.Vector2( uvAttributes.array[i], uvAttributes.array[i+1] ) );
}
var tangents = new Float32Array(positionAttributes.array.length);
var bitangents = new Float32Array(positionAttributes.array.length);
var tangArray = [];
var bitangentArray = [];
for (var i = 0; i < realVertices.length ; i += 3){
var v0 = realVertices[i+0];
var v1 = realVertices[i+1];
var v2 = realVertices[i+2];
var uv0 = realUvs[i+0];
var uv1 = realUvs[i+1];
var uv2 = realUvs[i+2];
var deltaPos1 = v1.sub(v0);
var deltaPos2 = v2.sub(v0);
var deltaUV1 = uv1.sub(uv0);
var deltaUV2 = uv2.sub(uv0);
var r = 1.0 / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
var tangent = deltaPos1.multiplyScalar(deltaUV2.y).sub( deltaPos2.multiplyScalar(deltaUV1.y) ).multiplyScalar(r); //p1 * uv2.y - p2 * uv1.y
var bitangent = deltaPos2.multiplyScalar(deltaUV2.x).sub( deltaPos1.multiplyScalar(deltaUV2.x) ).multiplyScalar(r);
tangArray.push(tangent.x);
tangArray.push(tangent.y);
tangArray.push(tangent.z);
tangArray.push(tangent.x);
tangArray.push(tangent.y);
tangArray.push(tangent.z);
tangArray.push(tangent.x);
tangArray.push(tangent.y);
tangArray.push(tangent.z);
bitangentArray.push (bitangent.x);
bitangentArray.push (bitangent.y);
bitangentArray.push (bitangent.z);
bitangentArray.push (bitangent.x);
bitangentArray.push (bitangent.y);
bitangentArray.push (bitangent.z);
bitangentArray.push (bitangent.x);
bitangentArray.push (bitangent.y);
bitangentArray.push (bitangent.z);
}
for (var i = 0; i < bitangentArray.length; i++ ){
tangents[i] =tangArray[i];
bitangents[i] = bitangentArray[i];
}
bufferGeometry.addAttribute( 'tangent', new THREE.BufferAttribute( tangents, 3 ) );
bufferGeometry.addAttribute( 'bitangent', new THREE.BufferAttribute( bitangents, 3 ) );
Vertex shader:
varying vec3 L;
varying vec3 V;
uniform vec3 lightPosition;
uniform vec3 eyePosition;
varying vec2 vUv;
attribute vec3 tangent;
attribute vec3 bitangent;
void main() {
vUv = uv; //uv is built-in
mTangent = bitangent;
vec3 surfaceNormal = (modelViewMatrix * vec4(normal,0.0)).xyz;
vec3 norm = normalize(surfaceNormal);
vec3 tang = normalize((modelViewMatrix * vec4(tangent, 0.0)).xyz);
vec3 bitang = normalize(cross(norm, tang));
mat3 toTangentSpace = mat3(
tang.x, bitang.x, norm.x,
tang.y, bitang.y, norm.y,
tang.z, bitang.z, norm.z
);
vec3 mvPosition = (modelViewMatrix * vec4(position,1.0)).xyz;
L = toTangentSpace * (lightPosition - mvPosition) ;
V = toTangentSpace * (- mvPosition);
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
Fragment shader:
varying vec3 L;
varying vec3 V;
uniform vec3 lightColor;
uniform sampler2D baseTexture;
uniform sampler2D normalMap;
varying vec2 vUv;
void main() {
vec4 normalMapValue = 2.0 * texture2D(normalMap, vUv, -1.0) - 1.0;
vec3 unitNormal = normalize(normalMapValue.rgb);
vec3 unitVectorToCamera = normalize(V);
vec3 totalDiffuse = vec3(0.0);
vec3 totalSpecular = vec3(0.0);
float distance = length(L);
vec3 unitLightVector = normalize(L);
float nDotl = dot(unitNormal,unitLightVector);
float brightness = max(nDotl,0.0);
totalDiffuse = vec3( 0.5,0.5,0.5) + totalDiffuse + (brightness * lightColor);
vec4 textureColour = texture2D(baseTexture,vUv);
gl_FragColor= vec4(totalDiffuse,1.0) * textureColour ;
}
Which doesn't look very right. I've been trying to follow http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/ but instead implementing it in three.js, but had no luck. Does anyone have any ideas?