• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
gcard28

Stable Cascaded Shadow Maps have made me lose all my hair

7 posts in this topic

I've been working furiously and so far stable cascaded shadow maps are mocking me..... I can't seem to get this working for the life of me.....

 

So I will run through my algorithm and perhaps some kind soul will take pity on me and show me the error of my ways......

 

[1] I calculate the split frustum values for FOUR cascades.

 

     I use this equation:

    // get the currently active camera
    ICamera *pcCamera = getCurrentCamera ();
    vsAssert (pcCamera != NULL);

    if (m_fSplitNearZ < pcCamera->getNearZ ())
    {
        m_fSplitNearZ = pcCamera->getNearZ ();
    }

    NUM_CASCADE_SPLITS = 4;

    for (vsInt32 iSplit = 1; iSplit < NUM_CASCADE_SPLITS; iSplit++)
    {
        vsFloat32 fStep = iSplit / (vsFloat32) NUM_CASCADE_SPLITS;

        vsFloat32 fLogSplit = m_fSplitNearZ * powf (m_fSplitFarZ / m_fSplitNearZ, fStep);

        vsFloat32 fLinearSplit = m_fSplitNearZ + (m_fSplitFarZ - m_fSplitNearZ) * fStep;

        // linearly interpolate between the linear and logarithmic split values
        m_fSplitPositions [iSplit] = fLogSplit * m_fLogSplitWeight + fLinearSplit * (1.0f - m_fLogSplitWeight);
    }

    // ensure the border split values are accurate
    m_fSplitPositions [0] = m_fSplitNearZ;
    m_fSplitPositions [NUM_CASCADE_SPLITS] = m_fSplitFarZ;

So now I have my frustum splits.....

 

I use these splits to calculate the corners of the cascade frustum that is being considered. So if I am going to update the first cascade shadow map I use the first and second frustum split positions. ie. m_fSplitPositions [0] and m_fSplitPositions [1].

 

So now I have 8 view space corners for the split frustum in view space. I generate the bounding box of these points and get the center. Again this is in View space. I take the max AABB point, subtract from the centroid I calculated and get it's length. This is what I use as the radius of the sphere that encloses this split frustum.

 

I then multiply the View Space frustum center by the inverse view to get the center in world space. I do the above for all FOUR cascade frustums when I am done I have FOUR spheres in TOTAL. One for each split sub frustum. Since I have 4 cascades I have 4 spheres.

CalculateCascadeBoundingSphere (void)
{
    // find the minimum enclosing sphere for the split frustum, this is done in
    // local space to avoid precision variation between frames

    ICamera *pcCurrentCamera = getCurrentCamera ();
    vsAssert (pcCurrentCamera != NULL);

    vsFloat32 fTanHalfFOVY = pcCurrentCamera->getTanHalfFOVY ();
    vsFloat32 fTanHalfFOVX = fTanHalfFOVY * pcCurrentCamera->getAspectRatio ();


    vsFloat32 fNearX = fTanHalfFOVX * m_fCurrentNearSplit;
    vsFloat32 fNearY = fTanHalfFOVY * m_fCurrentNearSplit;
    vsFloat32 fNearZ = m_fCurrentNearSplit;

     vsFloat32 fFarX = fTanHalfFOVX  * m_fCurrentFarSplit;
     vsFloat32 fFarY = fTanHalfFOVY * m_fCurrentFarSplit;
     vsFloat32 fFarZ = m_fCurrentFarSplit;


    // calculate the frustum AABB in view space
    CAABB cFrustumAABB;

    cFrustumAABB.m_cMin.setVector (FLT_MAX, FLT_MAX, FLT_MAX);
    cFrustumAABB.m_cMax.setVector (-FLT_MAX, -FLT_MAX, -FLT_MAX);

    cFrustumAABB.Add (CVector3 (fNearX, fNearY, fNearZ));
    cFrustumAABB.Add (CVector3 (fNearX, -fNearY, fNearZ));
    cFrustumAABB.Add (CVector3 (-fNearX, -fNearY, fNearZ));
    cFrustumAABB.Add (CVector3 (-fNearX, fNearY, fNearZ));

    cFrustumAABB.Add (CVector3 (fFarX, fFarY, fFarZ));
    cFrustumAABB.Add (CVector3 (fFarX, -fFarY, fFarZ));
    cFrustumAABB.Add (CVector3 (-fFarX, -fFarY, fFarZ));
    cFrustumAABB.Add (CVector3 (-fFarX, fFarY, fFarZ));

    CVector3 cCenter, cRadius;

    cCenter.Add (cFrustumAABB.m_cMin, cFrustumAABB.m_cMax);
    cCenter.MulScalar (0.5f);

    cRadius.Sub (cFrustumAABB.m_cMax, cCenter);

    vsFloat32 fRadius = cRadius.getLength ();
}

These spheres I use to generate FOUR light view and FOUR light view projection matrices. I do that as follows:

 

Remember I do this FOUR times, so at the end I have FOUR light view matrices and FOUR light projection matrices:

CalculateCascadeViewMatrix (vsUInt32 iCascade)
{
    vsAssert (m_pcDirectionalLightEditorObject != NULL);

    // calculate the cascade view matrix

    const CVector3 &rcLightDirection = m_pcDirectionalLightEditorObject->getDirection ();

    CVector3 cEye, cTarget;

    // target is center of view split frustum in world space
    cTarget.setVector (m_cCurrentCascadeBoundingSphere [iCascade].m_cCenter);

    // scale the light direction based on the shadow range
    CVector3 cScaledLightDirection (rcLightDirection);
    cScaledLightDirection.MulScalar (m_fShadowRange);

    // eye = target - (light_direction * shadow_range)
    cEye.Sub (cTarget, cScaledLightDirection);

    // generate the light view look at matrix
    LookAt (m_cViewMatrix [m_iCascade], cEye, cTarget);
}

LookAt (CMatrix4 &rcViewMatrix, const CVector3 &rcEye, const CVector3 &rcTarget)
{
    CVector3 cZAxis;

    // we look down -z axis this is why vector is reversed
    cZAxis.Sub (rcEye, rcTarget);
    cZAxis.Normalize ();

    CVector3 cYAxis (CVector3::Y_AXIS);

	// check it's not coincident with direction
    if (CMath::FAbs (cYAxis.DotProduct (cZAxis)) >= 1.0f)
    {
	  // use camera up
          cYAxis.setVector (CVector3::Z_AXIS);
    }


    // cross twice to rederive, only direction is unaltered
    CVector3 cXAxis;
        
    cXAxis.CrossProduct (cYAxis, cZAxis);
    cXAxis.Normalize ();

    cYAxis.CrossProduct (cZAxis, cXAxis);
    cYAxis.Normalize ();


    // setup the light view matrix (this places objects in light view space)
    rcViewMatrix.m [m00] = cXAxis.x;
    rcViewMatrix.m [m01] = cXAxis.y;
    rcViewMatrix.m [m02] = cXAxis.z;
    rcViewMatrix.m [m03] = -cXAxis.DotProduct (rcEye);

    rcViewMatrix.m [m10] = cYAxis.x;
    rcViewMatrix.m [m11] = cYAxis.y;
    rcViewMatrix.m [m12] = cYAxis.z;
    rcViewMatrix.m [m13] = -cYAxis.DotProduct (rcEye);

    rcViewMatrix.m [m20] = cZAxis.x;
    rcViewMatrix.m [m21] = cZAxis.y;
    rcViewMatrix.m [m22] = cZAxis.z;
    rcViewMatrix.m [m23] = -cZAxis.DotProduct (rcEye);

    rcViewMatrix.m [m30] = 0.0f;
    rcViewMatrix.m [m31] = 0.0f;
    rcViewMatrix.m [m32] = 0.0f;
    rcViewMatrix.m [m33] = 1.0f;
}

Ok now I generate FOUR orthographic projection matrices, using the radius values of the four frustum split bounding spheres I generated up above:

CalculateCascadeProjectionMatrix (vsUInt32 iCascade)
{
    vsFloat32 fWidth = m_cCascadeBoundingSphere [iCascade].m_fRadius * 2.0f;
    vsFloat32 fHeight = m_cCascadeBoundingSphere [iCascade].m_fRadius * 2.0f;

    // calculate the cascade orthographic projection matrix
    m_cProjectionMatrix [iCascade].m [m00] = 2.0f / fWidth;
    m_cProjectionMatrix [iCascade].m [m11] = 2.0f / fHeight;

    m_cProjectionMatrix [iCascade].m [m22] = -2.0f / (m_fProjectionFarZ - m_fProjectionNearZ);
    m_cProjectionMatrix [iCascade].m [m23] = -(m_fProjectionFarZ + m_fProjectionNearZ) / (m_fProjectionFarZ - m_fProjectionNearZ);
    m_cProjectionMatrix [iCascade].m [m33] = 1.0f;

    // engine specific projection matrix with render system depth range
    m_pcRenderSystem->ConvertProjectionMatrix (m_cProjectionMatrix [iCascade], m_cProjectionRSDepthMatrix [iCascade], true);
}

I also generate a rounding matrix to prevent the shadow map cascades from shimmering as the camera translates:

CalculateCascadeRoundMatrix (vsUInt32 iCascade)
{
    CVector3 cOriginShadow;
    CVector3 cOrigin (0.0f, 0.0f, 0.0f);

    // transform origin to light view projection space
    m_cLightViewProjectionMatrix [iCascade].Transform (cOriginShadow, cOrigin);

    vsFloat32 fShadowMapSize = getShadowMapSize ();

    // convert clip space to texture coordinates
    vsFloat32 fTexCoordX = cOriginShadow.x * fShadowMapSize * 0.5f;
    vsFloat32 fTexCoordY = cOriginShadow.y * fShadowMapSize * 0.5f;

    // round to the nearest whole texel
    vsFloat32 fTexCoordRoundedX = CMath::Round (fTexCoordX);
    vsFloat32 fTexCoordRoundedY = CMath::Round (fTexCoordY);

    /*
       the difference between the rounded and actual tex coordinate is the
       amount by which we need to translate the shadow matrix in order to
       cancel sub-texel movement
    */
    vsFloat32 fDX = fTexCoordRoundedX - fTexCoordX;
    vsFloat32 fDY = fTexCoordRoundedY - fTexCoordY;

    // transform fDX, fDY back to homogenous light space
    fDX /= fShadowMapSize * 0.5f;
    fDY /= fShadowMapSize * 0.5f;

    // set the rounding matrix
    m_cRoundMatrix [iCascade].m [m03] = fDX;
    m_cRoundMatrix [iCascade].m [m13] = fDY;
    m_cRoundMatrix [iCascade].m [m23] = 0.0f;
    m_cRoundMatrix [iCascade].m [m33] = 1.0f;
}

So now I have CMatrix4x4 m_cLightViewMatrix [4],  CMatrix4x4 m_cLightProjectionMatrix [4] and m_cRoundMatrix [4];

 

I loop like so and generate the shadow matrix:

 

for (iCascade = 0; iCascade < 4; iCascade++)

{

       m_cShadowMatrix [iCascade] = m_cRoundMatrix [iCascade] * m_cLightProjectionMatrix [iCascade] * m_cLightViewMatrix [iCascade];

}

 

I use m_cShadowMatrix [4] to generate the shadow maps in my shadow atlas..... So In the shader I generate the vertices of the shadow caster into word space and then multiply like so:

 

         m_cShadowMatrix * m_cWorldMatrix * vsInput.vPosition;

 

In the pixel shader I save out the z value.

 

Is my algortihm sound? Or perhaps I missed something in my understanding of the algorithm. I would appreciate any help anyone can give me as to where I could be going wrong. Also if anyone knows of any demos with source that I could run to test this algorithm I would be ever so greatful.

 

Many thanks to anyone who can help me!

0

Share this post


Link to post
Share on other sites

I am working on figuring this out...... Right now I get a blank white screen..... So it won't be very helpful for me to post this..... :)

0

Share this post


Link to post
Share on other sites

It seems doubtful that misunderstanding the overall algorithm is the source of a totally blank screen; presumably, if the algorithm as you're implementing it seems to make sense to you, you'll at least get some kind of output, even if it's not correct. That said, what you're describing (with words) seems reasonable; with the code/math, it's hard to say (at least for me) whether you've overlooked something.

 

In fact, it seems like a bit too much code for it to be practical to find a bug just by reading the whole thing, particularly since the bug may well stem from the execution of one or more seemingly-trivial steps. Since you've already got the code broken down into smaller, more manageable chunks, have you already tried testing some of those smaller parts on some fixed input to see if you get results that are consistent with what you expect?

 

My prediction is that you'll find that some small part of your implementation just doesn't do what you think it does; my experience has always been that once I get done fixing problems like that, I've already gotten enough feedback that any fundamental logic errors become immediately apparent (either that, or things just work).

0

Share this post


Link to post
Share on other sites

I find it instructive to start at the end of the pipeline and use comment codes to shut off everything.  Comment out everything in the shaders, only have what is necessary to produce some form of output.  For instance, have your vertex shader output only what is necessary to get it working, which is only position. 

Now make sure that the fragment shader is outputting only color.  If you get this far then you know that they are both setup and activated properly.  Now start moving the comment codes and re-compile as you re-add a line or two at a time. 

 

Also, you can setup a screen aligned quad and use this to display the shadow texture(s). 

If this is the part where you see nothing but a white screen, then maybe try and adjust your camera's near and far settings.  If you are using a very big spread between these two then maybe you are stretching out the z-axis enough to blank out the shadows.  For example, if you are using near = 0.001, far 10000, then you might see nothing but a white screen for the depth texture.  Try setting up some keyboard controls and adjust them starting at something like, near = .1, far = 100.   Now if you actually can see the shadows, adjust them as needed.

Then again, I would suppose that some camera setups may not exhibit this issue.

0

Share this post


Link to post
Share on other sites

One thing I have noticed in the shader is that when I multiply by the combined light view and orthographic projection matrix for a specific frustum, the generated point is NOT in the range -1.0f to 1.0f in both the x and y.

 

This seems to be the problem. Isn't the projection matrix supposed to get the coordinates in these ranges?   Has anyone else encountered this? Also if you do not think this is an issue won't it be an issue when the shadow map is sampled using these coordinates?

 

Thanks again for anyone's help

Edited by gcard28
0

Share this post


Link to post
Share on other sites

Well as expected, it was a small error on my part that prevented this from working. Once I followed the advice of breaking everything down piece by piece I was able to discover what I did wrong.

 

Thanks again for everyone's help, I now have stable cascaded shadow maps working!

Edited by gcard28
0

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  
Followers 0