Deferred Shadow Maps [Fixed]

Started by
4 comments, last by Capoeirista 10 years, 8 months ago

Hi folks,

I'm running in to trouble trying to get my deferred light maps working for directional lights.

In my light map pass I render all of the geometry in the scene using a world/view/projection matrix based on the position and direction of the light source. The result I'm getting looks like what I'd expect it to be.

Trouble comes in (I'm assuming) the pixel shader when I'm try to convert the current world position in to light-screen-space. I'd really appreciate it if you could run your eyes over the logic...

So the setup for the light matrix that I pass in to the pixel shader's constant buffer :


// Calculate the world/view/projection matrix for the light
XMMATRIX world	= XMLoadFloat4x4( &aCamera->GetWorld() );
XMMATRIX view	= XMMatrixLookAtLH( XMLoadFloat3(&aLight->GetPosition()), XMLoadFloat3(&lightDirection), XMLoadFloat3(&XMFLOAT3(0.0f, 1.0f, 0.0f)) );
XMMATRIX projection = XMMatrixPerspectiveFovLH( XMConvertToRadians(30.0f), 1.0f, 1.0f, 1024.0f );
XMMATRIX lightWVP   = XMMatrixMultiply( XMMatrixMultiply(world, view), projection );

// Bind the matrix to the lighting buffer	
lightingBuffer->myLightViewProjection = XMMatrixTranspose( lightWVP );

Then in the shader I calculate the world space position (currentDepth is sampled from the depth g-buffer):


// Calculate the screen-space position
float4 currentPosition;
currentPosition.x = anInput.myTexCoords.x * 2.0f - 1.0f;
currentPosition.y = -(anInput.myTexCoords.y * 2.0f - 1.0f);
currentPosition.z = currentDepth;
currentPosition.w = 1.0f;

// Transform the screen space position in to world-space
currentPosition = mul( currentPosition, myInverseViewProjection );
currentPosition /= currentPosition.w;

And then I convert the current position in to light-space :


// multiply current position by light view projection to get light-screen space
float4 lightScreenPosition = mul( currentPosition, myLightViewProjection );

// convert position in to texture coordinates
float2 lightTextureCoords = 0.5f * (float2(lightScreenPosition.x, -lightScreenPosition.y) + 1);

// sample from light depth map
float lightDepth = ShadowMap.Sample( ShadowSampler, lightTextureCoords ).r;

And I guess if the lightDepth > currentDepth then I should be returning my ambient light for the current pixel?


if( lightDepth > currentDepth )
{
 return float4( 0.1f, 0.1f, 0.1f, 0.1f );
}

// otherwise continue on with the light map calculations

I'm obviously messing something up here, any ideas what?

Thanks for your help!

Advertisement

This is the scene I'm trying to render with shadows. The light (directional) is positioned at (0.0f, 0.0f, -150.0f), so the two pyramids at the front should be fully lit in the z-axis. The back two pyramids should be mostly in shadow:

XqH2k8O.png

This is the shadow map I'm generating from the light's position :

bXLBtTm.png

The shadow map looks mostly right (I mucked around with the histogram settings in PIX to get the contrast you see above), not sure why the top of the front pyramid is getting chopped off... but at least the relative depths look correct.

I seem to be just getting a value of 1.0f out of the shadow map buffer, because when I multiply the diffuse light by the depth that I'm reading out of the shadow map buffer (just to test things out), I'm getting the original scene.

So I'm guessing something is messed up with the way I'm converting world space position to light-screen space?

It thought it might be the byte alignment for my directional light shader's constant buffer... looking at it in PIX some of the values seem off ( (0.0f, 1.0f, 1.0f) instead of (1.0f, 1.0f, 1.0f) for the light colour), but I can read the ones that look wrong without any data errors. They also look fine when looking at the buffer in Intel GPA.

This is the structure of the constant buffer I'm passing in :


struct LightingConstants
{
	DirectX::XMFLOAT3		myLightDirection;
	ALIGN_16 DirectX::XMFLOAT3	myLightColour;
	ALIGN_16 DirectX::XMFLOAT3	myCameraPosition;
	DirectX::XMMATRIX		myInverseViewProjection;
	DirectX::XMMATRIX		myLightViewProjection;
};

Just to be on the safe side I changed the buffer to this :


struct LightingConstants
{
 DirectX::XMFLOAT3	myLightDirection;
 float			myPadding1;
 DirectX::XMFLOAT3	myLightColour;
 float			myPadding2;
 DirectX::XMFLOAT3	myCameraPosition;
 float			myPadding3;
 DirectX::XMMATRIX	myInverseViewProjection;
 DirectX::XMMATRIX	myLightViewProjection;
};

So the constant buffer looks correct in PIX, but I'm still messing up the shadows.

I've made a couple of improvements to the way my depth maps (from the light perspective) are made.

First off the render target I'm using is 1024x1024 instead of 1024x768, so the way I'm calculating the world/view/projection matrices for the light make much more sense :


XMMATRIX world = XMMatrixIdentity();
XMMATRIX view  = XMMatrixLookAtLH( XMLoadFloat3(&aLight->GetPosition()), XMLoadFloat3(&lightDirection), XMLoadFloat3(&XMFLOAT3(0.0f, 1.0f, 0.0f)) );
XMMATRIX projection = XMMatrixPerspectiveFovLH( (float)(XM_PI / 2.0f), 1.0f, 1.0f, 1024.0f );

In this instance the world matrix is always the identity matrix. The result of the light-depth pass looks like this :

dB2oULu.png

But I can't seem to get the shadow calculation correct in the deferred directional light pixel shader.

I render the directional light using a full screen quad, and then reconstruct the world space position of the current vertex like this :


// Get the depth value
float currentDepth = DepthMap.Sample( DepthSampler, anInput.myTexCoords ).r;
	
// Calculate the screen-space position
float4 currentPosition;
currentPosition.x = anInput.myTexCoords.x * 2.0f - 1.0f;
currentPosition.y = -(anInput.myTexCoords.y * 2.0f - 1.0f);
currentPosition.z = currentDepth;
currentPosition.w = 1.0f;
	
// Transform the screen space position in to world-space
currentPosition = mul( currentPosition, myInverseViewProjection );
currentPosition /= currentPosition.w;

Then I convert the world position in to light space :


// Transform the world space position in to light space
float4 lightScreenPosition = mul( currentPosition, myLightViewProjection );
lightScreenPosition       /= lightScreenPosition.w;

The light view projection matrix is the same as the on I use for the light-depth buffer. Next I calculate the texture coordinates to use when sampling from the light depth buffer :


// Calculate the light texture coordinates.
float2 lightTexCoords;
lightTexCoords.x =  lightScreenPosition.x / 2.0f + 0.5f;
lightTexCoords.y =  lightScreenPosition.y / 2.0f + 0.5f;

Then I compare the depth of the current position in light space with the depth sampled from the light-depth map. If the depth of the light-space position is greater then I assume the pixel is lit :


// Sample the shadow map depth value from the depth texture using the sampler at the projected texture coordinate location.
float depthValue = ShadowMap.Sample(ShadowSampler, lightTexCoords).r;

// Calculate the depth of the light.
float lightDepthValue = lightScreenPosition.z;
lightDepthValue       = lightDepthValue - 0.001f;

// Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
// If the light is in front of the object then light the pixel, if not then shadow this pixel since an object (occluder) is casting a shadow on it.
if( lightDepthValue < depthValue )
{
 return float4( 1.0f, 1.0f, 1.0f, 1.0f );
}

return float4( 0.0f, 0.0f, 0.0f, 1.0f );

But instead of getting pyramids at the front of the scene nicely lit, and the pyramids at the back mostly in shadow I get this horrible mess :

mvEiORJ.png

Any ideas what I'm doing wrong here - I'm guessing it has to be something to do with the light-space position calculations (I can render directional lights without shadow mapping with a very similar shader), but I'm banging my head against a brick wall trying to figure out what the problem is smile.png

Thanks!

Fixed!

I had a number of issues for those of you interested :

  • I was using a different format for my light-depth render target and my scene-depth render target
  • I was clearing my light-depth render target to black, not white
  • I wasn't flipping the y-axis coordinate in my light-depth texture lookup

This topic is closed to new replies.

Advertisement