The projective texture mapping is a technique allowing a texture to be projected onto
the scene's objects like a slide projector would do it.
The secret of texture projection is based on the texture projection matrix initialization
also called TexGenMatrix. If your 3D engine already performs projective texturing using
the 3D pipeline fixed functions, the texGen matrix may be initialized by the following code:
glActiveTextureARB( GL_TEXTURE0_ARB );
glTexGeni(GL_S, GL_EYE_PLANE, &TexGenMatrix[0]);
glTexGeni(GL_T, GL_EYE_PLANE, &TexGenMatrix[4]);
glTexGeni(GL_R, GL_EYE_PLANE, &TexGenMatrix[8]);
glTexGeni(GL_Q, GL_EYE_PLANE, &TexGenMatrix[12]);
This matrix is accessible in the vertex shader by the gl_TextureMatrix[tu] variable
where tu is the texture unit for which the TexGenMatrix matrix has been
initialized (in the example, tu=0).
For those that would like to implement the TexGenMatrix initialization,
just understand that this matrix is somewhat equivalent to the camera matrix.
Indeed, the texGen matrix defines all transformations (projection matrix, view matrix) of the
projector that, in the texture projection context, works like a camera. For more detail on this topic,
see the excellent whitepaper by Cass Everitt: Projective Texture Mapping.
The DEMO_Projective_Texture_Mapping.xml demo shows all the stages of the texGen matrix
construction for projective texture mapping. The construction of the texGen matrix being done
using the great LUA language, the conversion to another language should be a mere formality!
The following image shows the rendering of the DEMO_Projective_Texture_Mapping.xml demo:
Fig. 22 - the DEMO_Projective_Texture_Mapping.xml demo
The major advantage of making the texture projection with shaders is that the
reverse projection can be solved in a simple and clean way. The following screenshot illustrates the
reverse projection:
Fig. 23 - the problem of reverse projection
The projected texture coordinates are obtained by multiplying the TexGenMatrix
by the vertex position in world space (cf posWorld in the code). These coordinates
form a {s, t, r, q} quadruplet. The reverse projection occurs when the {q} coordinate becomes
negative. It is not easy to act on the {q} coordinate at the fixed functions level, but in a
pixel shader it is really simple as shown by the following shader code (from the DEMO_Projective_Texture_Mapping.xml
demo):
[Vertex_Shader]
uniform mat4 TexGenMat;
uniform mat4 InvViewMat;
varying vec3 normal, lightDir, eyeVec;
void main()
{
normal = gl_NormalMatrix * gl_Normal;
vec4 posEye = gl_ModelViewMatrix * gl_Vertex;
vec4 posWorld = InvViewMat * posEye;
gl_TexCoord[0] = TexGenMat * posWorld;
lightDir = vec3(gl_LightSource[0].position.xyz - posEye.xyz);
eyeVec = -posEye.xyz;
gl_Position = ftransform();
}
[Pixel_Shader]
uniform sampler2D projMap;
varying vec3 normal, lightDir, eyeVec;
void main (void)
{
vec4 final_color =
(gl_FrontLightModelProduct.sceneColor * gl_FrontMaterial.ambient) +
(gl_LightSource[0].ambient * gl_FrontMaterial.ambient);
vec3 N = normalize(normal);
vec3 L = normalize(lightDir);
float lambertTerm = dot(N,L);
if(lambertTerm > 0.0)
{
final_color += gl_LightSource[0].diffuse *
gl_FrontMaterial.diffuse * lambertTerm;
vec3 E = normalize(eyeVec);
vec3 R = reflect(-L, N);
float specular = pow( max(dot(R, E), 0.0),
gl_FrontMaterial.shininess );
final_color += gl_LightSource[0].specular *
gl_FrontMaterial.specular *
specular;
// Suppress the reverse projection.
if( gl_TexCoord[0].q>0.0 )
{
vec4 ProjMapColor = texture2DProj(projMap, gl_TexCoord[0]);
final_color += ProjMapColor*lambertTerm;
}
}
gl_FragColor = final_color;
}
This pixel shader is a variation of the phong lighting one available in the
Point Light
tutorial. The projected texture coordinates are calculated by the following code:
vec4 posEye = gl_ModelViewMatrix * gl_Vertex;
vec4 posWorld = InvViewMat * posEye;
gl_TexCoord[0] = TexGenMat * posWorld;
where TexGenMat is the texture projection matrix and InvViewMat
is the inverse view matrix. The goal of this last one is simply to calculate
the vertex position in world space. The texture projection is done in the pixel shader by the
following code:
if( gl_TexCoord[0].q>0.0 )
{
vec4 ProjMapColor = texture2DProj(projMap, gl_TexCoord[0]);
final_color += ProjMapColor*lambertTerm;
}
The texture2DProj() allows us to directly exploit the projected textures coordinates
stored in gl_TexCoord[0]. The call to texture2DProj() could be remplaced by
a call to texture2D() but in that case we have to divide the {s, t} coordinates
by the {q} one as the following code shows it:
if( gl_TexCoord[0].q>0.0 )
{
vec2 projCoords = gl_TexCoord[0].st / gl_TexCoord[0].q;
vec4 ProjMapColor = texture2D(projMap, projCoords);
final_color += ProjMapColor*lambertTerm;
}