Shadow mapping in GLSL

I am trying to implement the tutorial in <a href=“OpenGL Shadow Mapping Tutorial - Paul's Projects”>here</a> in GLSL.

It says that during texture generation the texture matrix is multiplied by the inverse of camera view matrix, so I do as below before the final pass.


	glGetFloatv( GL_MODELVIEW_MATRIX, reinterpret_cast<float*>( &cameraMatrix ) );

	Mat44 textureMatrix = ( biasMatrix_ * ( lightProjMatrix_ * ( lightViewMatrix_ * cameraMatrix.inverse() ) ) );

	glActiveTexture( GL_TEXTURE1 );
	glMatrixMode( GL_TEXTURE );
	glLoadMatrixf( reinterpret_cast<float*>( &textureMatrix ) );
	glMatrixMode( GL_MODELVIEW );
	glActiveTextureARB( GL_TEXTURE0 );

	_drawScene( RG_ALL );

And here is my shader:


VERTEX

void main()
{
	gl_TexCoord[0] = gl_MultiTexCoord0;
	gl_TexCoord[1] = gl_TextureMatrix[1] * gl_ModelViewMatrix * gl_Vertex;

	gl_Position = ftransform();
}

FRAG

uniform sampler2D   	texture0;
uniform sampler2DShadow	depthMap;

void main( void )
{
	float shadow = shadow2DProj( depthMap, gl_TexCoord[1] ).r;
    vec4 base = texture2D( texture0, gl_TexCoord[0] );
    shadow = shadow != 1.0 ? 0.35 : 0.95;
	gl_FragColor = vec4( base.rgb * shadow, 1.0 );
}

I get nothing but artifacts and mess that changes as I move the camera. Other than that I get nothing close to a shadow at all, shouldn’t the inverse view matrix cancel out with the view matrix of gl_ModelViewMatrix? If so why would the output change as I update the camera? What do you think is wrong?

Try this.

Here is a sample code I wrote:

It’s ANSI C, GLUT, GLSL based.

http://fabiensanglard.net/shadowmapping/index.php

It’s raw shadowmap for beginner, there is no PCF or VSM example (it’s something I’m working on)

And by the way: You don’t need to multiply by the inverse camera transform. Just transform your vertex directly in light camera space.

Thanks. It did help a lot, at least on the shader side. I was using shader2DProj before, and I am not quite sure why I couldn’t get it to work but I will study it thoroughly once I get rid of the artifacts.

So far, I did get close, but I still can’t figure out why the artifact in the image below could occur. Do you have any ideas?

hm…if you are talking about the top of the turret being shadowed, it’s probably a problem of bias.

Do you cull the frontfacing polygons during shadowmap rendition ?

Yes I do that. But I don’t see why it would be a bias problem as that wrongly shadowed area faces the light completely. Here is the scene from light’s perspective:

I did try to play around with some offset values using glPolygonOffset() but it seems that the correct and uncorrect shadow is connected tightly together, as they come out and disappear at the same values:

Here is the rendering code if anybody cares to look at it.


/* File: Renderer.cpp | Author: Dogan Demir | 2008 */

#include "Renderer.h"
#include "Polygon.h"
#include "SystemManager.h"

//Biggest power of two texture with a 800x600 window is 512x512
#define DEPTH_TEXTURE_WIDTH  512
#define DEPTH_TEXTURE_HEIGHT 512
#define SCENE_SIZE 40.f

Renderer::Renderer( EntityManager* entityManager ) : entityManager_(entityManager)
{
	activeShader_ = 0;
}

bool
Renderer::initialize()
{
	glEnable( GL_TEXTURE_2D );

	glEnable( GL_DEPTH_TEST );
	glEnable( GL_CULL_FACE );
	glCullFace( GL_BACK );
	glClearColor( 0.0, 0.0, 0.0, 1.f );

	_initializeApplicationSpecific();

	return true;
}

void
Renderer::_initializeApplicationSpecific()
{
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	lightPosition_ = Vec4D( 10.f, 50.0f, 40.f );
	lightTarget_ = Vec4D( 0.2f, 0.3f, 0.4f );

	//Calculate light view and projection matrices
	glPushMatrix();

	//Light matrices
	glLoadIdentity();
	gluPerspective( 45.0f, 1.0f, 1.0f, 100.0f );
	glGetFloatv( GL_MODELVIEW_MATRIX, reinterpret_cast<float*>( &lightProjMatrix_ ) );

	glLoadIdentity();
	gluLookAt( lightPosition_.x, lightPosition_.y, lightPosition_.z, lightTarget_.x, lightTarget_.y, lightTarget_.z, 0.f, 1.f, 0.f );
	glGetFloatv( GL_MODELVIEW_MATRIX, reinterpret_cast<float*>( &lightViewMatrix_ ) );

	glPopMatrix();

	glGenTextures( 1, &depthTexture_ );
	glBindTexture( GL_TEXTURE_2D, depthTexture_ );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, DEPTH_TEXTURE_WIDTH, DEPTH_TEXTURE_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );

	glClearDepth( 1.0f );
	glDepthFunc( GL_LEQUAL );

	biasMatrix_ = Mat44( Vec4D( 0.5, 0,   0,   0.5 ),
						 Vec4D( 0.0, 0.5, 0,   0.5 ),
						 Vec4D( 0,   0,   0.5, 0.5 ),
						 Vec4D( 0,   0,   0,   1 ) );
}

void 
Renderer::destroy()
{
	glDeleteTextures( 1, &depthTexture_ );
}

void 
Renderer::draw()
{
	glClear( GL_DEPTH_BUFFER_BIT );

	_drawToDepthTexture();

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	glActiveTextureARB( GL_TEXTURE1 );
	glMatrixMode( GL_TEXTURE );
	glLoadMatrixf( reinterpret_cast<float*>( &biasMatrix_ ) );
	glMultMatrixf( reinterpret_cast<float*>( &lightProjMatrix_ ) );
	glMultMatrixf( reinterpret_cast<float*>( &lightViewMatrix_ ) );
	glMatrixMode( GL_MODELVIEW );
	glActiveTextureARB( GL_TEXTURE0 );

	_drawScene( RG_ALL );
	//_drawDepthMap();

	glutSwapBuffers();
	glutPostRedisplay();
}

void 
Renderer::_drawToDepthTexture()
{
	glViewport( 0, 0, DEPTH_TEXTURE_WIDTH, DEPTH_TEXTURE_HEIGHT );

	glColorMask( 0, 0, 0, 0 );
	//glEnable( GL_POLYGON_OFFSET_FILL );
    //glPolygonOffset( 100.0f, 555.0f );
	glCullFace( GL_FRONT );

	glMatrixMode( GL_PROJECTION_MATRIX );
	glPushMatrix();
	glLoadMatrixf( reinterpret_cast<float*>( &lightProjMatrix_ ) );

	glMatrixMode( GL_MODELVIEW_MATRIX );
	glPushMatrix();
	glLoadMatrixf( reinterpret_cast<float*>( &lightViewMatrix_ ) );

	_drawScene( RG_ALL );

	glBindTexture( GL_TEXTURE_2D, depthTexture_ );
	glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, DEPTH_TEXTURE_WIDTH, DEPTH_TEXTURE_HEIGHT );

	glMatrixMode( GL_PROJECTION_MATRIX );
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW_MATRIX );
	glPopMatrix();

	glCullFace( GL_BACK );
	//glDisable( GL_POLYGON_OFFSET_FILL );
	glColorMask( 1, 1, 1, 1 );

	Vec2D winSize = SystemManager::getInstance().getGlutManager()->getWindowSize();
	glViewport( 0, 0, winSize.x, winSize.y );
}

void 
Renderer::_drawScene( RENDER_GROUP renderGroup )
{
	Vector< Vector<Entity*> > renderQueue = entityManager_->getRenderQueue();
	Vector< Vector<Entity*> >::iterator iterQ;
	Vector<Entity*>::iterator iterE;

	//All queues...
	for( iterQ = renderQueue.begin(); iterQ != renderQueue.end(); iterQ++ )
	{
		//All entities...
		for( iterE = iterQ->begin(); iterE != iterQ->end(); iterE++ )
		{
			if( (*iterE)->isVisible() )
			{
				if( renderGroup == RG_ALL )
				{
					_drawEntity( *iterE );
				}
			}
		}
	}
}

void
Renderer::_drawEntity( Entity* entity )
{
	//Apply transformation
	glPushMatrix();
	glMultMatrixf( reinterpret_cast<float*>( entity->getTransform() ) );

	glActiveTexture( GL_TEXTURE1 );
	glMatrixMode( GL_TEXTURE );
	glPushMatrix();
	glMultMatrixf( reinterpret_cast<float*>( entity->getTransform() ) );
	glMatrixMode( GL_MODELVIEW );

	//For each mesh...
	Mesh* mesh = entity->getMesh();
	Vector<Polygon>* polygons = mesh->getPolygonList();
	Vector<Vertex>*  vertices = mesh->getVertexList();

	//Apply shader
	uInt shader = entity->getShader();
	if( shader != NO_SHADER )
	{
		//Don't bother calling OpenGL if we're gonna use the previous shader
		if( shader != activeShader_ )
		{
			glUseProgram( shader );
			activeShader_ = shader;
		}

		Map<String, ParameterInfo> parameters = entity->getShaderParameters();
		Map<String, ParameterInfo>::iterator iter;

		float values[3];

		for( iter = parameters.begin(); iter != parameters.end(); iter++ )
		{
			switch( iter->second.type )
			{
			case PT_3FV:
				{
				values[0] = iter->second.value[0]; 
				values[1] = iter->second.value[1]; 
				values[2] = iter->second.value[2];

				glUniform3fv( glGetUniformLocation( shader, 
													iter->first.c_str() ), 
													1, 
													values );
				}
				break;
			case PT_F:
				{
				values[0] = iter->second.value[0];
				glUniform1fv( glGetUniformLocation( shader,
													iter->first.c_str() ),
													1,
													values );
				}
				break;
			}
		}

		//Depth map hack
		int dUniformLocation = glGetUniformLocation( shader, "depthMap" );
		glActiveTexture( GL_TEXTURE1 );
		glBindTexture( GL_TEXTURE_2D, depthTexture_ );
		glUniform1i( dUniformLocation, 1 );
	}
	else
	{
		if( activeShader_ != 0 )
		{
			activeShader_ = 0;
			glUseProgram(0);
		}
	}

	//Apply texture
	Vector<uInt>* textureList = entity->getTextureList();
	uInt texture;
	String tString;
	for( uInt t = 0; t < textureList->size(); t++ )
	{
		texture = (*textureList)[t];
		if( texture != NO_TEXTURE )
		{
			glActiveTexture( GL_TEXTURE0 );
			glBindTexture( GL_TEXTURE_2D, texture );

			if( shader != NO_SHADER )
			{
				char tChar[] = { 'a', '\0' };
				_itoa_s( t, tChar, 2, 10 );
				tString += "texture";
				tString += tChar;
				int dUniformLocation = glGetUniformLocation( shader, tString.c_str() );
				glUniform1i( dUniformLocation, 0 );
				tString.clear();
			}
		}
	}

	//For each polygon of the mesh...
	Vector<int>* indices;
	Vector<Vec2D>* texCoord;
	uInt polyCount = polygons->size();
	uInt indxCount;
	Vertex v;
	Polygon polygon;
	for( uInt j = 0; j < polyCount; j++ )
	{
		polygon	  = (*polygons)[j];
		indices	  = polygon.getIndices();
		indxCount = indices->size();
		texCoord  = polygon.getTexCoord();

		glBegin( GL_TRIANGLES );

		//For each vertex in each polygon of the mesh...
		for( uInt k = 0; k < indxCount; k++ )
		{
			v = (*vertices)[(*indices)[k]];

			glColor3f( v.color.x, v.color.y, v.color.z );
			if( texCoord->size() )
			{
				Vec2D vTex = (*texCoord)[k];
				glTexCoord2f( vTex.x, vTex.y );
			}
			else
			{
				glTexCoord2f( v.texCoord.x, v.texCoord.y );
			}
			glNormal3f( v.normal.x, v.normal.y, v.normal.z );
			glVertex3f( v.position.x, v.position.y, v.position.z );
		}
		glEnd();
	}
	glPopMatrix();
	glActiveTexture( GL_TEXTURE1 );
	glMatrixMode( GL_TEXTURE );
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW );
	glActiveTextureARB( GL_TEXTURE0 );
}

void
Renderer::_drawDepthMap()
{
	Vec2D winSize = SystemManager::getInstance().getGlutManager()->getWindowSize();

	//We will go from perspective to orthogonal projection to align the quad to the screen
	glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D( 0, winSize.x, 0, winSize.y );
	
	activeShader_ = 0;
	glUseProgram(0);

	//Clear any previous model and view transformations
	glMatrixMode( GL_MODELVIEW );
	glPushMatrix();
	glLoadIdentity();
    glDisable( GL_DEPTH_TEST );

	//Set additive blending
	glBindTexture( GL_TEXTURE_2D, depthTexture_ );

	//Draw the quad using 'screen' coordinates - resulting from orthogonal projection
	glBegin( GL_QUADS );
	glTexCoord2f(  0,  0 );	glVertex3f(  0,  0, 0 );
	glTexCoord2f(  1,  0 );	glVertex3f(  winSize.x, 0, 0 );
	glTexCoord2f(  1,  1 );	glVertex3f(  winSize.x, winSize.y, 0 );
	glTexCoord2f(  0,  1 );	glVertex3f(  0,  winSize.y, 0 );
	glEnd();

	//Go back to perspective projection
	glEnable( GL_DEPTH_TEST );
	glMatrixMode( GL_PROJECTION );
	glPopMatrix();
	glMatrixMode( GL_MODELVIEW );
	glPopMatrix();
}

One possible explanation:

Given the position of your light and where it is looking:

sqrt((10 * 10) + (50 * 50) + (40 * 40)) = 64.807407

And you setup you gluPerspective near and far as 1 and 100. This mean you lose a LOT of precision, try to place your objects closer in the light camera space with something like:

gluPerspective( 45.0f, 1.0f, 20.0f, 1000.0f );

Second issue:

You use a shadow map with different ratio than your FBO, I suggest to use a non-power of two to match the ratio, get the shadow to work and then work on the backward compatibility.

Thanks a lot for the help.

Doesn’t shorter ( far - near ) value mean more depth precision? I eventually got rid of the artifacts by using (0.98, 1000) but I don’t understand how.

And I also get this interesting distortion in the shadow. I was thinking it could have something to do with field of view, and I did experiment, less field of view shrinks down the shadow, but by just playing around with values I can’t match up to what it’s supposed to be at all. Any ideas?

Light perspective:

Ah I forgot to update the aspect ratio of the light frustum. Thanks a lot for all the help nicolasbol.

Dogdemir,

There is usually two issues: Pure precision and also location of your object in your viewing volume, you were likely affected only by the latter.

  1. Pure precision

According to the “OpenGL Reference Manual”, log2(zFar/zNear) bits of precision are lost, the closer zNear is to zero, the most precision you lose. However the change you made did not change the ratio.

  1. Location of your objects in your viewing volume.

There is more depth precision in the front of the viewing volume.
Refer to this page for complete explanations:

http://www.opengl.org/resources/faq/technical/depthbuffer.htm

In a nutshell, it’s because of the W divide used after the perspective projection, to move in unit cube coordinate.
By setting the zFar to 1000 instead of 100, you’ve moved your object closer to the POV in the area where you have more z precision.

Just a guess here, without math proof, but consider that objects located more than 50% away in your viewing volume lose 50% of precision (so with a 24 bits depth buffer, you rely on a 12 bits precision which is poor)

It’s particularly annoying when you render landscape and that’s what Cascade Shadow Mapping is for.