Shadows are always nice, but they can sometimes be a hazzle to implement, i am going to show you the simplest way to implement them in a method called projected depth shadow maps.
I am also going to bring up a way to create some nice shadows and light effects.
But first some theory, what we are going to do is.
1. render the depth to a texture, i will use a FBO here since they are nice an easy to use.
2. i am going to compare this map with the distance from the lightsource to what i am going to render.
3. finally we have to mask all the artifacts, and believe me, there are plenty of them there.
Now the real problem here is the texture projection, but fortunately openGL and glsl has some tricks up it’s sleeve.
What you do is you first make two matrices, one projection and one modelview matrix, we do the same with the rendering matrices to insure we will get the same every time.
Next we multiply the lightprojection and lightmodelview matrices into a temp matrix.
Then we combine that matrix with the inverted camera matrix and call it the texture matrix.
When we render the final rendering we then need to feed openGL with the new texture matrix, normally this is normally done with glTexGen, but we are going to take a little shortcut by directly uploading it with glLoadMatrixf.
Then in the shader we call a simple little code to buid new texture coordinates
gl_TexCoord[2] = gl_TextureMatrix[0]*gl_ModelViewMatrix*gl_Vertex; // in the vertex shader
gl_TexCoord[2] = gl_TexCoord[2]/gl_TexCoord[2].w; // fragment shader
gl_TexCoord[2]=(gl_TexCoord[2]+ 1.0) * 0.5;
now you need only use this texture coordinate and then compare the resulting texture lookup with the third parameter in the texture coordinate to determine if it’s in a shadow or not.
Sounds simple, so let’s get cracking
First of all this lesson is based on lesson 2c + the FBO parts of lesson 1, so all the helper funcs are there then we need two more functions.
void Combine_Matrix4(float MatrixA[16],float MatrixB[16], float *retM)
{
float NewMatrix[16];
int i;
for(i = 0; i < 4; i++){ //Cycle through each vector of first matrix.
NewMatrix[i*4] = MatrixA[i*4] * MatrixB[0] + MatrixA[i*4+1] * MatrixB[4] + MatrixA[i*4+2] * MatrixB[8] + MatrixA[i*4+3] * MatrixB[12];
NewMatrix[i*4+1] = MatrixA[i*4] * MatrixB[1] + MatrixA[i*4+1] * MatrixB[5] + MatrixA[i*4+2] * MatrixB[9] + MatrixA[i*4+3] * MatrixB[13];
NewMatrix[i*4+2] = MatrixA[i*4] * MatrixB[2] + MatrixA[i*4+1] * MatrixB[6] + MatrixA[i*4+2] * MatrixB[10] + MatrixA[i*4+3] * MatrixB[14];
NewMatrix[i*4+3] = MatrixA[i*4] * MatrixB[3] + MatrixA[i*4+1] * MatrixB[7] + MatrixA[i*4+2] * MatrixB[11] + MatrixA[i*4+3] * MatrixB[15];
}
memcpy(retM,NewMatrix,64);
}
void Inverse_Matrix4(float m[16], float *ret)
{
float inv[16]; // The inverse will go here
inv[0] = m[0];
inv[1] = m[4];
inv[2] = m[8];
inv[4] = m[1];
inv[5] = m[5];
inv[6] = m[9];
inv[8] = m[2];
inv[9] = m[6];
inv[10] = m[10];
inv[12] = inv[0]*-m[12]+inv[4]*-m[13]+inv[8]*-m[14];
inv[13] = inv[1]*-m[12]+inv[5]*-m[13]+inv[9]*-m[14];
inv[14] = inv[2]*-m[12]+inv[6]*-m[13]+inv[10]*-m[14];
inv[3] = 0.0f;
inv[7] = 0.0f;
inv[11] = 0.0f;
inv[15] = 1.0f;
memcpy(ret,inv,64);
}
|
THese will help with some of the matrix math, once you start making more advanced stuff you should put all of this in a larger class.
Next we set up the fbo
glBindTexture(GL_TEXTURE_2D, depth_tex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 2048, 2048, 0,
GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL );
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,
GL_TEXTURE_2D, depth_tex, 0);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, color_rb);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,GL_RGBA,2048, 2048);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, color_rb);
|
Then in the update function we need to build all five matrices, these matrices are really only float[16].
glPushMatrix();
glLoadIdentity();
gluPerspective(45.0f, (float)800/600, 10.0f, 1000.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, cameraProjectionMatrix);
glLoadIdentity();
gluLookAt(0, 17, 46,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
glRotatef(angle,0.0f,1.0f,0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, cameraViewMatrix);
glLoadIdentity();
gluPerspective(65.0f, 1.0f, 25.0f, 200.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, lightProjectionMatrix);
glLoadIdentity();
gluLookAt( lpos[0], lpos[1], lpos[2],
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
glRotatef(angle,0.0f,1.0f,0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, lightViewMatrix);
glPopMatrix();
float tempa[16];
float inverted[16];
Inverse_Matrix4(cameraViewMatrix,inverted);
Combine_Matrix4(lightViewMatrix,lightProjectionMatrix, tempa);
Combine_Matrix4(inverted,tempa, textureMatrix);
|
Notice that we invert the cameraViewMatrix and combine it with the combined light matrix, we do this to cancel out the camera movement, this moves the texture matrix from world space to camera space and it’s what allows us to move things about without it getting all “funky”.
Next we have the renderplane function, i won’t explain it but it draws a single quad, super simple.
Then we are going to start rendering, first the light pass., it’s simple enough, but instead of the usual glTranslatef and glRotatef we load our premade matrices.
glViewport (0, 0, 2048, 2048);
glClearDepth (1.0f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
checkfbo();
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity ();
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(lightProjectionMatrix);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(lightViewMatrix);
glColor3f(1,1,1);
cv90_render();
|
Then we set up a few things
glViewport (0, 0, 800, 600);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glClearDepth (1.0f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity ();
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(cameraProjectionMatrix);
glMatrixMode(GL_TEXTURE);
glLoadMatrixf(textureMatrix);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(cameraViewMatrix);
glColor3f(1,1,1);
if (useshader) glUseProgramObjectARB(ProgramObject);
sendUniform1i("texunit0", 0);
sendUniform1i("texunit1", 1);
sendUniform1i("texunit2", 2);
sendUniform3f("lpos", lpos);
|
Now comes the fun bit, just make a note that we are not doing anything funny here, it’s a simple setup of multi texturing and then the rendering.
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,tex1);
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,tex2);
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,depth_tex);
cv90_render();
//switch to the ground texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,tex3);
glActiveTextureARB(GL_TEXTURE1_ARB);
glBindTexture(GL_TEXTURE_2D,tex4);
RenderPlane();
glActiveTextureARB(GL_TEXTURE2);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE1);
glDisable(GL_TEXTURE_2D);
glActiveTextureARB(GL_TEXTURE0);
glDisable(GL_TEXTURE_2D);
if (useshader) glUseProgramObjectARB(0);
glFlush ();
|
Ok on to the shader parts.
Vertex shader
uniform sampler2D texunit0;
uniform sampler2D texunit1;
uniform vec3 lpos;
varying vec4 pos;
varying vec3 normal;
varying vec3 lightVec;
varying vec3 viewVec;
void main( void )
{
pos= gl_ModelViewProjectionMatrix * gl_Vertex;
normal = normalize(gl_NormalMatrix * gl_Normal);
lightVec = normalize(lpos - pos.xyz);
viewVec = vec3 (normalize(- (gl_ModelViewProjectionMatrix *gl_Vertex)));
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_TexCoord[2] = gl_TextureMatrix[0]*gl_ModelViewMatrix*gl_Vertex;
}
|
Fragment shader
uniform sampler2D texunit0;
uniform sampler2D texunit1;
uniform sampler2D texunit2;
uniform vec3 lpos;
varying vec4 pos;
varying vec3 normal;
varying vec3 lightVec;
varying vec3 viewVec;
void main( void )
{
gl_TexCoord[2] = gl_TexCoord[2]/gl_TexCoord[2].w;
gl_TexCoord[2]=(gl_TexCoord[2]+ 1.0) * 0.5;
vec4 base = texture2D(texunit0, gl_TexCoord[0].xy);
vec3 norm = texture2D(texunit1, gl_TexCoord[0].xy).xyz*2.0-1.0;
vec4 shadow = texture2D(texunit2,gl_TexCoord[2].xy);
norm = normalize(gl_NormalMatrix * norm);
float fresnel =max((norm.z-0.6)*-1.0,0.0);
float diffuse = max(dot(lightVec, norm),0.0);
float specular = max(dot(reflect(lightVec,norm), viewVec), 0.0)*1.7;
specular=pow(specular,8.0);
float shade=1;
if((shadow.z+0.005) < gl_TexCoord[2].z)
shade=0;
else
shade= diffuse;
gl_FragColor = (base* shade)+ (vec4(0.0,0.1,0.3,0.0)*fresnel) +(vec4(0.5,0.5,0.4,0.0)*specular*shade);
}
|
And that is it, shadow mapping will produce a lot of artifacts, one of them can be hidden by always including the diffuse value among the shadow.
I will continue to clear out some of them in coming tuts.
Download this tutorial for MSVCPP 6.0
note: this does not include the updated code so you need to change it before using.