Skip to main content
GameDev.net gamedev.net
🔒 Locked

[DX9] Help with Deferred Shadow Maps

Started by Steve_Segreto Nov 28, 2011 at 1:19 PM 4 replies 4.1k views
Original Post
Steve_Segreto
Steve_Segreto
I'm adapting MJP's Deferred Cascaded Shadow Map code from XNA to DX9.

I'm facing the following issues currently:

1. My shadows render but they do not occlude correctly based on depth, instead they are just plastered on top of the object and occlude the top of the object as well as the ground beneath. Inside the pixel shader for computing the shadow occlusion factor, i have verified that my gbuffer's linear depth comes in correctly and yields the expected CSM splits. I've also verified that the depth as seen by the light appears as expected. However the depth read from the shadow map looks suspicious because whilst it seems to come from the correct part of the shadow map, it is either 0 or 1 there are no in between gradients. I render my GBuffer depth in linear depth and also my shadowmap pixel shader outputs linear depth (though I have tried both ways with my shadowmap PS and neither worked any better) If I just hardcode my shadowmap pixel shader to output a value close to 0 like 0.1f it shows as pure black and if I output 0.0f it shows as pure white.

2. I'm fairly sure that I adapted the 4 split orthographic frustums correctly from MJP's code. Not only can I render them in world space and they look OK, but they also show up correctly when using the color output from the occlusion pixel shader. However, when I use these view/projection matrices to render my shadowcasters, the shadows appear to displace more and more as the main camera's view matrix rotates about. It almost seems like there is some sort of translation before rotation issue. Admittedly I did have trouble converting this part of the code from XNA RH to DX9 LH.

3. The cascade splits seem to disappear when the main camera's view angle is very close to parallel to the ground. If I have a very tall column in the middle of a flat ground plane, the column will cast a shadow across all 4 CSM split frustums. This looks fine if the column is viewed from "above", but if viewed from the ground and looking at a certain angle along the ground, the shadows flit in and out and sometimes are completely absent.

Please if anybody could give me some pointers or help me it would be appreciated. I can post whatever code/screenshots are required, but I didn't want to spam the forums with a huge code post just yet.
DJTN
DJTN
It sounds like you have multiple problems. I would double check the conversion of the right hand coordinate system to left hand. I would also check your image format where you store the shadow data as it sounds like you're having precision issues. I've never worked with MJP's CSM code but he does visit these forums from time to time.

kauna
kauna

... it is either 0 or 1 there are no in between gradients. I render my GBuffer depth in linear depth and also my shadowmap pixel shader outputs linear depth (though I have tried both ways with my shadowmap PS and neither worked any better) If I just hardcode my shadowmap pixel shader to output a value close to 0 like 0.1f it shows as pure black and if I output 0.0f it shows as pure white.


The very basic shadow mapping tests whether the current pixel is shadowed or not. So, in that case a pixel is either fully lit or fully in shadow (ie. white or back). The way you store your shadow map (linear space or z/w) doesn't affect the way the shadowing test works.

You need to apply a filter such as PCF (percentage closer filtering) in order to make your shadows to have smooth edges.

Cheers!
MJP
MJP
If you'd like to post some of the code, I can have a look and try to see where you went wrong.
Steve_Segreto
Steve_Segreto

If you'd like to post some of the code, I can have a look and try to see where you went wrong.



Thank you MJP!


Here is the header file: CShadowRender.h


typedef enum ShadowFilteringType
{
PCF2x2 = 0,
PCF3x3 = 1,
PCF5x5 = 2,
PCF7x7 = 3
} ShadowFilteringType;

const int NumSplits = 4;

class CShadowRenderer
{
public:
LPDIRECT3DSURFACE9 dsBuffer;
LPDIRECT3DSURFACE9 oldDS;
LPDIRECT3DTEXTURE9 shadowMap;
LPD3DXEFFECT shadowMapEffect;
LPDIRECT3DTEXTURE9 shadowOcclusion;
LPDIRECT3DTEXTURE9 disabledShadowOcclusion;

class CScreenQuad *fullScreenQuad;

D3DXVECTOR3 frustumCornersVS[8];
D3DXVECTOR3 frustumCornersWS[8];
D3DXVECTOR3 frustumCornersLS[8];
D3DXVECTOR3 farFrustumCornersVS[4];

D3DXVECTOR3 splitFrustumCornersVS[8];
D3DXVECTOR3 splitFrustumCornersWS[8];
D3DXVECTOR3 splitFrustumCornersLS[8];

class CCamera *lightCameras[NumSplits];
D3DXMATRIX lightViewProjectionMatrices[NumSplits];
D3DXVECTOR2 lightClipPlanes[NumSplits];
float splitDepths[NumSplits + 1];

bool enabled;
bool showCascadeSplits;
ShadowFilteringType filteringType;
D3DXHANDLE shadowOcclusionTechniques[4];

CShadowRenderer();
~CShadowRenderer();

void OnLostDevice();
void OnResetDevice();
void BuildEffect();

LPDIRECT3DTEXTURE9 Render(
CGrowableArray< struct _PVSEntry > *modelList,
LPDIRECT3DTEXTURE9 depthTexture,
DirLight *light,
class CCamera *mainCamera);

void CalculateFrustum(
int splitIndex,
DirLight *light,
class CCamera *mainCamera,
class CCamera *lightCamera,
float minZ,
float maxZ);

void RenderShadowMap(
CGrowableArray< struct _PVSEntry > *modelList,
int splitIndex);

void RenderShadowOcclusion(
class CCamera *mainCamera,
LPDIRECT3DTEXTURE9 depthTexture);

void SetRenderTarget(
int iRenderTargetIdx,
LPDIRECT3DTEXTURE9 pd3dRenderTargetTexture);

void ClearTexture(
LPDIRECT3DTEXTURE9 pd3dTexture,
D3DCOLOR xColor);
};

class CScreenQuad
{
public:
CScreenQuad ( LPDIRECT3DDEVICE9 pDevice )
{
m_pDevice = pDevice;

// Upper Left
m_Vertices[0].vPos = D3DXVECTOR3(-1, 1, 1);
m_Vertices[0].vTexCoordAndCornerIdx = D3DXVECTOR3(0, 0, 0);

// Upper Right
m_Vertices[1].vPos = D3DXVECTOR3(1, 1, 1);
m_Vertices[1].vTexCoordAndCornerIdx = D3DXVECTOR3(1, 0, 1);

// Lower Left
m_Vertices[2].vPos = D3DXVECTOR3(-1, -1, 1);
m_Vertices[2].vTexCoordAndCornerIdx = D3DXVECTOR3(0, 1, 3);

// Lower Right
m_Vertices[3].vPos = D3DXVECTOR3(1, -1, 1);
m_Vertices[3].vTexCoordAndCornerIdx = D3DXVECTOR3(1, 1, 2);

m_pDevice->CreateVertexBuffer(4*sizeof(sVertex),
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_MANAGED,
&m_pVB,
NULL);
VOID* pVoid;
m_pVB->Lock(0, 0, (void**)&pVoid, 0);
memcpy(pVoid, m_Vertices, sizeof(m_Vertices));
m_pVB->Unlock();

// Set description.
D3DVERTEXELEMENT9 Declaration[] =
{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
D3DDECL_END()
};
m_pDevice->CreateVertexDeclaration(Declaration, &m_pDeclaration);

m_Indices[0] = 0;
m_Indices[1] = 3;
m_Indices[2] = 2;
m_Indices[3] = 0;
m_Indices[4] = 1;
m_Indices[5] = 3;

m_pDevice->CreateIndexBuffer(6*sizeof(USHORT),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&m_pIB,
NULL);

VOID* pVoid2;
m_pIB->Lock(0, 0, (void**)&pVoid2, 0);
memcpy(pVoid2, m_Indices, sizeof(m_Indices));
m_pIB->Unlock();
}

~CScreenQuad()
{
SAFE_RELEASE(m_pVB);
SAFE_RELEASE(m_pIB);
}

void Render()
{
m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(sVertex) );
g_pRenderStateMgr->SetVertexDeclaration( m_pDeclaration );
m_pDevice->SetIndices( m_pIB );

m_pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, 4, 0, 2 );
m_pDevice->SetStreamSource( 0, NULL, 0, 0 );
m_pDevice->SetIndices( NULL );
}

private:

struct sVertex
{
D3DXVECTOR3 vPos;
D3DXVECTOR3 vTexCoordAndCornerIdx;
};

LPDIRECT3DVERTEXBUFFER9 m_pVB;
LPDIRECT3DINDEXBUFFER9 m_pIB;
LPDIRECT3DVERTEXDECLARATION9 m_pDeclaration;
sVertex m_Vertices[4];
USHORT m_Indices[6];
LPDIRECT3DDEVICE9 m_pDevice;
};


Here is the CPP file:



int ShadowMapSize = 512;

CShadowRenderer::CShadowRenderer()
{
shadowMap = NULL;
oldDS = NULL;
dsBuffer = NULL;
shadowOcclusion = NULL;
disabledShadowOcclusion = NULL;

ZeroMemory( frustumCornersVS, sizeof( frustumCornersVS ) );
ZeroMemory( frustumCornersWS, sizeof( frustumCornersWS ) );
ZeroMemory( frustumCornersLS, sizeof( frustumCornersLS ) );

ZeroMemory( farFrustumCornersVS, sizeof( farFrustumCornersVS ) );

ZeroMemory( splitFrustumCornersVS, sizeof( splitFrustumCornersVS ) );
ZeroMemory( splitFrustumCornersWS, sizeof( splitFrustumCornersWS ) );
ZeroMemory( splitFrustumCornersLS, sizeof( splitFrustumCornersLS ) );

ZeroMemory( lightViewProjectionMatrices, sizeof( lightViewProjectionMatrices ) );
ZeroMemory( lightClipPlanes, sizeof( lightClipPlanes ) );
ZeroMemory( splitDepths, sizeof( splitDepths ) );

enabled = true;
showCascadeSplits = false;
filteringType = PCF2x2;
for (DWORD i = 0; i < NumSplits; i++)
{
shadowOcclusionTechniques[ i ] = NULL;
}
shadowMapEffect = NULL;

for (int i = 0; i < NumSplits; i++)
{
lightCameras[ i ] = new CCamera( 2900.0f );
}

// Create the full-screen quad
fullScreenQuad = new CScreenQuad( g_d3dDevice );
}

CShadowRenderer::~CShadowRenderer()
{
OnLostDevice();
SAFE_RELEASE( shadowMapEffect );
SAFE_DELETE( fullScreenQuad );
for (int i = 0; i < NumSplits; i++)
{
SAFE_DELETE( lightCameras[ i ] );
}
}

void CShadowRenderer::BuildEffect()
{
// Load the effect we need
ID3DXBuffer* errors = NULL;
if (FAILED(D3DXCreateEffectFromFile(g_d3dDevice, MakeFullName( L"%s\\Resources\\Shaders\\ShadowMap.fx" ),
0, 0, D3DXSHADER_USE_LEGACY_D3DX9_31_DLL | D3DXSHADER_DEBUG, 0, &shadowMapEffect, &errors)))
{
if( errors )
{
OutputDebugStringA( (PCHAR)errors->GetBufferPointer() );
OutputDebugStringA( "\n" );
MessageBoxA( 0, (PCHAR)errors->GetBufferPointer(), 0, 0 );
SAFE_RELEASE(errors);
CleanupMPKLib();
exit(-1);
}
}
SAFE_RELEASE(errors);

// We'll keep an array of EffectTechniques that will let us map a
// ShadowFilteringType to a technique for calculating shadow occlusion
shadowOcclusionTechniques[0] = shadowMapEffect->GetTechniqueByName("CreateShadowTerm2x2PCF");
shadowOcclusionTechniques[1] = shadowMapEffect->GetTechniqueByName("CreateShadowTerm3x3PCF");
shadowOcclusionTechniques[2] = shadowMapEffect->GetTechniqueByName("CreateShadowTerm5x5PCF");
shadowOcclusionTechniques[3] = shadowMapEffect->GetTechniqueByName("CreateShadowTerm7x7PCF");
}

void CShadowRenderer::OnLostDevice()
{
SAFE_RELEASE( shadowMap );
SAFE_RELEASE( dsBuffer );
SAFE_RELEASE( shadowOcclusion );
SAFE_RELEASE( disabledShadowOcclusion );
shadowMapEffect->OnLostDevice();
}

void CShadowRenderer::OnResetDevice()
{
// Create the shadow map, using a 32-bit floating-point surface format
g_d3dDevice->CreateTexture(ShadowMapSize * NumSplits,
ShadowMapSize,
1,
D3DUSAGE_RENDERTARGET,
D3DFMT_R32F,
D3DPOOL_DEFAULT,
&shadowMap,
NULL);

// Create a depth-stencil surface using the same dimensions as the shadow map
g_d3dDevice->CreateDepthStencilSurface(ShadowMapSize * NumSplits,
ShadowMapSize,
DXUTGetPresentParameters().AutoDepthStencilFormat,
D3DMULTISAMPLE_NONE,
0,
TRUE, // ??? CHECKME
&dsBuffer,
NULL);

// Create the shadow occlusion texture using the same dimensions as the backbuffer
g_d3dDevice->CreateTexture(DXUTGetPresentParameters().BackBufferWidth,
DXUTGetPresentParameters().BackBufferHeight,
1,
D3DUSAGE_RENDERTARGET,
D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT,
&shadowOcclusion,
NULL);

// Create a 1x1 texture that we'll clear to white and return when shadows are disabled
g_d3dDevice->CreateTexture(1,
1,
1,
D3DUSAGE_RENDERTARGET,
D3DFMT_A8R8G8B8,
D3DPOOL_DEFAULT,
&disabledShadowOcclusion,
NULL);

if (!shadowMapEffect)
{
BuildEffect();
}
else
{
shadowMapEffect->OnResetDevice();
}

}

LPDIRECT3DTEXTURE9 CShadowRenderer::Render(
CGrowableArray< PVSEntry > *modelList,
LPDIRECT3DTEXTURE9 depthTexture,
DirLight *light,
CCamera *mainCamera )
{
if (g_enableShadowMapping)
{
// Set our targets
g_d3dDevice->GetDepthStencilSurface( &oldDS );
g_d3dDevice->SetDepthStencilSurface( dsBuffer );
SetRenderTarget( 0, shadowMap );

g_d3dDevice->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_BRIGHT_WHITE, 1.0f, 0);
g_d3dDevice->Clear(0, 0, D3DCLEAR_ZBUFFER, D3DCOLOR_BLACK, 1.0f, 0);

// Get corners of the main camera's bounding frustum
D3DXMATRIX cameraMatrix = mainCamera->GetViewMatrix();
mainCamera->GetCorners(frustumCornersWS, 1.0f); // 1.0f means LH

D3DXVec3TransformCoordArray( frustumCornersVS, sizeof( D3DXVECTOR3 ),
frustumCornersWS, sizeof( D3DXVECTOR3 ),
&cameraMatrix, ARRAYSIZE( frustumCornersVS ) );
for (int i = 0; i < 4; i++)
farFrustumCornersVS = frustumCornersVS[i + 4];

// Calculate the cascade splits. We calculate these so that each successive
// split is larger than the previous, giving the closest split the most amount
// of shadow detail.
float N = (float)NumSplits;
float nearClip = mainCamera->GetNearClipDist();
float farClip = mainCamera->GetFarClipDist();
splitDepths[0] = nearClip;
splitDepths[NumSplits] = farClip;
const float splitConstant = 0.95f;
for (int i = 1; i < ARRAYSIZE(splitDepths) - 1; i++)
{
splitDepths = splitConstant * nearClip
* (float)powf(farClip / nearClip, i / N)
+ (1.0f - splitConstant) * ((nearClip + (i / N))
* (farClip - nearClip));
}


// Render our scene geometry to each split of the cascade
for (int i = 0; i < NumSplits; i++)
{
float minZ = splitDepths;
float maxZ = splitDepths[i + 1];
CalculateFrustum(i, light, mainCamera, lightCameras[ i ], minZ, maxZ);
RenderShadowMap(modelList, i);
}
RenderShadowOcclusion(mainCamera, depthTexture);
return shadowOcclusion;
}
else
{
// If we're disabled, just clear our 1x1 texture to white and return it
ClearTexture( disabledShadowOcclusion, D3DCOLOR_BRIGHT_WHITE );

return disabledShadowOcclusion;
}
}

// clear given texture with given color
void CShadowRenderer::ClearTexture(
LPDIRECT3DTEXTURE9 pd3dTexture,
D3DCOLOR xColor)
{
LPDIRECT3DSURFACE9 pd3dSurface;
pd3dTexture->GetSurfaceLevel(0, &pd3dSurface);
g_d3dDevice->ColorFill(pd3dSurface, NULL, xColor);
pd3dSurface->Release();
}

void CShadowRenderer::SetRenderTarget(
int iRenderTargetIdx,
LPDIRECT3DTEXTURE9 pd3dRenderTargetTexture)
{
LPDIRECT3DSURFACE9 pd3dSurface;
pd3dRenderTargetTexture->GetSurfaceLevel(0, &pd3dSurface);
g_d3dDevice->SetRenderTarget(iRenderTargetIdx, pd3dSurface);
pd3dSurface->Release();
}

void CShadowRenderer::CalculateFrustum(
int splitIndex,
DirLight *light,
CCamera *mainCamera,
CCamera *lightCamera,
float minZ,
float maxZ)
{
// Shorten the view frustum according to the shadow view distance
D3DXMATRIX cameraMatrix = mainCamera->GetWorldMatrix();

for (int i = 0; i < 4; i++)
splitFrustumCornersVS = frustumCornersVS[i + 4] * (minZ / mainCamera->GetFarClipDist());

for (int i = 4; i < 8; i++)
splitFrustumCornersVS = frustumCornersVS * (maxZ / mainCamera->GetFarClipDist());

D3DXVec3TransformCoordArray( splitFrustumCornersWS, sizeof( D3DXVECTOR3 ),
splitFrustumCornersVS, sizeof( D3DXVECTOR3 ),
&cameraMatrix, ARRAYSIZE( splitFrustumCornersVS ) );

// Find the centroid
D3DXVECTOR3 frustumCentroid = D3DXVECTOR3(0,0,0);
for (int i = 0; i < 8; i++)
frustumCentroid += splitFrustumCornersWS;
frustumCentroid /= 8;

// Position the shadow-caster camera so that it's looking at the centroid,
// and backed up in the direction of the sunlight
float distFromCentroid = max((maxZ - minZ),
D3DXVec3Length( &( splitFrustumCornersVS[4] - splitFrustumCornersVS[5] ) )) + 50.0f;
D3DXMATRIX viewMatrix;
D3DXVECTOR3 pos = frustumCentroid - (light->dirW * distFromCentroid);

D3DXMatrixLookAtLH( &viewMatrix, &pos, &frustumCentroid, &D3DXVECTOR3( 0, 1, 0 ) );

// Determine the position of the frustum corners in light space
D3DXVec3TransformCoordArray( splitFrustumCornersLS, sizeof( D3DXVECTOR3 ),
splitFrustumCornersWS, sizeof( D3DXVECTOR3 ),
&viewMatrix, ARRAYSIZE( splitFrustumCornersLS ) );

// Calculate an orthographic projection by sizing a bounding box
// to the frustum coordinates in light space
D3DXVECTOR3 mins = splitFrustumCornersLS[0];
D3DXVECTOR3 maxes = splitFrustumCornersLS[0];
for (int i = 0; i < 8; i++)
{
if (splitFrustumCornersLS.x > maxes.x)
maxes.x = splitFrustumCornersLS.x;
else if (splitFrustumCornersLS.x < mins.x)
mins.x = splitFrustumCornersLS.x;
if (splitFrustumCornersLS.y > maxes.y)
maxes.y = splitFrustumCornersLS.y;
else if (splitFrustumCornersLS.y < mins.y)
mins.y = splitFrustumCornersLS.y;
if (splitFrustumCornersLS.z > maxes.z)
maxes.z = splitFrustumCornersLS.z;
else if (splitFrustumCornersLS.z < mins.z)
mins.z = splitFrustumCornersLS.z;
}

// Create an orthographic camera for use as a shadow caster
lightCamera->m_matView = viewMatrix;
lightCamera->m_viewport.MinZ = 0;
lightCamera->m_viewport.MaxZ = 1;
lightCamera->m_viewport.Width = ShadowMapSize;
lightCamera->m_viewport.Height = ShadowMapSize;
lightCamera->m_viewport.X = splitIndex * ShadowMapSize;
lightCamera->m_viewport.Y = 0;
lightCamera->CreateOrthoProj(mins.x, maxes.x, mins.y, maxes.y, mins.z - 100.0f, maxes.z);
}

void CShadowRenderer::RenderShadowMap(
CGrowableArray< struct _PVSEntry > *modelList,
int splitIndex)
{
D3DPERF_BeginEvent( DXUT_PERFEVENTCOLOR3, L"RenderShadowMap" );

// Set the viewport for the current split
g_d3dDevice->SetViewport( lightCameras[ splitIndex ]->GetViewport() );

// Stash the view/proj from our main camera and
// switch them out with this light frustum's view/proj.
D3DXMATRIX oldView, oldProj;
oldView = g_pCamera->m_matView;
oldProj = g_pCamera->m_matProj;
g_pCamera->m_matView = lightCameras[ splitIndex ]->GetViewMatrix();
g_pCamera->m_matProj = lightCameras[ splitIndex ]->GetProjectionMatrix();

//
// Draw renderables (non-alpha pass and batched)
//
DirLight light;
float dt = 0.0f;
for (int i = 0; i < modelList->GetSize(); i++)
{
CRenderableMesh *pMesh = dynamic_cast< CRenderableMesh * >( (*modelList)[ i ].m_pRenderable );
if (pMesh && g_pRegion->m_isAboveGround)
{
SHADER_QUALITY oldQuality = pMesh->m_currentQuality;
pMesh->SetShaderQuality( DEPTH_ONLY );
CStaticMesh *b = pMesh->m_pOwner;
if (b)
{
if (!FAILED(b->PreDraw( pMesh, &light )))
{
b->Draw( pMesh, (*modelList)[ i ].m_pInstanceDetails, &g_pCamera->GetPos(), dt );
}
else
{
pMesh->m_vBatchInstance_Pos.clear();
pMesh->m_vBatchInstance_Rotation.clear();
}
}
else
{
if (!FAILED(pMesh->PreDraw( &light )))
{
pMesh->Draw( NULL,
&g_pCamera->GetPos(),
&(*modelList)[ i ].m_worldTransform,
&(*modelList)[ i ].m_invWorldTransform,
dt );
}
}
pMesh->SetShaderQuality( oldQuality );
}
else
{
CActor *pActor = (*modelList)[ i ].m_pActor;
if (pActor)
{
pActor->m_depthOnly = true;
//
// Set animation update time
//
if (g_stopOnEvent)
{
if (g_advanceTime)
{
pActor->AdvanceTime( dt );
}
}
else
{
pActor->AdvanceTime( g_freezeTime ? 0.0f : dt );
}
if (!(*modelList)[ i ].m_animateOnly)
{
pActor->Draw( g_renderSkeleton, &light, dt, -1, -1, false );
}
pActor->m_depthOnly = false;
}
}
}

//
// Restore main camera's view/proj
//
g_pCamera->m_matView = oldView;
g_pCamera->m_matProj = oldProj;

D3DPERF_EndEvent();
}

void CShadowRenderer::RenderShadowOcclusion(
CCamera *mainCamera,
LPDIRECT3DTEXTURE9 depthTexture)
{
D3DPERF_BeginEvent( DXUT_PERFEVENTCOLOR2, L"RenderShadowOcclusion" );

// Restore main camera's viewport
g_d3dDevice->SetViewport( mainCamera->GetViewport() );

// Set the device to render to our shadow occlusion texture, and to use
// the original DepthStencilSurface
SetRenderTarget( 0, shadowOcclusion );
g_d3dDevice->SetDepthStencilSurface( oldDS );
SAFE_RELEASE( oldDS );

D3DXMATRIX cameraTransform = mainCamera->GetWorldMatrix();

// We'll use these clip planes to determine which split a pixel belongs to
for (int i = 0; i < NumSplits; i++)
{
lightClipPlanes.x = splitDepths;
lightClipPlanes.y = splitDepths[i + 1];

lightViewProjectionMatrices = lightCameras->GetViewProj();
}

// Setup the Effect
shadowMapEffect->SetTechnique( shadowOcclusionTechniques[(int)filteringType] );
shadowMapEffect->SetBool( "g_bShowSplitColors", showCascadeSplits );
shadowMapEffect->SetMatrix( "g_matInvView", &cameraTransform );
shadowMapEffect->SetMatrixArray( "g_matLightViewProj", lightViewProjectionMatrices, NumSplits );
shadowMapEffect->SetValue( "g_vFrustumCornersVS", farFrustumCornersVS, NumSplits * sizeof( D3DXVECTOR3 ) );
shadowMapEffect->SetValue( "g_vClipPlanes", lightClipPlanes, NumSplits * sizeof( D3DXVECTOR2 ) );
shadowMapEffect->SetTexture( "ShadowMap", shadowMap );
shadowMapEffect->SetTexture( "DepthTexture", depthTexture );
D3DSURFACE_DESC desc;
shadowOcclusion->GetLevelDesc( 0, &desc );
shadowMapEffect->SetValue( "g_vOcclusionTextureSize", &D3DXVECTOR2( (float)desc.Width, (float)desc.Height ), sizeof( D3DXVECTOR2 ) );
shadowMap->GetLevelDesc( 0, &desc );
shadowMapEffect->SetValue( "g_vShadowMapSize", &D3DXVECTOR2( (float)desc.Width, (float)desc.Height ), sizeof( D3DXVECTOR2 ) );
shadowMapEffect->SetBool( "g_bShowSplitColors", showCascadeSplits );

// Begin effect
UINT numPasses = 0;
shadowMapEffect->Begin( &numPasses, 0 );
shadowMapEffect->BeginPass( 0 );

// Draw the full screen quad
fullScreenQuad->Render();

// End the effect
shadowMapEffect->EndPass();
shadowMapEffect->End();

D3DPERF_EndEvent();
}



This is the camera code that mimics the XNA BoundingFrustum::GetCorners() method:


void CCamera::GetCorners (D3DXVECTOR3 *corners, float hand)
{
D3DXVECTOR3 p = GetPos();
D3DXVECTOR3 d = GetLookVector() * hand;
D3DXVECTOR3 right = GetRightVector();
D3DXVECTOR3 up = GetUpVector();

float Hnear = 2 * tan(m_fov / 2) * GetNearClipDist();
float Wnear = Hnear * m_aspectRatio;
float Hfar = 2 * tan(m_fov / 2) * GetFarClipDist();
float Wfar = Hfar * m_aspectRatio;

D3DXVECTOR3 nc = p + (d * GetNearClipDist());

m_cornersWS[ 0 ] = nc + (up * Hnear/2) - (right * Wnear/2); // near top left
m_cornersWS[ 1 ] = nc + (up * Hnear/2) + (right * Wnear/2); // near top right
m_cornersWS[ 2 ] = nc - (up * Hnear/2) + (right * Wnear/2); // near bottom right
m_cornersWS[ 3 ] = nc - (up * Hnear/2) - (right * Wnear/2); // near bottom left

D3DXVECTOR3 fc = p + (d * GetFarClipDist());

m_cornersWS[ 4 ] = fc + (up * Hfar/2) - (right * Wfar/2); // far top left
m_cornersWS[ 5 ] = fc + (up * Hfar/2) + (right * Wfar/2); // far top right
m_cornersWS[ 6 ] = fc - (up * Hfar/2) + (right * Wfar/2); // far bottom right
m_cornersWS[ 7 ] = fc - (up * Hfar/2) - (right * Wfar/2); // far bottom left

memcpy( *corners, m_cornersWS, sizeof( D3DXVECTOR3 ) * 8 );
}



This is the camera code for generating an orthographic projection matrix:



void CCamera::CreateOrthoProj( float xMin, float xMax, float yMin, float yMax, float nearClip, float farClip )
{
m_zNear = nearClip;
m_zFar = farClip;

D3DXMatrixOrthoOffCenterLH( &m_matProj2, xMin, xMax, yMin, yMax, nearClip, farClip );
m_matProj = m_matProj2;
}


I did make a small change to ShadowMap.fx here ( I flipped the direction of the clip plane test):


// Unrolling the loop allows for a performance boost on the 360
#ifdef XBOX
[unroll(NUM_SPLITS - 1)]
#endif
for (int i = 0; i < NUM_SPLITS; i++)
{
#ifdef XBOX
[flatten]
#endif
if (vPositionVS.z >= g_vClipPlanes.x && vPositionVS.z < g_vClipPlanes.y)
{
matLightViewProj = g_matLightViewProj;
fOffset = i / (float)NUM_SPLITS;
vColor = vSplitColors;
iCurrentSplit = i;
}
}


This is a screen shot of how the occlusion texture is being pasted on top of the object as if some depth comparison is wrong:

Original backbuffer:

smap1.jpg

With cascaded split visualization:

smap2.jpg


Input depth buffer to RenderShadowOcclusion(). This linear depth buffer comes from the GBuffer pass before drawing the shadowmap and shadow occlusion texture.

First depth image is full depth range (0 .. 1.0f)


smap_depth.jpg

Second image is constrained to (0 .. 0.35f) for viewability:

smap_depth40.jpg


This is the lightmap texture:


lightmap.jpg
This is a close-up of the actor with my final shader set to just display the results of sampling the shadow occlusion texture:

shadowonly.jpg

And the lightmap used to generate the above image:
shadowonly_lm.jpg


Steve_Segreto
Steve_Segreto
I got my issues solved.



smap3.jpg


Thank you for writing the code for this sample MJP, it is a neat little treasure and it looks beautiful!!!!

I had two main issues.


#1. I was writing view space depth to my shadowmap because I thought it would have less precision errors. I changed it back to linear depth and the shadowmap was correctly laid onto the meshes during the RenderShadowOcclusion phase.


#2. The shadows were strangely moving away from the shadow casters when I turned the camera because my main camera used an off-center perspective projection matrix (the #else part of the code below was my original camera matrix, I changed it to a plain old FovLH() matrix and the shadows stayed rooted to the actor's feet).





#if 1
D3DXMatrixPerspectiveFovLH(&m_matProj2, m_fov, m_aspectRatio, m_zNear, m_zFar );
#else
float fXSpread;
float fYSpread;

if (g_tateMode)
{
fXSpread = m_zNear / ((cot(m_fov / 2) / m_aspectRatio) / 2);
fYSpread = -(m_zNear / (cot(m_fov / 2) / 2));
}
else
{
fXSpread = m_zNear / (cot(m_fov / 2) / 2);
fYSpread = -(m_zNear / ((cot(m_fov / 2) * m_aspectRatio) / 2));
}

float w = (float)DXUTGetPresentParameters().BackBufferWidth;
float h = (float)DXUTGetPresentParameters().BackBufferHeight;
float fMinX = (float)(m_viewport.X) / (float)(w);
float fMaxX = (float)(m_viewport.X + m_viewport.Width) / (float)(w);
float fMinY = (float)(m_viewport.Y + m_viewport.Height) / (float)(h);
float fMaxY = (float)(m_viewport.Y) / (float)(h);

fMinX *= fXSpread;
fMaxX *= fXSpread;
fMinY *= fYSpread;
fMaxY *= fYSpread;

fMinX -= fXSpread / 2;
fMaxX -= fXSpread / 2;
fMinY -= fYSpread / 2;
fMaxY -= fYSpread / 2;

D3DXMatrixPerspectiveOffCenterLH(&m_matProj2, fMinX, fMaxX, fMinY, fMaxY, m_zNear, m_zFar);
#endif





There also is likely some sort of pixel-to-texel issue still when I converted from RH to LH, here maybe in ShadowMap.fx:



void ShadowTermVS (in float3 in_vPositionOS : POSITION,
in float3 in_vTexCoordAndCornerIndex : TEXCOORD0,
out float4 out_vPositionCS : POSITION,
out float2 out_vTexCoord : TEXCOORD0,
out float3 out_vFrustumCornerVS : TEXCOORD1)
{
// Offset the position by half a pixel to correctly align texels to pixels
out_vPositionCS.x = in_vPositionOS.x - (1.0f / g_vOcclusionTextureSize.x);
out_vPositionCS.y = in_vPositionOS.y + (1.0f / g_vOcclusionTextureSize.y);
out_vPositionCS.z = in_vPositionOS.z;
out_vPositionCS.w = 1.0f;



Any advice if the above code is still correct for DX9 LH?

This code works great other than that, thanks again!!!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.