The answers above are good, but I'll show you some code snippets I use in WebGL to pull it off.
Usually my GLSL vertex shader looks something like the following. The NormalMatrix
is a 3x3 matrix computed on the client side as the inverse transpose of the upper 3x3 portion of the ModelViewMatrix
and passed as a uniform variable:
attribute vec4 vertexPosition;
attribute vec3 vertexNormal;
...
uniform mat4 ModelViewProjection;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
...
void main() {
gl_Position = ModelViewProjection*vertexPosition;
vec3 P = vec3(ModelViewMatrix * vertexPosition);
vec3 N = normalize(NormalMatrix * vertexNormal);
vec3 L = normalize(light0Position - P);
...
}
On the client side my Matrix4x4
class has a normal()
method that extracts the appropriate 3x3 matrix:
var ModelView = View.mult(Model);
var NormalMatrix = ModelView.normal();
var MVP = Projection.mult(ModelView);
gl.uniformMatrix4fv(program.ModelViewProjection, false, MVP.array);
gl.uniformMatrix4fv(program.ModelViewMatrix, false, ModelView.array);
gl.uniformMatrix3fv(program.NormalMatrix, false, NormalMatrix);
I store matrices in column-major (GL) order:
function Matrix4x4() {
this.array = [1, 0, 0, 0, // stored in column-major order
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1];
}
The normal()
method actually returns a 9 element array represented a 3x3 matrix in column-major order:
Matrix4x4.prototype.normal = function() {
var M = this;
var determinant =
+M.elem(0,0)*(M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))
-M.elem(0,1)*(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))
+M.elem(0,2)*(M.elem(1,0)*M.elem(2,1) - M.elem(1,1)*M.elem(2,0));
var invDet = 1.0/determinant;
var normalMatrix = [];
var N = function(row,col,val) { normalMatrix[col*3 + row] = val; }
N(0,0, (M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))*invDet);
N(1,0,-(M.elem(0,1)*M.elem(2,2) - M.elem(0,2)*M.elem(2,1))*invDet);
N(2,0, (M.elem(0,1)*M.elem(1,2) - M.elem(0,2)*M.elem(1,1))*invDet);
N(0,1,-(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))*invDet);
N(1,1, (M.elem(0,0)*M.elem(2,2) - M.elem(0,2)*M.elem(2,0))*invDet);
N(2,1,-(M.elem(0,0)*M.elem(1,2) - M.elem(1,0)*M.elem(0,2))*invDet);
N(0,2, (M.elem(1,0)*M.elem(2,1) - M.elem(2,0)*M.elem(1,1))*invDet);
N(1,2,-(M.elem(0,0)*M.elem(2,1) - M.elem(2,0)*M.elem(0,1))*invDet);
N(2,2, (M.elem(0,0)*M.elem(1,1) - M.elem(1,0)*M.elem(0,1))*invDet);
return normalMatrix;
}
You can find my client matrix.js
module here.