Shadow Mapping

From VRwiki
Jump to: navigation, search
Screenshot from the demo program

Shadow mapping is a technique to achieve real time shadows in a 3D environment. This technique consists in rendering (offscreen) the depth buffer of the scene as seen from the light; this way, if a fragment's distance from the light is greater than the corrisponding value in the offscreen depth buffer, it means that such fragment is shadowed.

It is possibile to adopt this technique in XVR, thanks to a combination of GLSL shaders and to the new support for FrameBuffer Objects (FBOs for short).

Shadow Mapping in XVR

Setting up a shadow buffer

First of all, an FBO for offscreen rendering must be created.

The new CVmFbo XVR class is documented here.

var fbo = CVmFbo(VR_FBO_SHADOW, width, height, format);

where

  • width,height are the dimensions of the offscreen framebuffer (you may want to use square and power-of-2 framebuffers to circumvent once-common limitations in GPU adapters)
  • format is an OpenGL format flag (such as GL_DEPTH_COMPONENT or GL_DEPTH_COMPONENT16)

The basic usage of an FBO is quite simple:

fbo.start();
SceneBegin();
/* drawing code here */
SceneEnd();
fbo.stop();

To use the texture associated with the FBO, you can simply call SetActiveTexture:

SetActiveTexture(fbo); // Sets the texture associated with the FBO 
 

Please note that, unlike PBuffers, FBOs are not different OpenGL contexts: this means that they share everything, including lights and display lists. In general, FBOs are much easier to use than PBuffers.

Setting up the texture matrix

The most tricky part of the code concerns some preliminary tasks that must be accomplished in order to make everything work correctly. In particular, one needs to set up a texture matrix that performs the correct transformation of texture coordinates from the light's point of view to the camera's.

This is accomplished as follows:

  • when rendering from the light's point of view, save the projection and modelview matrices.
light_mat_proj      = glGet(GL_PROJECTION_MATRIX);
light_mat_modelview = glGet(GL_MODELVIEW_MATRIX);
  • before calling the shader, the texture matrix has to be constructed:
glMatrixMode(GL_TEXTURE);
glLoadIdentity();  // clear things up
glTranslate(0.5, 0.5, 0.5); // we have to clamp values in the [0.0, 1.0] range, not [-1.0, 1.0]
glScale(0.5, 0.5, 0.5);
glMultMatrix(light_mat_proj); // now multiply by the matrices we have retrieved before
glMultMatrix(light_mat_modelview);
 
// finally, multiply by the *inverse* of the *current* modelview matrix
var s_mat_m = glGet(GL_MODELVIEW_MATRIX);
if (! InvertMatrix(&s_mat_m))
   Quit("Singular view matrix!"); // this should not happen!
 
// IMPORTANT! Starting from Engine 0150, the function InverMatrix has been deprecated
// and replaced by MatrixInverse_4 which has a different syntax. Please check the XVR
// Help.
 
glMultMatrix(s_mat_m);
glMatrixMode(GL_MODELVIEW);

Please note that *light_mat_proj* and *light_mat_modelview* are always the same if the light does not move; in that case you do not need to retrieve them at every frame.

A simple shadow mapping shader

The shader to achieve shadow mapping is quite simple in its most basic form. The vertex shader needs only to send the correct texture coordinates to the fragment shader, which, in turn, when using a VR_FBO_SHADOW FBO, needs only to access its Sampler2DShadow in order to gather the boolean shadowed/not shadowed information.

Here is a basic vertex shader:

[VERTEX SHADER]
varying vec4 shadowTexCoord;
void main (void)
{
   // vertex calculation
   gl_Position = ftransform();
   
   // shadow texture coordinates generation
   shadowTexCoord = gl_TextureMatrix[0] * gl_ModelViewMatrix * gl_Vertex;
}

Remember though, that when using a vertex shader you are giving up all usual OpenGL calculations; if you want them back, you have to restore them explicitly. A very useful resource in this respect is the Shader Generator.

The corrisponding fragment shader is as follows:

[FRAGMENT SHADER]
uniform sampler2DShadow shadowMap;
varying vec4 shadowTexCoord;
void main(void)
{
   gl_FragColor = shadow2DProj(shadowMap, shadowTexCoord).r * glColor;
}

This yields pitch black shadows; other effects can be easily obtained fiddling with this code.

Demo program

A demo tutorial program with commented source code is available.

  • you can see it here
  • you can download it here (685k)

See Also

External links

  • A short but clear tutorial about implementing shadow mapping with GLSL
  • Shader Generator, shows how to restore standard OpenGL functionalities in the shader programs