Fabien Sanglard's non-blog
ShadowMapping with GLSL
And hence, I had to learn matrices.
The actual stuff
When I got started learning openGL Shading Language, the "Orange Book" was a great resource, but I thought it lacked code samples.
Here is a modest cross platform implementation of shadow mapping.
My goal was to provide something easy to compile, on Windows, MacOS and Linux. That's why the entire source is one .c file (+2 shaders)
coded in ANSI C. Crossplatform window management is performed via GLUT. I did not use GLEW to ensure Microsoft Windows portability, again
this is for ease of compilation.
Source:
main.c
VertexShader.glsl
FragmentShader.glsl
Zipped pack
Win32 and macOS X Binaries:

Note: Due to a Macos X bug with GLUT (relative path is lost an set to "/" when the app starts), I cannot
distribute the binary. Running the sample in XCode is fine.
Note 2: Kris de Greve ported the code to C# mono. Source code here
Code explanation
This is the rawest form of shadow mapping:
- Rendering is done offscreen via FBO. There is no fallback using
glCopyTexSubImage2Das I assumed that any GPU with support for GLSL would also support FBOs. - There is no "hidden" mechanisms in the shader. Function used for lookup is
texture2Dand the W divide is done manually. I will write an other sample usingshadow2DProj, built-in GLSL functions. - The light matrix is passed via the TEXTURE7 matrix. It's not best practice to re-use variables but it made the code shorter and IMHO less scary for a beginner to read.
FBO creation
The shadowmap is rendered offscreen via an openGL Framebuffer Object (FBO). Only depth value are saved during rendition, there
is no color texture bound to the FBO and color writes are disabled via glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE).
// Hold id of the framebuffer for light POV rendering
GLuint fboId;
// Z values will be rendered to this texture when using fboId framebuffer
GLuint depthTextureId;
void generateShadowFBO()
{
int shadowMapWidth = RENDER_WIDTH * SHADOW_MAP_RATIO;
int shadowMapHeight = RENDER_HEIGHT * SHADOW_MAP_RATIO;
GLenum FBOstatus;
// Try to use a texture depth component
glGenTextures(1, &depthTextureId);
glBindTexture(GL_TEXTURE_2D, depthTextureId);
// GL_LINEAR does not make sense for depth texture. However, next tutorial shows usage of GL_LINEAR and PCF
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Remove artifact on the edges of the shadowmap
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
// No need to force GL_DEPTH_COMPONENT24, drivers usually give you the max precision if available
glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapWidth, shadowMapHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
// create a framebuffer object
glGenFramebuffersEXT(1, &fboId);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
// Instruct openGL that we won't bind a color texture with the currently bound FBO
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
// attach the texture to FBO depth attachment point
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, depthTextureId, 0);
// check FBO status
FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n");
// switch back to window-system-provided framebuffer
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
Light POV transformation
I've read a lot of tutorial where people concatenate the "reverse camera transform". I don't see the utility
of this and I prefer to load directly the bias*projection*modelview matrix in the GL_TEXTURE7 matrix.
void setTextureMatrix(void)
{
static double modelView[16];
static double projection[16];
// Moving from unit cube [-1,1] to [0,1]
const GLdouble bias[16] = {
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0};
// Grab modelview and transformation matrices
glGetDoublev(GL_MODELVIEW_MATRIX, modelView);
glGetDoublev(GL_PROJECTION_MATRIX, projection);
glMatrixMode(GL_TEXTURE);
glActiveTextureARB(GL_TEXTURE7);
glLoadIdentity();
glLoadMatrixd(bias);
// concatating all matrices into one.
glMultMatrixd (projection);
glMultMatrixd (modelView);
// Go back to normal matrix mode
glMatrixMode(GL_MODELVIEW);
}
Vertex Shader
No rocket science here, we transform the vertex with the camera matrices, the same vertex with the light POV matrix and we get the fragment color
// Used for shadow lookup
varying vec4 ShadowCoord;
void main()
{
ShadowCoord= gl_TextureMatrix[7] * gl_Vertex;
gl_Position = ftransform();
gl_FrontColor = gl_Color;
}
Fragment Shader
The shadow variable hold the shadowed test result. As you see, the goal is to compare the z value (in light POV)
of the vertex rendered, with what was rendered to the shadowmap.
uniform sampler2D ShadowMap;
varying vec4 ShadowCoord;
void main()
{
vec4 shadowCoordinateWdivide = ShadowCoord / ShadowCoord.w ;
// Used to lower moiré pattern and self-shadowing
shadowCoordinateWdivide.z += 0.0005;
float distanceFromLight = texture2D(ShadowMap,shadowCoordinateWdivide.st).z;
float shadow = 1.0;
if (ShadowCoord.w > 0.0)
shadow = distanceFromLight < shadowCoordinateWdivide.z ? 0.5 : 1.0 ;
gl_FragColor = shadow * gl_Color;
}
Avoid self-shadowing and Moiré pattern
Self-shadowing occurs because of the depth buffer limited precision. This is also know as Z-fighting. This only
affect polygons facing the light because it's what was rendered to the shadowmap.

Even if you use the maximum precision available (GL_DEPTH_COMPONENT24) this is not an issue that can be solved efficiently with raw power:
No level of precision can totally get ride of self-shadowing.
A good technique to reduce it is to cull front facing polygons during the shadowmap rendition step, using glCullFace(GL_FRONT)
and switch back to glCullFace(GL_BACK) during the second step. Here is the result:

As you can see this technique only move the self-shadowing issue to the polygon not facing the light, but it's much less noticeable
Adding a small bias during sampling shadowCoordinateWdivide.z += 0.0005 remove the selfshadowing from the backfaces as well.

The importance of shadowmap resolution
Whatever resolution you use, depending on the position of the light, you will experiment aliasing issue with your shadow.
For this issue, raw processing power can help a little : You can crank up the resolution to which you render the shadowmap.
160x120 shadowmap:

640x480 shadowmap:

1280x960 shadowmap:

Set the texture filtering to GL_LINEAR won't help much. The best way, it to use Percentage Closer Filtering (PCF).
This algorithm will provides us with a tiny penumbra, one step toward shadows, this shadow method is covered in my next article.
Avoid artifacts behind and on the sides of the light
When using shadowmapping, you get some projection artifacts when you try to retrieve shadow information out of the light frustrum.
On the sides of the frustrum and behind it.
On the side:
As you can see on the upper left, the cube's shadows are projected again, because we fetched shadowmap information beyond the [0,1]
limit of a texture.

The way to solve this one is to specify what openGL should sample in such case. This can be done with the following lines:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ) and glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ).
Behind the camera:
But there is still one artefact, visible in the upper right:

This is when we try to sample value behind the light's view frustrum. This is solved via the shader line:
if (ShadowCoord.w > 0.0) .

The Windows XP/Vista special
Unfortunatly, Microsoft decided not to support openGL extension beyond v1.1 . As a result, we need to retrieve the location of the functions needed for FBO and GLSL when the program start up. This is definitely an ugly piece of code and I recommend to use GLEW instead, it is done manually here in order to provide a single compilable file.
#ifdef _WIN32
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
// FrameBuffer (FBO) gen, bin and texturebind
PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT ;
PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT ;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT ;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT ;
void getOpenGLFunctionPointers(void)
{
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC) wglGetProcAddress("glGenFramebuffersEXT");
glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) wglGetProcAddress("glBindFramebufferEXT");
glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)wglGetProcAddress("glFramebufferTexture2DEXT");
glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)wglGetProcAddress("glCheckFramebufferStatusEXT");
}
#endif
Recommended books
Here are two books I recommend if you want to learn more about shadowMapping:
Add a comment
Comments (26)
Thanks in advance.
Guo
Whether you do it BEFORE or AFTER, it is the same:
Just to clarify:
1. Applying bias transform before perspective divide (i.e. in CLIP SPACE):
(x,y,z,w) * bias = (0.5*x + 0.5*w, ..., ..., w)
Then perspective divide = ( 0.5*x/w + 0.5, ..., ... )
2. Applying bias transform "after" perspective divide (i.e. in NDC SPACE):
(x,y,z,w) -> Perspective divide = (x/w, y/w, z/w)
Then apply bias transform = (0.5*x/w + 0.5, ..., ... )
startTranslate(0,8,-16);
glutSolidCube(4);
endTranslate();
startTranslate(0,4,-16);
glutSolidCube(4);
endTranslate();
startTranslate(4,4,-16);
glutSolidCube(4);
endTranslate();
startTranslate(4,8,-16);
glutSolidCube(4);
endTranslate();
Which is a 4x4 cube, you get massive shadowing artifacts while the light is spinning. The shadowing does not work for some reason when you have objects that close together. If there is a reason for this I would like to know, as I'm running into the same problem no matter what I do to try and correct it. Thank you for the concise tutorial though.
thank you for the tutorial very cool one, I am wondering in case of using shadow map with shader and you are drawing gemetric which already have shader let's say bump would you have to draw the scene 3 times one to capture the depth another to draw the shadow map and a third to draw the geometric with their bump shader too or the is another whay of doing it?
thank you.
Fortunately shadows with VSM and in bumpmapping demo works.
Added .xy to texture2D(ShadowMap,shadowCoordinateWdivide).z -> texture2D(ShadowMap,shadowCoordinateWdivide.xy).z
And shadows with PCF, I changed shadow2DProj(...).w; to shadow2DProj(...).x; (and y & z works too) to make shadows visible.
As I said I will go into more detail on my blog but will post a comment for each of the attempted fixes in the comments of each entry (just like this). Please feel free to make additions or corrections in either location (with Fabien's blessing of course).
Anyway, on to ShadowMapping with GLSL.
I was initially using the Mesa driver with my ATI Technologies Inc Manhattan [Mobility Radeon HD 5400 Series] and was able to get the sample compiled and working using the following patch;
diff -uwr a/src/main.c c/src/main.c
--- a/src/main.c 2009-02-08 10:40:18.000000000 +1000
+++ c/src/main.c 2011-09-27 11:39:07.168034670 +1000
@@ -2,7 +2,13 @@
#include "windows.h"
#endif
+#ifdef __linux__
+#include "GL/glew.h"
+#include "GL/glut.h"
+#include "GL/gl.h"
+#else
#include "GLUT/glut.h"
+#endif
#ifdef _WIN32
#include "glext.h"
@@ -10,6 +16,8 @@
#include
+#include
+#include
#ifdef _WIN32
Copy the above content to a file patch1 in the directory where you have unpacked shadowmapping.zip and run the following command.
$ patch -p1 < patch1
You should then be able to cd to the src subdirectory and compile with this line.
$ gcc -g -o ShadowMapping_with_GLSL main.c -lGL -lm -lGLU -lglut -lGLEW
Of course you'll need freeglut-devel and glew-devel installed as well as a libGL and libGLU (at this point provided by mesa-libGL-devel and mesa-libGLU respectively).
That got me going but due to problems with other openGL examples I decided to switch to the Catalyst (fglrx) driver so....
I ended up with this patch.
diff -uwr a/src/main.c b/src/main.c
--- a/src/main.c 2009-02-08 10:40:18.000000000 +1000
+++ b/src/main.c 2011-09-27 11:02:07.601099602 +1000
@@ -2,7 +2,13 @@
#include "windows.h"
#endif
+#ifdef __linux__
+#include "GL/glx.h"
+#include "GL/glut.h"
+#include "GL/gl.h"
+#else
#include "GLUT/glut.h"
+#endif
#ifdef _WIN32
#include "glext.h"
@@ -10,6 +16,8 @@
#include
+#include
+#include
#ifdef _WIN32
and the following compiler line.
$ gcc -g -o ShadowMapping_with_GLSL main.c -I/usr/include/fglrx/ATI -lGL -lm -lGLU -lglut
Well, this ended up being way too long. I'll provide more details in my post and will post comments as I work on the others (if it's OK with you Fabien?)
Cheers,
Brad
One question: how can I add textures to this setup? I've tried redrawing the scene without any shaders and using glSetActiveTextureARB(GL_TEXTURE0), before and after the shadow pass, but no matter what I do I can't get the texture to show up. I've confirmed it's properly loaded.
I just can't figure out how to combine the shadow mapping with regular texturing!
Thanks for these, very informative. However I'd be interested to see this done with modern OpenGL (ES 2.0) without any fixed pipeline / matrix stack and using that shadow2DProj(), any updates? ;)
1/ It is very easy to port this tutorial to OpenGL ES 2.0, just create a matrix uniform to replace the texture Matrix.
2/ As for the shadow2DProj usage, look at the PCF tutorial, it is used there.
Great job btw with all this stuff, very informative for the rest of us.
For OpenGL ES 2.0: Don't use the shadow projection function and perform the comparaison manually.
Also I cant figure out what causes this annoying glitch in the shadows when the light source is almost on top of the objects (the shadow cast by the 2 blocks on the right): http://777-team.org/~matti/tmp/shadowbug.jpg
- Matti
The issue you are describing is called "Peter Panning", it is due to a depth offset set to high when rendering the shadow map
or two low when comparing the Z value in the shader.
As for the performance hit: It is even worse if you go with PCF !!
Fabien
As for the performance, I don't get how come rendering the shadow map is relatively cheap but using it really expensive :o This is with a 512x512 map.
i'm doing shadow map
it help me a lot