Sign in to follow this  

Perspective Shadow Maps - I'm doing something wrong here...

Recommended Posts

So I've been working on a program that tests out a slew of directional shadow mapping techniques. I have uniform working great, and now I'm trying Perspective. But it's not quite working. There's something afoot, but I'm not rightly sure what it is. I've searched the forums for various things, but I haven't found any specifics that have fixed it. So using that resource as well as the 6th section on this page ( which is what I'll be referring to as the 'example code' ), I've been trying to get my thing to work, to little avail. I've gotten close, but no cigar. There's two locations where there could be a problem: when I'm generating the transform matrix for shadow mapping or when I'm drawing to the shadow map. I'm using Direct3D, so I have PiX at my disposal. I know the problem isn't in the step where I render my geometry using the shadow map since I can open up and look at the shadow map, and it doesn't look right ( most of the time it's just blank, with thin strips of something or other, or it's got something on it but it's still not working. ) Generating the Transformation Matrix The general algorithm to generate the matrix, as I understand it, is as follows. I'll ignore the case if the light is perpendicular to the camera direction for now. Numbers in parenthesis are points of potential confusion which I'll mention afterwards:
  • Transform the light direction into a position in post perspective space by running it through the Camera's ViewProj matrix. Then divide the position by it's W to get a not-W-messed-up position in post-perspective space. (1)
  • Push the light position to the Infinity Plane, which is the plane Z = ( Far+Near ) / ( Far-Near ). Make note in doing this that you're pushing from 0,0,0.5, and not 0,0,0. (2)
  • If the light is coming from behind the camera, set a flag so that in the shadow map generation and shadow map using, I write and light on Z fails.
  • Create a View Matrix looking at 0,0,0.5, with 0,1,0 as the Up.
  • Using the 8 points of the post-perspective frustum "cube" ( 1/-1, 1/-1, 1/0 ), use the distance of each from the light position to find a near and far distance that envelops the cube relative to the light position (3)
  • Transform the previously mentioned 8 points with the Shadow View Matrix. Hold two copies of them, one with X = 0.0 and the other with Y = 0.0, then normalize. Since a dot product with 0,0,1, the direction of light in this Shadow View is just the z component, I use the minimum z components of each of the 8 to find the angles for the proj matrix (4)
  • Construct a Projection matrix using the near/far/XAngle/YAngle values.
  • Acquire the shadow ViewProj matrix.
(1) Otherwise, I have this weird ( X*W, Y*W, Z*W, W ) vector that doesn't - as least I imagine - represent any useful position in post perspective space. In the example code he doesn't really do this. This is also a source of confusion because... (2) In the sample implementation I looked at, this isn't really done. He calculates the needed Z value, but doesn't use it. He just sort of treats it like it's already there. I've stepped through my code and my resulting light position definitely isn't on the infinity plane through efforts of the ViewProj matrix alone without some sort of pushing on my part. As I was writing this I noted that I need to push it from 0,0,0.5 and not the origin, but fixing this didn't fix the problem. (3) This is something I added myself because it makes sense. Clearly there is more you need to do to make sure things behind the chosen near plane cast shadows on the things in front of it, but that can be fixed later... right? (4) This diagram shows what I'm doing. Note that I'm doing this in two different planes for the X angle and Y angle: The black line is the Z axis in Shadow Shadow Map space. The blue dot is the origin of this system and the light position, the purple lines are the near and far planes, and the red line has an angle with the Z axis that I'm trying to acquire, by using the arccos of the z component of it's normalized direction from the origin. My actual code I'll put into a source code box here. Maybe I've made a typo somewhere or something? There's a bunch of commented out stuff for things I've tried.
void SceneControl::UpdatePerspective()
	lx::Vector4 points[8];

	// Transform each bound point into Post-Perspective Space
	// ======================================================

	for( int x = 0; x < 8; ++x )
		points[x]	= m_mBoundPoints[x] * m_mViewProjMatrix;
		points[x]	*= 1.0f / points[x].w;

	// Find the light position in Post-Perspective Space
	// =================================================

	lx::Vector4 csLightDir	= m_mViewMatrix * m_vDirLight;
	lx::Vector4 ppLightPos	= m_mProjMatrix * csLightDir;
	ppLightPos /= ppLightPos.w;
	ppLightPos.w = 1.0f;

	if( abs( csLightDir.z ) < lx::EPSILON )		/* The light is perpendicular to the Camera. */	
		// snip
	else						/* The light is not perpendicular to the Camera. */
		// Push the light position to the Infinity Plane
		// =============================================

		float targetZ			= ( m_fFarClip + m_fNearClip ) / ( m_fFarClip - m_fNearClip );
		lx::Vector3 CubeCenter( 0.0f, 0.0f, 0.5f );

		lx::Vector3 pushVec		= ( ppLightPos - CubeCenter );
		pushVec				*= ( targetZ - CubeCenter.z ) / pushVec.z;

		ppLightPos			= CubeCenter + pushVec;

		//ppLightPos			*= ( targetZ / ppLightPos.z );
		//ppLightPos.w			= 1.0f;

		// Set if the light source needs to be inverted - IE the sun is behind the camera.
		// ===============================================================================

		if( csLightDir.z > 0.0f )
			m_bPerspectiveInverseSM = true;
			m_bPerspectiveInverseSM = false;

		// Get the Shadow Mapping View Matrix
		// ==================================

		lx::Matrix PSMViewMatrix;

		lx::Vector3 ppLightDir	= ( lx::Vector3( 0.0f, 0.0f, 0.5f ) - ppLightPos ).Normalize();
		lx::Vector3 ppLightUp( 0.0f, 1.0f, 0.0f );		// Do I want to do this or something more complicated...
		lx::Vector3 xAxis, yAxis;
		xAxis.Cross( ppLightUp, ppLightDir ).Normalize();
		yAxis.Cross( ppLightDir, xAxis ).Normalize();

		PSMViewMatrix._11 = xAxis.x;
		PSMViewMatrix._21 = xAxis.y;
		PSMViewMatrix._31 = xAxis.z;
		PSMViewMatrix._41 = - ( xAxis * ppLightPos );

		PSMViewMatrix._12 = yAxis.x;
		PSMViewMatrix._22 = yAxis.y;
		PSMViewMatrix._32 = yAxis.z;
		PSMViewMatrix._42 = - ( yAxis * ppLightPos );

		PSMViewMatrix._13 = ppLightDir.x;
		PSMViewMatrix._23 = ppLightDir.y;
		PSMViewMatrix._33 = ppLightDir.z;
		PSMViewMatrix._43 = - ( ppLightDir * ppLightPos );

		PSMViewMatrix._14 = 0.0f;
		PSMViewMatrix._24 = 0.0f;
		PSMViewMatrix._34 = 0.0f;
		PSMViewMatrix._44 = 1.0f;

		// Find out the near and far clip values for the shadow mapping perspective
		// ========================================================================

		/* Get the min and max using the point of the screen space unit cube. */

		float SMNearClip, SMFarClip;
		SMNearClip = SMFarClip = ( m_mPerspectiveUnitCubePoints[0] - ppLightPos ) * ppLightDir;

		for( int x = 1; x < 8; ++x )
			float dist = ( m_mPerspectiveUnitCubePoints[x] - ppLightPos ) * ppLightDir;
			if( dist < SMNearClip )
				SMNearClip = dist;
			if( dist > SMFarClip )
				SMFarClip = dist;

		/* Expand the near clip plane by the post perspective world bounds. Although this is
		   best done with the second shadow map approach ( as opposed to the virtual camera
		   pull back method, which enlarges space near the viewer making the shadow quality
		   worse ) that... might be a bit out of the scope of this demo. Thustly, I'll just
		   pull back the near clip plane to include as much of the world bounds as I can. 
		   This handles the case when there are objects outside of view that are casting 
		   shadows on the scene within the camera frustum. I'm basically going to do another
		   min distance thing here. But if I get a negative distance, that means the 
		   directional point is within the world... which means I guess I'll pull it as
		   close as I can... */

		/*for( int x = 0; x < 8; ++x )
			float dist = ( ppLightPos - points[x] ) * ppLightDir;
			if( dist <= 0.0f )
				SMNearClip	= min( SMNearClip, 0.01f );

			if( dist < SMNearClip )
				SMNearClip = dist;				

		// Find out the angles by which I'll be modulating the projection. Based on Unit Cube only.
		// ========================================================================================

		/* Transform the Unit Cube points into Shadow Map View Space so I can work with them... */

		lx::Vector3 horizontalCubePoints[8];
		lx::Vector3 verticalCubePoints[8];

		for( int x = 0; x < 8; ++x )
			horizontalCubePoints[x] = PSMViewMatrix * m_mPerspectiveUnitCubePoints[x];
			verticalCubePoints[x] = horizontalCubePoints[x];

			horizontalCubePoints[x].y = 0.0f;
			verticalCubePoints[x].x = 0.0f;


		float XCosAngle, YCosAngle;
		//XCosAngle = ( horizontalCubePoints[0] - ppLightPos ).Normalize() * ppLightDir;
		//YCosAngle = ( verticalCubePoints[0] - ppLightPos ).Normalize() * ppLightDir;

		XCosAngle = horizontalCubePoints[0].z;
		YCosAngle = verticalCubePoints[0].z;

		for( int x = 1; x < 8; ++x )
			if( horizontalCubePoints[x].z < XCosAngle )
				XCosAngle = horizontalCubePoints[x].z;
			if( verticalCubePoints[x].z < YCosAngle )
				YCosAngle = verticalCubePoints[x].z;

		//float dist = ( ppLightPos - lx::Vector3( 0.0f, 0.0f, 0.5f ) ).GetLength();
		//XCosAngle = YCosAngle = atan( 1.0f / dist );

		// Construct the Shadow Mapping Projection Matrix
		// ==============================================

		lx::Matrix PSMProjMatrix;

		float YScale				= 2.0f / ( /*tan( YCosAngle );*/ tan( acos(YCosAngle) ) );
		float XScale				= 2.0f / ( /*tan( XCosAngle );*/ tan( acos(XCosAngle) ) );
		float FarOverDiff			= SMFarClip / ( SMFarClip - SMNearClip );
		float NegNearFarOverDiff		= -SMNearClip * FarOverDiff;

		PSMProjMatrix._11			= XScale;
		PSMProjMatrix._21			= 0.0f;
		PSMProjMatrix._31			= 0.0f;
		PSMProjMatrix._41			= 0.0f;

		PSMProjMatrix._12			= 0.0f;
		PSMProjMatrix._22			= YScale;
		PSMProjMatrix._32			= 0.0f;
		PSMProjMatrix._42			= 0.0f;

		PSMProjMatrix._13			= 0.0f;
		PSMProjMatrix._23			= 0.0f;
		PSMProjMatrix._33			= FarOverDiff;
		PSMProjMatrix._43			= NegNearFarOverDiff;

		PSMProjMatrix._14			= 0.0f;
		PSMProjMatrix._24			= 0.0f;
		PSMProjMatrix._34			= 1.0f;
		PSMProjMatrix._44			= 1.0f;

		// Construct the Shadow Mapping Matrix
		// ===================================

		m_mPSMShadowMatrix = ( PSMViewMatrix * PSMProjMatrix ).Transpose();

One thing about the code is that I'm using 1.0 for the 44 slot of the Proj matrix. This isn't how you generally do a projection matrix, but it helps it look closer to how it should for some reason. I'm also using my own LX math library, as you might read, instead of D3DX. Generating the Shadow Map My vertex shader code that generates the shadow map looks like this:

; c0					Helper Vector ( 0.5, 0.0, 4.0, 1.0 )
; c1-c4					WorldViewProj Matrix
; c5-c8					Shadow Matrix

dcl_position0 v0

m4x4 r0, v0, c1			; Transform the position into world view proj coordinates

;rcp r1.w, r0.w			; Acquire 1/W
;mul r2.xyzw, r0.xyzw, r1.w	; Get X/W, Y/W, Z/W, 1
;m4x4 r0, r2, c5		; Transform the post-perspective position into shadow map coordinates
;mov oPos, r0			; Output those coordinates
;rcp r0.w, r0.w			; Acquire 1/W
;mul, r0.z, r0.w	; Get Depth as Z/W.

m4x4 r1, r0, c5			; Transform the post-perspective position into shadow map coordinates
mov oPos, r1			; Output those coordinates
rcp r0.w, r1.w			; Acquire 1/W
mul, r0.z, r0.w		; Get Depth as Z/W.

So I have all that commented out stuff in the middle... it makes sense to me that I should take the screen space position, transform it into a proper post-perspective position, and then run it through the shadow's ViewProj matrix, but I've been reading otherwise. Is this the case? Or is it some potential avenue of confusion. The Matricies are properly transposed before being put into the shader constants. So... any ideas why this isn't working? If you already know how this should be properly implemented, perhaps you might know where I'm going wrong. Finally, some screenshots of what I am getting: What it does 1 What it should do 1 ( using my uniform implementation ) What it does 2 What it should do 2 What the shadow map looks like, in PiX [Edited by - Kimani on January 29, 2008 1:15:24 PM]

Share this post

Link to post
Share on other sites
Bump for help.

If you know anything about Perspective Shadow Maps, post something here. I know that people here have implemented them before. Some sort of details about the algorithm in general ( avoiding generality - I mean details ) would be great.

I'm spending hours and hours a day just staring at these broken shadows in frustration. Please?

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this