I've made a quick test with glsl myself.
What I do per vertex is this:
uniform vec4 lightPosition; // position of lightsource in world space (-1.0, 0.5, 1, 1.0)
uniform vec4 cameraPosition; // position of camera in world space (0.0, 0.0, 4,0)
// interpolated vertex attributes (passed to the fragment shader)
varying vec2 uv; // texture coordinate
varying vec3 lightDir; // light direction in normalmap space
varying vec3 viewDir; // view direction in normalmap space
void main()
{
// inverse model matrix (constant for all polys)
mat4x4 invModel= inverse( gl_ModelViewMatrix );
// transform light and camera into object space (untransformed vertices)
// (constant for all polys)
vec3 osLightPos = vec3( invModel * lightPosition );
vec3 osCameraPos = vec3( invModel * cameraPosition );
// pass interpolated uv to fragment shader
uv = gl_MultiTexCoord0.xy;
// tangent space matrix
// transforms vectors from the normalmap into object space
vec3 x= gl_MultiTexCoord1.xyz; // tangent
vec3 y= gl_MultiTexCoord2.xyz; // binormal
vec3 z= gl_Normal;
// inverse tangent space transforms vectors from objects space into normalmap space
mat3 inverseTangentSpaceMatrix = transpose( mat3(x, y, z) );
// direction from light to vertex
lightDir = normalize(gl_Vertex.xyz - osLightPos);
// direction vector from camera to vertex
viewDir= normalize(gl_Vertex.xyz - osCameraPos);
// transform both vectors from object space into normalmap space
lightDir= inverseTangentSpaceMatrix * lightDir;
viewDir= inverseTangentSpaceMatrix * viewDir;
// transform vertex to camera space
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
And per pixel:
// textures
uniform sampler2D texturemap;
uniform sampler2D normalmap;
// interpolated vertex attributes
varying vec2 uv; // texture coordinate
varying vec3 lightDir; // light direction in normalmap space
varying vec3 viewDir; // view direction in normalmap space
void main()
{
// initial color
vec4 col = vec4(0.0, 0.0, 0.0, 0.0);
// constant diffuse color
vec4 diffuseColor= vec4(2.0, 1.2, 1.2, 1.0);
// re-normalized light direction
vec3 nLightDir= normalize( lightDir );
// extract normal from unsigned rgb texture
vec3 n= texture2D(normalmap, uv).xyz * 2.0 - 1.0;
// re-normalize normal (better fix normalmap in advance)
n= normalize( vec3(-n.x, n.y, -n.z) );
// texture color
vec4 texColor1= texture2D(texturemap, uv);
// diffuse lighting
float intensity = dot(n, nLightDir);
if (intensity > 0.0)
{
col += texColor1 * diffuseColor * intensity;
}
// reflection vector
vec3 refl = viewDir - n * 2.0 * dot(n, viewDir);
// specular
float specular = dot(refl, nLightDir);
if (specular > 0.8) // 0.8^30 = 0.0
{
float s = pow(specular, 30.0);
col += s;
}
gl_FragColor = col;
}
And the vertex data I feed into it is the following:
// vertexposition normal tangent binormal uv
/* 0 */ -0.5,-0.5,-0.5, 0, 0,-1, 1, 0, 0, 0,-1, 0, 0,1, // front face (-z)
/* 1 */ -0.5, 0.5,-0.5, 0, 0,-1, 1, 0, 0, 0,-1, 0, 0,0,
/* 2 */ 0.5, 0.5,-0.5, 0, 0,-1, 1, 0, 0, 0,-1, 0, 1,0,
/* 3 */ 0.5,-0.5,-0.5, 0, 0,-1, 1, 0, 0, 0,-1, 0, 1,1,
/* 4 */ 0.5,-0.5, 0.5, 0, 0, 1, -1, 0, 0, 0,-1, 0, 0,1, // back face (+z)
/* 5 */ 0.5, 0.5, 0.5, 0, 0, 1, -1, 0, 0, 0,-1, 0, 0,0,
/* 6 */ -0.5, 0.5, 0.5, 0, 0, 1, -1, 0, 0, 0,-1, 0, 1,0,
/* 7 */ -0.5,-0.5, 0.5, 0, 0, 1, -1, 0, 0, 0,-1, 0, 1,1,
/* 8 */ 0.5,-0.5,-0.5, 1, 0, 0, 0, 0, 1, 0,-1, 0, 0,1, // right face (+x)
/* 9 */ 0.5, 0.5,-0.5, 1, 0, 0, 0, 0, 1, 0,-1, 0, 0,0,
/*10 */ 0.5, 0.5, 0.5, 1, 0, 0, 0, 0, 1, 0,-1, 0, 1,0,
/*11 */ 0.5,-0.5, 0.5, 1, 0, 0, 0, 0, 1, 0,-1, 0, 1,1,
/*12 */ -0.5,-0.5, 0.5, -1, 0, 0, 0, 0,-1, 0,-1, 0, 0,1, // left face (-x)
/*13 */ -0.5, 0.5, 0.5, -1, 0, 0, 0, 0,-1, 0,-1, 0, 0,0,
/*14 */ -0.5, 0.5,-0.5, -1, 0, 0, 0, 0,-1, 0,-1, 0, 1,0,
/*15 */ -0.5,-0.5,-0.5, -1, 0, 0, 0, 0,-1, 0,-1, 0, 1,1,
/*16 */ -0.5,-0.5, 0.5, 0,-1, 0, 1, 0, 0, 0, 0, 1, 0,1, // top face (-y)
/*17 */ -0.5,-0.5,-0.5, 0,-1, 0, 1, 0, 0, 0, 0, 1, 0,0,
/*18 */ 0.5,-0.5,-0.5, 0,-1, 0, 1, 0, 0, 0, 0, 1, 1,0,
/*20 */ 0.5,-0.5, 0.5, 0,-1, 0, 1, 0, 0, 0, 0, 1, 1,1,
/*20 */ -0.5, 0.5,-0.5, 0, 1, 0, 1, 0, 0, 0, 0,-1, 0,1, // bottom face (+y)
/*21 */ -0.5, 0.5, 0.5, 0, 1, 0, 1, 0, 0, 0, 0,-1, 0,0,
/*22 */ 0.5, 0.5, 0.5, 0, 1, 0, 1, 0, 0, 0, 0,-1, 1,0,
/*23 */ 0.5, 0.5,-0.5, 0, 1, 0, 1, 0, 0, 0, 0,-1, 1,1(6 quads of a handsome unit cube)
"gl_ModelViewMatrix" spins the cube around it's center,
"gl_ProjectionMatrix" moves the cube 4 units to the back