Shadow Mapping Math

Started by
1 comment, last by ZMaster 18 years, 10 months ago
Hi, I really need some help on implementing shadow mapping. I thought it would be easier :-). I create a PBuffer, render my scene from the light's point of view into the buffer and copy it into a depth texture (GL_DEPTH_COMPONENT). In my second rendering pass I set up projective texture mapping on one texture layer, bind the depth texture to it and compare Z-Values using GL_ARB_shadow. That works actually quite fine, I mean shadows are drawn that way. BUT: My problem is acutally the "rendering from the light's point of view". My light is directional, so it actually has no position. The light simulates sunlight on a relativly large outdoor terrain. For testing reasons, I set up the lightview as a normal perspective-view camera. You can see what happens if you look at the following screenshot: Screenshot 02 What I would like to do is: Take the dimensions of the camera's viewing frustum and calculate the position and perspective of the light, so that it always faces into the same direction, but still always has the camera's view frustum within it's own. I hope I could explain that precisly enough ;-) If you look at another screenshot: Screenshot 01 you can also see, that objects further away from the camera, seem to have some kind of depth precision problem. If I render the depth map onto a textured quad, I can just see a white primitive, no shades at all, so maybe z-values are very close to each other. But this might also solve with the correct light view. Here's the shadow rendering code, if you want to have a look on it (CalcLightView() would be the function which sets up the lightview, but does only set up something like a light-camera for now.)
/////////////   Constructor/Destructor   ////////////////////////
SunLight::SunLight()
{
	vDir = Vector3f(0.125f, -0.75f, -0.125f);
	vDir.normalize();
	//lightView.DisableMatrixTransform();

	SetAmbientColor(0.4f, 0.4f, 0.4f, 1.0f);
	SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
	SetShininess(128.0f);

	bIsEnabled = true;

	nShadowMapSize = 512;
}

SunLight::SunLight(Vector3f direction)
{
	vDir = direction;
	vDir.normalize();
	//lightView.DisableMatrixTransform();

	SetAmbientColor(0.4f, 0.4f, 0.4f, 1.0f);
	SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	SetSpecularColor(1.0f, 1.0f, 1.0f, 1.0f);
	SetShininess(128.0f);

	bIsEnabled = true;

	nShadowMapSize = 512;
}

SunLight::~SunLight()
{
	bIsEnabled = false;

	if(texDepthMap.GetID() > 0)
		texDepthMap.Free();

	DestroyPBuffer();
}


/////////////   Render the light source   ///////////////////////
void SunLight::RenderAll(void)
{
    if(bShadowMapping && eyeView)
	{
		CalcLightView();
		BeginDepthMapPass();
			//Render the tree
			for(int i=0; i < nNumOfChildren; i++)
			{
				oChildren->RenderAll();
			} 
		EndDepthMapPass();
		BeginShadowPass();
	}

	//Enable lighting
	if(bIsEnabled)
	{
		glEnable(GL_LIGHTING);
	
		float fDir[4] = { vDir.x, vDir.y, vDir.z, 0.0f };
		glLightfv(GL_LIGHT0, GL_POSITION, fDir);
		glLightfv(GL_LIGHT0, GL_AMBIENT, fAmbientColor);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, fDiffuseColor);
		glEnable(GL_LIGHT0);

		glMaterialfv(GL_FRONT, GL_SPECULAR, fSpecularColor);
		glMaterialf(GL_FRONT, GL_SHININESS, fShininess);
	}

	//Render the tree
	for(int i=0; i < nNumOfChildren; i++)
	{
		oChildren->RenderAll();
	}

	//Disable lighting
	if(bIsEnabled)
	{
		glDisable(GL_LIGHT0);
		glDisable(GL_LIGHTING);
	}

	if(bShadowMapping && eyeView)
	{
		EndShadowPass();
	}

}


/////////////   Initialize the shadow mapping  //////////////////
bool SunLight::CreatePBuffer(void)
{
	//Create a P-Buffer
	int nPBuffer[16] = { WGL_TEXTURE_FORMAT_ARB, 
						 WGL_TEXTURE_RGBA_ARB,
						 WGL_TEXTURE_TARGET_ARB,
						 WGL_TEXTURE_2D_ARB,
						 0 };

	hPBuffer = wglCreatePbufferARB(RenderManager::Instance()->GetTarget(),
								   RenderManager::Instance()->GetPixelFormatIndex(),
								   nShadowMapSize,
								   nShadowMapSize,
								   nPBuffer);

	hDCPBuffer = wglGetPbufferDCARB(hPBuffer);
	hRCPBuffer = RenderManager::Instance()->GetRenderingContext();

	if(!hPBuffer)
	{
		MESSAGE("WARNING: PBuffer could not be created -> no shadows!");
		return false;
	}
 
	return true;
}


void SunLight::DestroyPBuffer(void)
{
	//Destroy the P-Buffer
	if(hPBuffer)
	{
		wglReleasePbufferDCARB(hPBuffer, hDCPBuffer);
		wglDestroyPbufferARB(hPBuffer);
	}
	hPBuffer = NULL;

	//Release the device context
	if(hDCPBuffer && gHWND)
		ReleaseDC(gHWND, hDCPBuffer);
    hDCPBuffer = NULL;

	//Switch back to rendering into the frame buffer
	wglMakeCurrent(RenderManager::Instance()->GetTarget(),
				   RenderManager::Instance()->GetRenderingContext());
}


bool SunLight::InitShadowMapping(void)
{	
	//Check for the extensions
	if(!ExtensionManager::Instance()->IsSupported("GL_ARB_depth_texture"))
	{
		MESSAGE("WARNING: Hardware does not support shadows: lacking GL_ARB_depth_texture");
		bShadowMapping = false;
		return false;
	}

	if(!ExtensionManager::Instance()->IsSupported("GL_ARB_shadow"))
	{
		MESSAGE("WARNING: Hardware does not support shadows: lacking GL_ARB_shadow");
		bShadowMapping = false;
		return false;
	}

	if(!ExtensionManager::Instance()->IsSupported("WGL_ARB_pbuffer"))
	{
		MESSAGE("WARNING: Hardware does not support shadows: lacking WGL_ARB_pbuffer");
		bShadowMapping = false;
		return false;
	}


    //Create the depth map
	float fAmbientColor[4] = { 0.65f, 0.65f, 0.65f, 1.0f }; //Shadow ambient color
	bShadowMapping = texDepthMap.CreateShadowMap(nShadowMapSize, fAmbientColor);

    if(!bShadowMapping)
	{
		MESSAGE("WARNING: Shadow depth map could not be created. Shadows are disabled.");
		return false;
	}

	if(!CreatePBuffer())
	{
		texDepthMap.Free();
		return false;
	}

    return true;
}


void SunLight::CalcLightView(void)
{
	//Makes no sense, really. This is what I want to get working... --->
    Matrix4x4 lightViewMatrix;
	Vector3f *vFrustum = eyeView->GetFrustumHandle()->GetFrustumPoints();
	Vector3f vFrustumLS[8];
	memcpy(vFrustumLS, vFrustum, sizeof(Vector3f) * 8);
	Vector3f vFrustumLSMin, vFrustumLSMax;
    
	//Calculate the light view coordinate system
	Vector3f vLightX = vDir.cross(Vector3f(0.0f, 1.0f, 0.0f));
	Vector3f vLightY = vLightX.cross(vDir);
	vLightX.normalize();
	vLightY.normalize();
	lightViewMatrix(0, 0) = vLightX.x;  lightViewMatrix(0, 1) = vLightX.y;  lightViewMatrix(0, 2) = vLightX.z;
	lightViewMatrix(1, 0) = vLightY.x;  lightViewMatrix(1, 1) = vLightY.y;  lightViewMatrix(1, 2) = vLightY.z;
	lightViewMatrix(2, 0) = -vDir.x;	lightViewMatrix(2, 1) = -vDir.y;	lightViewMatrix(2, 2) = -vDir.z;

    //Position
	lightViewMatrix.Translate(-(eyeView->GetPosition()));

	//Set the light modelview matrix
	Matrix4x4 *lightModelView = lightView.GetModelviewMatrixHandle();
	//memcpy(lightModelView, &lightViewMatrix, sizeof(Matrix4x4));

   
	//Set up the light like a normal camera (for testing only)
	lightView.SetPerspective(90.0f, 1.0f, 1.0f, 1000.0f);
	lightView.SetViewport(0, 0, nShadowMapSize, nShadowMapSize);
	lightView.SetPosition(Vector3f(2500.0f, 400.0f, 3000.0f));
	lightView.LookAt(Vector3f(2688.0f, 0.0f, 2688.0f));
}


void SunLight::BeginDepthMapPass(void)
{   
    //State: Shadow pass
	State::Instance()->EnableShadowPass();

	//Render into the P-Buffer
	wglMakeCurrent(hDCPBuffer, hRCPBuffer);

	//Clear the depth buffer
	glClear(GL_DEPTH_BUFFER_BIT);

	//Set the light view
	eyeView->Disable();		//Disable the camera
	lightView.Enable();		//Enable the lightview "camera"
	glLoadIdentity();		//Make the modelview matrix the identity matrix
	lightView.Update(0.0f);	//Load the projection and modelview matrix of the lightview "camera"

	//Disable unnecessary states
	State::Instance()->SetColorMask(false, false, false, false);
	State::Instance()->LockColorMask();
	State::Instance()->DisableBlending();
	State::Instance()->LockBlending();
	State::Instance()->DisableTexturing();
	State::Instance()->LockTexturing();

}

void SunLight::EndDepthMapPass(void)
{
	//State: Render pass
	State::Instance()->DisableShadowPass();

	//Save the depth buffer to the depth map
	glBindTexture(GL_TEXTURE_2D, texDepthMap.GetID());
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, nShadowMapSize, nShadowMapSize);

	//Render into the frame buffer
	wglMakeCurrent(RenderManager::Instance()->GetTarget(),
				   RenderManager::Instance()->GetRenderingContext());

	//Enable states
    State::Instance()->UnlockColorMask();
	State::Instance()->SetColorMask(true, true, true, true);
	State::Instance()->UnlockBlending();
	State::Instance()->UnlockTexturing();
	State::Instance()->EnableTexturing();

	//Set the eye view
	lightView.Disable();	//Disable the lightview "camera"
	eyeView->Enable();		//Enable the camera
	glLoadIdentity();		//Make the modelview matrix the identity matrix
	eyeView->Update(0.0f);	//Load the projection and modelview matrix of the camera
	
}

void SunLight::BeginShadowPass(void)
{
	//Clear the depth buffer
	glClear(GL_DEPTH_BUFFER_BIT);

	const float x[4]	 = { 1.0f, 0.0f, 0.0f, 0.0f };
	const float y[4]	 = { 0.0f, 1.0f, 0.0f, 0.0f };
	const float z[4]	 = { 0.0f, 0.0f, 1.0f, 0.0f };
	const float w[4]	 = { 0.0f, 0.0f, 0.0f, 1.0f };
	const float bias[16] = { 0.5f, 0.0f, 0.0f, 0.0f,
							 0.0f, 0.5f, 0.0f, 0.0f,
							 0.0f, 0.0f, 0.5f, 0.0f,
							 0.5f, 0.5f, 0.5f, 1.0f };

	//Bind the depth map to texture unit 4
	texDepthMap.Bind(4);

	//Setup texture coordinate generation for projective texture mapping
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	glEnable(GL_TEXTURE_GEN_Q);

	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
	glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

	glTexGenfv(GL_S, GL_EYE_PLANE, x);
	glTexGenfv(GL_T, GL_EYE_PLANE, y);
	glTexGenfv(GL_R, GL_EYE_PLANE, z);
	glTexGenfv(GL_Q, GL_EYE_PLANE, w);

	glMatrixMode(GL_TEXTURE);
	 glLoadMatrixf(bias);
	 lightView.GetProjectionMatrixHandle()->Apply();
	 lightView.GetModelviewMatrixHandle()->Apply();
	
	glMatrixMode(GL_MODELVIEW);
	//Compare the depth values
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
}

void SunLight::EndShadowPass(void)
{
	//Unbind the depth map
	texDepthMap.Unbind();

	//Do not compare anything
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);
	//Disable texture coordinate generation
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	glDisable(GL_TEXTURE_GEN_Q);

	//Reset the texture matrix
	glMatrixMode(GL_TEXTURE);
	 glLoadIdentity();

	glMatrixMode(GL_MODELVIEW);

}


I hope someone can help me out with this... Thanks in advance.
Advertisement
Quote:BUT: My problem is acutally the "rendering from the light's point of view". My light is directional, so it actually has no position. The light simulates sunlight on a relativly large outdoor terrain.

choose a position eg aim the light at the piece of ground 100meters in front of the camera

Quote:you can also see, that objects further away from the camera, seem to have some kind of depth precision problem
i cant see what u mean, perhaps u mean zfighting (can be fixed somewhat with using polygonoffset)
also since u have a projection modelview matrix, each pixel in the shadowmap is gonna cover a larger screenarea thus will look worse. ways to fix this are one of the various methods of perspective shadowmapping (3/4 different methods, none are easy to do) or use multiple shadowmaps
Thanks for your ideas.

After days, I finally figured it out. Found some sources on the net, mainly DirectX but now I got it working with OpenGL in a similar way. Since the light position changes with the camera position and only the view frustum is on the depth map, I could squeeze a bit more precision out, compared to depth-mapping the whole terrain. A 2048x2048 shadow map looks quite good. Nevertheless, a disadvantage is, that since the virtual light position changes with the camera's view, you can see the shadow edges flickering whenever moving the camera. Having a low-res depth map even makes it worse.
But I have to live with this, maybe I can even change that a bit, didn't try yet.
This "z-fighting" problem, disappears when I move the far clipping plane of the camera further away from it. Having small light angles, makes the effect visible again, if I don't move the clipping plane away. I still think it's a precision or inacurracy problem between the depth values in the z-buffer and the depth map. However...

For everyone who's interested, here's the code:
void SunLight::CalcLightView(void){   	//Get the eye matrices	Matrix4x4 eyeProjection = *(eyeView->GetProjectionMatrixHandle());	Matrix4x4 eyeModelView  = *(eyeView->GetModelviewMatrixHandle());	//The lights modelview * projection matrix	Matrix4x4 lightModelViewProjection;	//Copy the frustum points to local memory	Vector3f  *vFrustumPtr = eyeView->GetFrustumHandle()->GetFrustumPoints();	Vector3f  vFrustum[8]; 	memcpy(vFrustum, vFrustumPtr, sizeof(Vector3f) * 8);	//Frustum AABB values	Vector3f  vMin, vMax;    	//Set the lights modelview and projection matrix to the identity matrix	lightModelView.LoadIdentity();	lightProjection.LoadIdentity();    //Set the shadow map viewport	glViewport(0, 0, nShadowMapSize, nShadowMapSize);	//Look in the unit cube into the direction of the light	lightModelView.LookAt(Vector3f(0.0f, 0.0f, 0.0f), -vDir, Vector3f(0.0f, 1.0f, 0.0f));	//Make the projection matrix the unit cube (Left handed coordinate system)	lightProjection.OrthoLH(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);	//Calculate the modelview * projection matrix	lightModelViewProjection = lightModelView * lightProjection;	//Transform the frustum coordinates from world space into the light space	for(int i=0; i < 8; i++) vFrustum = lightModelViewProjection * vFrustum;	//Obtain the min and max coordinate values for the AABB	vMin = vFrustum[0];	for(int i=0; i < 8; i++)	{		if(vFrustum.x < vMin.x) vMin.x = vFrustum.x;		if(vFrustum.y < vMin.y) vMin.y = vFrustum.y;		if(vFrustum.z < vMin.z) vMin.z = vFrustum.z;	}	vMax = vMin;	for(int i=0; i < 8; i++)	{		if(vFrustum.x > vMax.x) vMax.x = vFrustum.x;		if(vFrustum.y > vMax.y) vMax.y = vFrustum.y;		if(vFrustum.z > vMax.z) vMax.z = vFrustum.z;	}	//Now set up a new projection matrix that will just project the	//exact dimensions of the AABB of the current view frustum	//(left handed coordinate system)	lightProjection.OrthoLH(vMin.x, vMax.x, 							vMin.y, vMax.y,							vMin.z, vMax.z);}

This topic is closed to new replies.

Advertisement