Depth Shadow maps

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.


9 Comments

  • By Peter Wallström, May 4, 2010 @ 17:03

    -old comments-

    Comment by mziskandar on 2007:02:21 15:43
    the tutorial im waiting for… great zeo!

    Comment by mziskandar on 2007:02:21 19:26
    sadly, im receiving: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT error.

    im using ati9600xt.

    Comment by mziskandar on 2007:02:21 20:36
    just want to share with anybody who failed to compile using vs2005.

    remove:
    #include

    add:
    #include
    using namespace std;

    Comment by eXile on 2007:08:30 10:51
    Oh well …

    … I just got it running in my Ati 9600 Pro (fixed some errors, like mziskandar said).

    Comment by eXile on 2007:08:30 11:15
    In glsl.frag REPLACE the three lines:
    gl_TexCoord[2] = gl_TexCoord[2]/gl_TexCoord[2].w;

    gl_TexCoord[2]=(gl_TexCoord[2]+ 1.0) * 0.5;

    With:
    vec4 texCoord2 = ((gl_TexCoord[2]/gl_TexCoord[2].w) + 1.0) * 0.5;

    In glsl.frag REPLACE the line:
    vec4 shadow = texture2D(texunit2,gl_TexCoord[2].xy);

    With:
    vec4 shadow = texture2D(texunit2,texCoord2.xy);

    In glsl.frag REPLACE the line:
    if((shadow.z+0.005) < gl_TexCoord[2].z) With: if((shadow.z+0.005) < texCoord2.z) --- In glsl.frag REPLACE the three lines: float fresnel =max((norm.z-0.6)*-1.0,0); float diffuse = max(dot(lightVec, norm),0); With: float fresnel =max((norm.z-0.6)*-1.0,0.0); float diffuse = max(dot(lightVec, norm),0.0); --- In ZeoTut03a.cpp REPLACE the line: glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 2048, 2048, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL ); With: glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 2048, 2048, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL ); --- And recompile :-) If you are using Visual Studio 2005, you have to replace all "#include fstream.h" with "#include fstream" + an "using namespace std;". If you never compiled an OpenGL-application before, you also need to download the glext.h from http://oss.sgi.com/projects/ogl-sample/ABI/glext.h and place it into your Visual Studio 2005 installation path into …/VC/PlatformSDK/Include/gl.

    Tested twice and working with above changes on a Ati 9600 Pro.

    Comment by vinnythepoo on 2009:05:16 18:58
    Thanks for your tutorials,
    i have noticed that in your shadow mapping you declare the bias matrix, but you don’t use, and also you don’t invert anymore the camera, why ?

    Comment by vinnythepoo on 2009:05:16 20:59
    Forgot another thing, looks like that the instructions

    glMatrixMode(GL_TEXTURE);
    glLoadMatrixf(textureMatrix);

    Completely corrupt the pipeline once the function ends, i see because i have an fps counter and it is not shown

    Comment by Overlord on 2009:05:16 23:30
    The bias matrix was previously used but i later put that in the shader, the downloadable code as an older version that i just didn’t update so the code on this page is the correct one, i will probably do a full update once i have time, i am working on like 6 different projects right now all with a close deadline, but once it’s done i think you will like it.

    regarding glMatrixMode(GL_TEXTURE);, yea that might happen, just reset it with
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity ();
    I used the texture matrix because it’s convenient and easy to understand, in a production environment i would probably send a uniform to the shaders, it’s also something your required to do in 3.1.

    Comment by vinnythepoo on 2009:05:17 07:41

    I tried to reset the texture matrix but i get blank screen if i reset it, even after the end of the rendering function.
    How can i solve this ? looks like the texture matrix is corrupted after being used

    Comment by wallifin@hotmail.com on 2010:04:23 10:10
    hi vinnythepoo,
    try to change this lines
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(cameraProjectionMatrix);

    glMatrixMode(GL_TEXTURE);
    glLoadMatrixf(textureMatrix);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(cameraViewMatrix);
    with:
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadMatrixf(cameraProjectionMatrix);

    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glLoadMatrixf(textureMatrix);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf(cameraViewMatrix);

    and at the end of rendering add this line:
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glPopMatrix();

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glPopMatrix();
    glFlush ();

    for any questions send me a mail
    reguards
    walter :)
    exsuse me for my bad english :(

  • By ruxbin, August 16, 2010 @ 16:48

    Hey,nice shadow map tutorial.
    But one thing I’m not quite sure is about the following code in vertex shader
    “pos= gl_ModelViewProjectionMatrix * gl_Vertex;”
    which I think should be pos=gl_ModelView * gl_Vertex. Because we are considering camera coordinates while multiplying with gl_ModelViewProjectionMatrix will transform to the clip coordinates space.
    And after changing the code, I do see difference :)
    Is that right? Or U write that code 4 some specific reason?
    Thx

  • By Peter Wallström, August 16, 2010 @ 20:02

    Your right, it should be
    pos= gl_ModelViewMatrix * gl_Vertex;

    at certiain angles the difference is so small i didn’t notice it, the reason it’s there in the first place was because pos was originally used for something else.

  • By Aviel, June 3, 2011 @ 08:53

    Is there any common reason why this code might just render blackness, or have I made a very specific and serious screwup?

  • By Peter Wallström, June 3, 2011 @ 16:10

    There is no common reason for that, if it’s done right it just works, if you did something wrong it should render something even though it’s probably wrong, the only things i can think of is if your running openGL 3.x in forward compatible mode and the shader doesn’t compile (and you have no error checking) it wouldn’t render anything.
    or if you happen to render something over the screen it might also go black.
    Alternatively since it uses a fbo for the shadow map if you don’t switch to the back buffer nothing will ever render.
    So it’s probably something you did wrong for some reason, try messing around with the code and comment out parts and see what happens.

  • By Aviel, June 3, 2011 @ 19:19

    I have screwed around with it, and I only bug other people as a last resort. I probably screwed something up with the buffers, because I’ve verified that my shader isn’t the problem.

  • By Bob, February 25, 2012 @ 08:14

    How do i make it so the shadow does not move when the camera does?

  • By Peter Wallström, February 25, 2012 @ 10:20

    it’s all in this bit of code
    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);

    now if you remove the glRotatef the shadow should stop moving.

  • By Bob, February 29, 2012 @ 02:22

    I have tried removing the glRotatef.

    Here is the code i use to update the matrices

    glPushMatrix();
    glLoadIdentity();
    gluPerspective(45.0f, (float)mWidth/mHeight, 1.0f, 1000.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, cameraProjectionMatrix);

    glLoadIdentity();
    glTranslatef(-xTrans,yTrans,mZoom);
    glRotatef(xRot / 16.0f, 1.0, 0.0, 0.0);
    glRotatef(yRot / 16.0f, 0.0, 1.0, 0.0);
    glRotatef(zRot / 16.0f, 0.0, 0.0, 1.0);
    glGetFloatv(GL_MODELVIEW_MATRIX, cameraViewMatrix);

    glLoadIdentity();
    gluPerspective(65.0f, 1.0f, 1.0f, 100.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, lightProjectionMatrix);

    glLoadIdentity();
    gluLookAt(mLightPos.x,mLightPos.y,mLightPos.z,0.0f, 0.0f, 0.0f,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);

    My shader’s are exactly the same as above but the shadow still moves with the camera. The shadow map is getting created from the camera’s POV and not the Light’s POV. Does anything look wrong in how i am creating the matrices?

    The light pos never changes:
    mLightPos = vec3(-1.75f,7.8f,-13.0f);

Other Links to this Post

RSS feed for comments on this post. TrackBack URI

Leave a comment

*


WordPress Themes