Sign in to follow this  
Baltz

PSSM shadow problems

Recommended Posts

Hello I am trying to implement PSSM shadows in the engine and so far most things seem to be correct. There is problem i have with the splits. The frustum seems to get split correctly but when i sample the shadow maps, things go wrong for all shadow maps except the first one. i am using XNA and the PSSM code was ported from the popular DirectX implementation at: http://hax.fi/asko/PSSM.html anyone know what might be wrong?? thanks! Frustum splits Shadows sampled
just to describe the problem again, if the frustum is split in 3 (Red, green, blue)
the red area renders fine, the green and blue area however is all white.


this is where the PSSM calculations are happening


public class PSSMHelpers
{
// light
public Matrix _mLightView;
public Matrix _mLightProj;
public Vector4 _vLightDir = new Vector4(0, 0, 0, 0);
public Vector4 _vLightDiffuse = new Vector4(0.7f, 0.7f, 0.7f, 1);
public Vector4 _vLightAmbient = new Vector4(0.25f, 0.25f, 0.25f, 1);
public float _fLightNear = 10.0f;
public float _fLightFar = 400.0f; // dynamically adjusted
public float _fLightFarMax = 400.0f;
public float _fLightFOV = 90.0f;
public float _fLightRotation = MathHelper.ToRadians(150);
public Vector3 _vLightSource; // dynamically adjusted
public Vector3 _vLightTarget; // dynamically adjusted

// camera
public Matrix _mCameraView;
public Matrix _mCameraProj;
public Vector3 _vCameraSource = new Vector3(0, 20, -110);
public Vector3 _vCameraTarget = new Vector3(0, 20, -109);
public Vector3 _vCameraUpVector = new Vector3(0, 1, 0);
public float _fCameraNear = 0.5f;
public float _fCameraFar = 400.0f; // dynamically adjusted
public float _fCameraFarMax = 400.0f;
public float _fCameraFOV = 45.0f;

// split scheme
public int _iNumSplits = 3;
public float _fSplitSchemeLambda = 0.5f;
public float[] _pSplitDistances;

public PSSMHelpers()
{
}

public void CalculateViewProj(out Matrix view, out Matrix projection,
Vector3 position, Vector3 target, Vector3 up,
float fov, float near, float far, float aspect)
{

view = Matrix.CreateLookAt(position, target, up);

projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(fov), aspect, near, far);

}

public void AdjustCameraPlanes(Physics.AxisAlignedBoundingBox scene_abb)
{
CalculateViewProj(out _mCameraView, out _mCameraProj,
_vCameraSource, _vCameraTarget, _vCameraUpVector,
_fCameraFOV, _fCameraNear, _fCameraFarMax, 1);

float fMaxZ = 0.0f;

for (int i = 0; i < 8; i++)
{

// transform z coordinate with view matrix
float fZ = scene_abb.Corners[i].X * _mCameraView.M13 +
scene_abb.Corners[i].Y * _mCameraView.M23 +
scene_abb.Corners[i].Z * _mCameraView.M33 +
1 * _mCameraView.M43;

// check if its largest
if (fZ < fMaxZ)
{
fMaxZ = fZ;
}
}

_fCameraFar = (0 - fMaxZ) + _fCameraNear;
}



public void CalculateSplitDistances()
{
// Practical split scheme:
//
// CLi = n*(f/n)^(i/numsplits)
// CUi = n + (f-n)*(i/numsplits)
// Ci = CLi*(lambda) + CUi*(1-lambda)
//
// lambda scales between logarithmic and uniform
//

_fSplitSchemeLambda = MathHelper.Clamp(_fSplitSchemeLambda, 0.0f, 1.0f);

_pSplitDistances = new float[_iNumSplits + 1];

for (int i = 0; i < _iNumSplits; i++)
{
float fIDM = i / (float)_iNumSplits;
float fLog = _fCameraNear * (float)Math.Pow((_fCameraFar / _fCameraNear), fIDM);
float fUniform = _fCameraNear + (_fCameraFar - _fCameraNear) * fIDM;
_pSplitDistances[i] = fLog * _fSplitSchemeLambda + fUniform * (1 - _fSplitSchemeLambda);
}


// make sure border values are right
_pSplitDistances[0] = _fCameraNear;
_pSplitDistances[_iNumSplits] = _fCameraFar;
}


// Calculates the view and projection matrix for a light. The projection
// matrix is "zoomed in" on the given frustum split.
//
//
public void CalculateLightForFrustum(Vector3[] pCorners)
{

// calculate standard view and projection matrices for light
CalculateViewProj(out _mLightView, out _mLightProj,
_vLightSource, _vLightTarget, Vector3.Up,
_fLightFOV, _fLightNear, _fLightFarMax, 1);


// Next we will find the min and max values of the current
// frustum split in lights post-projection space
// (where coordinate range is from -1.0 to 1.0)
//
float fMaxX = -float.MaxValue;
float fMaxY = -float.MaxValue;
float fMinX = float.MaxValue;
float fMinY = float.MaxValue;
float fMaxZ = 0;

Matrix mLightViewProj = _mLightView * _mLightProj;

// for each corner point
for (int i = 0; i < 8; i++)
{
// transform point
Vector4 vTransformed = Vector4.Transform(pCorners[i], mLightViewProj);

// project x and y
vTransformed.X /= vTransformed.W;
vTransformed.Y /= vTransformed.W;

// find min and max values
if (vTransformed.X > fMaxX) fMaxX = vTransformed.X;
if (vTransformed.Y > fMaxY) fMaxY = vTransformed.Y;
if (vTransformed.Y < fMinY) fMinY = vTransformed.Y;
if (vTransformed.X < fMinX) fMinX = vTransformed.X;

// find largest z distance
if (vTransformed.Z > fMaxZ) fMaxZ = vTransformed.Z;
}


// set values to valid range (post-projection)
fMaxX = MathHelper.Clamp(fMaxX, -1.0f, 1.0f);
fMaxY = MathHelper.Clamp(fMaxY, -1.0f, 1.0f);
fMinX = MathHelper.Clamp(fMinX, -1.0f, 1.0f);
fMinY = MathHelper.Clamp(fMinY, -1.0f, 1.0f);

// Adjust the far plane of the light to be at the farthest
// point of the frustum split. Some bias may be necessary.
//
_fLightFar = fMaxZ + _fLightNear + 1.5f;

// re-calculate lights matrices with the new far plane
CalculateViewProj(out _mLightView, out _mLightProj,
_vLightSource, _vLightTarget, Vector3.Up,
_fLightFOV, _fLightNear, _fLightFar, 1);


// Next we build a special matrix for cropping the lights view
// to only contain points of the current frustum split
//

float fScaleX = 2.0f / (fMaxX - fMinX);
float fScaleY = 2.0f / (fMaxY - fMinY);

float fOffsetX = -0.5f * (fMaxX + fMinX) * fScaleX;
float fOffsetY = -0.5f * (fMaxY + fMinY) * fScaleY;

Matrix mCropView = new Matrix(fScaleX, 0.0f, 0.0f, 0.0f,
0.0f, fScaleY, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
fOffsetX, fOffsetY, 0.0f, 1.0f);

// multiply the projection matrix with it
_mLightProj *= mCropView;

// finally modify projection matrix for linearized depth
_mLightProj.M33 /= _fLightFar;
_mLightProj.M43 /= _fLightFar;
}


public Vector3[] CalculateFrustumCorners(
Vector3 vSource,
Vector3 vTarget,
Vector3 vUp,
float fNear, float fFar,
float fFOV, float fAspect, float fScale)
{

Vector3 vZ = vTarget - vSource;
vZ = Vector3.Normalize(vZ);

Vector3 vX;
vX = Vector3.Cross(vUp, vZ);
vX = Vector3.Normalize(vX);

Vector3 vY;
vY = Vector3.Cross(vZ, vX);

float fNearPlaneHeight = (float)Math.Tan(MathHelper.ToRadians(fFOV) * 0.5f) * fNear;
float fNearPlaneWidth = fNearPlaneHeight * fAspect;

float fFarPlaneHeight = (float)Math.Tan(MathHelper.ToRadians(fFOV) * 0.5f) * fFar;
float fFarPlaneWidth = fFarPlaneHeight * fAspect;

Vector3 vNearPlaneCenter = vSource + vZ * fNear;
Vector3 vFarPlaneCenter = vSource + vZ * fFar;

Vector3[] pPoints = new Vector3[8];
pPoints[0] = (vNearPlaneCenter - vX * fNearPlaneWidth - vY * fNearPlaneHeight);
pPoints[1] = (vNearPlaneCenter - vX * fNearPlaneWidth + vY * fNearPlaneHeight);
pPoints[2] = (vNearPlaneCenter + vX * fNearPlaneWidth + vY * fNearPlaneHeight);
pPoints[3] = (vNearPlaneCenter + vX * fNearPlaneWidth - vY * fNearPlaneHeight);

pPoints[4] = (vFarPlaneCenter - vX * fFarPlaneWidth - vY * fFarPlaneHeight);
pPoints[5] = (vFarPlaneCenter - vX * fFarPlaneWidth + vY * fFarPlaneHeight);
pPoints[6] = (vFarPlaneCenter + vX * fFarPlaneWidth + vY * fFarPlaneHeight);
pPoints[7] = (vFarPlaneCenter + vX * fFarPlaneWidth - vY * fFarPlaneHeight);

// calculate center of points
Vector3 vCenter = new Vector3(0, 0, 0);
for (int i = 0; i < 8; i++) vCenter += pPoints[i];
vCenter /= 8;

// for each point
for (int i = 0; i < 8; i++)
{
// scale by adding offset from center
pPoints[i] += (pPoints[i] - vCenter) * (fScale - 1);
}

return pPoints;
}
}









this is where shadows are generated and rendered



/// <summary>No Description</summary>
public class ShadowMapEnginePSSM
{
private const int m_ShadowMapSize = 1024;
private Shadows.PSSMHelpers m_PSSMHelpers;


private RenderTarget2D[] m_ShadowTextureRT;
public RenderTarget2D[] ShadowTextureRT
{
get { return m_ShadowTextureRT; }
}

//private Matrix[] m_LightViewProjectionPerRT;
private DepthStencilBuffer m_DepthBuffer;

private BrainScene m_SceneRef;

public BrainScene SceneRef
{
get { return m_SceneRef; }
set { m_SceneRef = value; }
}

private Camera m_CameraRef;

public Camera CameraRef
{
get { return m_CameraRef; }
set { m_CameraRef = value; }
}

private Viewport m_Viewport;


/// <summary>No Description</summary>
public ShadowMapEnginePSSM()
{
CreateTexturesAndRTs();
}

/// <summary>No Description</summary>
private void CreateTexturesAndRTs()
{
m_PSSMHelpers = new BrainEngine.BrainGraphics.Shadows.PSSMHelpers();
m_ShadowTextureRT = new RenderTarget2D[m_PSSMHelpers._iNumSplits];
//m_LightViewProjectionPerRT = new Matrix[m_PSSMHelpers._iNumSplits];

for (int i = 0; i < m_PSSMHelpers._iNumSplits; ++i)
{
m_ShadowTextureRT[i] = new RenderTarget2D(BrainGraphics.GraphicDevice.Direct3DDevice, m_ShadowMapSize, m_ShadowMapSize, 1, SurfaceFormat.Single);
}

m_DepthBuffer = new DepthStencilBuffer(BrainGraphics.GraphicDevice.Direct3DDevice, m_ShadowMapSize, m_ShadowMapSize, DepthFormat.Depth24Stencil8);

m_Viewport.Height = m_ShadowMapSize;
m_Viewport.Width = m_ShadowMapSize;
m_Viewport.MinDepth = 0.0f;
m_Viewport.MaxDepth = 1.0f;
m_Viewport.X = 0;
m_Viewport.Y = 0;

}


public void RenderShadowMaps()
{
if (m_SceneRef == null || m_CameraRef == null) return;

float _fLightRotation= MathHelper.ToRadians(150);

List<BrainSceneObject> scene_objects = new List<BrainSceneObject>(1000);
for (int i = 0; i < m_SceneRef.Groups.Count; ++i)
{
scene_objects.AddRange(m_SceneRef.Groups[i].SceneObjects.ToArray());
}

float fOffsetX = 0.5f + (0.5f / m_ShadowMapSize);
float fOffsetY = 0.5f + (0.5f / m_ShadowMapSize);

//compute bias matrix
Matrix biasMatrix = new Matrix(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
fOffsetX, fOffsetY, 0, 1.0f);



// calculate the light position
m_PSSMHelpers._vLightSource = new Vector3(-200 * (float)Math.Sin(_fLightRotation), 120, 200 * (float)Math.Cos(_fLightRotation));
m_PSSMHelpers._vLightTarget = new Vector3(0, 0, 0);
// and direction
m_PSSMHelpers._vLightDir = new Vector4(m_PSSMHelpers._vLightTarget - m_PSSMHelpers._vLightSource, 0);
m_PSSMHelpers._vLightDir = Vector4.Normalize(m_PSSMHelpers._vLightDir);


float fCameraAspect = (float)GraphicDevice.PresentParameters.BackBufferWidth / (float)GraphicDevice.PresentParameters.BackBufferHeight;

// Position the camera far plane as near as possible
// to minimize the amount of empty space
//
m_PSSMHelpers.AdjustCameraPlanes(m_SceneRef.AABB);

// Calculate the distances of split planes
// according to the selected split scheme
//
m_PSSMHelpers.CalculateSplitDistances();


// Clear the screen
//
GraphicDevice.Direct3DDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, GraphicDevice.BackBufferColor, 1.0f, 0);


// Set shadow rendering parameters
DepthStencilBuffer original_depth_buffer = GraphicDevice.Direct3DDevice.DepthStencilBuffer;
GraphicDevice.Direct3DDevice.RenderState.AlphaBlendEnable = false;
GraphicDevice.Direct3DDevice.RenderState.AlphaTestEnable = false;

//m_PSSMHelpers._vCameraSource = m_CameraRef.Position;
//m_PSSMHelpers._vCameraTarget = m_CameraRef.TargetPoint;


for (int i = 0; i < m_PSSMHelpers._iNumSplits; i++)
{
// near and far planes for current frustum split
float near = m_PSSMHelpers._pSplitDistances[i];
float far = m_PSSMHelpers._pSplitDistances[i + 1];


float fScale=1.1f;

Vector3[] frustum_corners = m_PSSMHelpers.CalculateFrustumCorners(
m_PSSMHelpers._vCameraSource,
m_PSSMHelpers._vCameraTarget,
m_PSSMHelpers._vCameraUpVector,
near,
far,
m_PSSMHelpers._fCameraFOV,
fCameraAspect, fScale);

m_PSSMHelpers.CalculateLightForFrustum(frustum_corners);


GraphicDevice.Direct3DDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
GraphicDevice.Direct3DDevice.Viewport = m_Viewport;
GraphicDevice.Direct3DDevice.SetRenderTarget(0, m_ShadowTextureRT[i]);
GraphicDevice.Direct3DDevice.DepthStencilBuffer = m_DepthBuffer;
GraphicDevice.Direct3DDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);


BrainEffect effect = GraphicDevice.ShaderManager.GetShader(ShaderList.ShadowMapsPSSM);
effect.Parameters["LightWorldViewProjection"].SetValue(m_PSSMHelpers._mLightView * m_PSSMHelpers._mLightProj);


// draw shadow casters
RenderGeometry(effect, true, null);
RenderGeometry(effect, false, null);




GraphicDevice.Direct3DDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
GraphicDevice.SetDefaultViewport();
GraphicDevice.Direct3DDevice.SetRenderTarget(0, null);
GraphicDevice.Direct3DDevice.DepthStencilBuffer = original_depth_buffer;

{
Viewport cameraViewport = GraphicDevice.Direct3DDevice.Viewport;
cameraViewport.MinDepth = (float)i * (1.0f / (float)m_PSSMHelpers._iNumSplits);
cameraViewport.MaxDepth = ((float)i + 1) * (1.0f / (float)m_PSSMHelpers._iNumSplits);
GraphicDevice.Direct3DDevice.Viewport = cameraViewport;
}


m_PSSMHelpers.CalculateViewProj(out m_PSSMHelpers._mCameraView, out m_PSSMHelpers._mCameraProj,
m_PSSMHelpers._vCameraSource, m_PSSMHelpers._vCameraTarget, m_PSSMHelpers._vCameraUpVector,
m_PSSMHelpers._fCameraFOV, near, far, fCameraAspect);



RenderShadowsToScene(i, m_PSSMHelpers._mLightView * m_PSSMHelpers._mLightProj * biasMatrix, m_PSSMHelpers._mCameraView * m_PSSMHelpers._mCameraProj );

}


}

public void RenderShadowsToScene(int split, Matrix shadow_matrix, Matrix camera_matrix)
{
if (m_SceneRef == null || m_CameraRef == null) return;

{
BrainEffect effect = GraphicDevice.ShaderManager.GetShader(ShaderList.ShadowMapsPSSMScene);

Matrix m_ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(m_CameraRef.FieldOfView, m_CameraRef.AspectRatio, m_PSSMHelpers._pSplitDistances[0], m_PSSMHelpers._pSplitDistances[m_PSSMHelpers._pSplitDistances.Length - 1]);
Matrix m_ViewMatrix = Matrix.CreateLookAt(m_CameraRef.Position, m_CameraRef.TargetPoint, new Vector3(0.0f, 1.0f, 0.0f));


effect.Parameters["ShadowSceneViewProjection"].SetValue(camera_matrix);


effect.Parameters["ShadowMatrixPerRT"].SetValue(shadow_matrix);


if (split == 0) effect.Parameters["SPLITCOLOR"].SetValue(new Vector4(1, 0, 0, 1));
if (split == 1) effect.Parameters["SPLITCOLOR"].SetValue(new Vector4(0, 1, 0, 1));
if (split == 2) effect.Parameters["SPLITCOLOR"].SetValue(new Vector4(0, 0, 1, 1));

effect.Parameters["shadowMapTexture0"].SetValue(m_ShadowTextureRT[0].GetTexture());


// draw shadow casters
RenderGeometry(effect, true, null);
RenderGeometry(effect, false, null);
}
}




private void RenderGeometry(BrainEffect shader, bool skinned, BoundingFrustum fs)
{

if (skinned)
shader.CurrentTechnique = shader.Techniques["RenderShadowMapSkin"];
else
shader.CurrentTechnique = shader.Techniques["RenderShadowMap"];

shader.Begin();

foreach (EffectPass pass in shader.CurrentTechnique.Passes)
{
pass.Begin();

foreach (BrainSceneGroup group in m_SceneRef.Groups)
{
foreach (BrainSceneObject scene_object in group.SceneObjects)
{


scene_object.Render(shader);
}
}

pass.End();
}

shader.End();
}
}






[Edited by - Baltz on October 22, 2008 11:34:31 AM]

Share this post


Link to post
Share on other sites
It sounds like the showmap uv calculation is wrong for those splits. That could be due to the shader constants not being set correctly for those splits.

A good debugging technique here is to directly render the shadowmap sample you're getting instead of using the result and doing the compare. That makes it easy to see what the shader is sampling and you can start playing with your math/code and watch the results directly.

Hope that helps!

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