How to implement Cascaded Shadow Map technique ( simply )?

Started by
7 comments, last by MD Forhad Reja 7 years, 11 months ago

After being success in implementing a decent shadow map for my game engine ( with a great help of this community ), I'm very very keen to jump into some advance shadow map techniques like Cascaded Shadow Map(CSM), Parallel-Split Shadow Map(PSSM), Variance Shadow Map(VSM) etc. Cause I want a shadow technique that will be able to cover big area in game very efficiently. After doing a lots of search in Internet, I found CSM most useful. I may be wrong but this technique has really impressed me. And I really want to implement it.

First looked into the Cascaded Shadow Map sample of Microsoft DirectX SDK(June 2010) called CascadedShadowMaps11 to get the basic idea. But what I actually understand is nothing. Uff! This sample is really really complex ( for me ). What actually bother me is the DX11 and XNA stuffs.

So I started doing something simpler in that way -

I render ( only depth ) all the shadow casting object three times into three different render target texture. Every RT texture has its own View and Projection matrix ( Actually a shadow camera ). three times means I just simply render them inside a for loop and I call the following function inside the loop to update the shadow camera for every RT texture -


void SceneManager::UpdateShadowCamera(Camera* cam, Light* light, Camera* texCam, size_t cascadeIndex)
{
    // Build View Matrix

    D3DXVECTOR3 dir = -light->mSunDirection; 
    ::T3DNormalize(&dir);

    D3DXVECTOR3 pos(-3, 30, -57);

    //-------------------------------
    D3DXVECTOR3 up(0, 1, 0);
    // Check it's not coincident with dir
    if (abs(DotProduct(&up, &dir)) >= 1.0f)
    {
        // Use camera up
        up = D3DXVECTOR3(0, 0, 1);
    }

    // cross twice to rederive, only direction is unaltered
    D3DXVECTOR3 left = CrossProduct(&dir, &up); 
    Normalize(&left);

    up = CrossProduct(&dir, &left); 
    Normalize(&up);

    // Derive quaternion from axes
    Quaternion q;
    q.FromAxes(left, up, dir);
    q.Normalize();

    texCam->matView = MakeViewMatrix(pos, q, NULL); 

    ViewFrustum* frustum = cam->mFrustum;
    D3DXVECTOR3 camPos = cam->GetPosition();

    float mSplitPoints[4] = { 1.0f, 20.0f, 80.0f, 300.0f };

    // Apply the right clip distance.
    float nearSplit = mSplitPoints[cascadeIndex];
    float farSplit = mSplitPoints[cascadeIndex + 1];

    // Build Projection Matrix
    texCam->matProjection = MakeProjectionOrtho(
       100, 100, // width and height
       1.0f, 100.0f // near and far
       );

    // Now I'm stuck here!
} 

So basically, this function has got four parameters. The cam is the main view camera, the light is carrying only a direction. texCam is the light/shadow camera. cascadeIndex is the for loop current index. Inside this function, I just did some basic calculation what I do for a simple shadow map.

And also, my shadow receiver pixel shader uses following code to determine which shadow map to use now -


float shadow = 1.0f;
    
    if ( pixelDepth < 20 ) {
		shadow = tex2Dproj(gDepthMap, SMPos);
        splitCol = float4(0.1, 0.0, 0.0, 1.0);
    }
    else if ( pixelDepth < 80 ) {
		shadow = tex2Dproj(gDepthMap2, SMPos2);
        splitCol = float4(0.0, 0.1, 0.0, 1.0);
    }
    else if ( pixelDepth < 300 ) {
		shadow = tex2Dproj(gDepthMap3, SMPos3);
        splitCol = float4(0.0, 0.0, 0.1, 1.0);
    }

pixelDepth is screenSpace Z value passed by vertex shader. Both in main code and pixel shader, split distances are hand-coded for the testing purpose. Those codes are working perfectly. I mean split colors are showing perfectly according to their distance.

That's it! I could not go anymore. I don't know how to calculate proper projection and view matrix for Cascaded shadow map. I think that my view matrix calculation is just fine. Or maybe not?

All the samples in Internet are very tough for me to understand. Some of them only calculates different projection matrix for each texture and the view matrix is same. and some of them recalculates both! Things are getting horrible. So I really need help.

Please help.

Advertisement

"pos" has to be different for each of the cascade splits. You want to place a shadow caster all along the player's view direction.

"First looked into the Cascaded Shadow Map sample of Microsoft DirectX SDK(June 2010) called CascadedShadowMaps11 to get the basic idea. But what I actually understand is nothing. Uff! This sample is really really complex ( for me )."

..Not to come off sounding like an ass, but therein lies the issue. Its seems like every day a post pops up sound like this.. "I'm trying to do X and I looked at this/that example to learn....". Without understanding the underlying theory/basis of how Cascade Shadow Maps works...looking at the code will only serve confuse and potentially discourage you from whatever goal you intend to achieve.

I you truly understand how shadow mapping works in theory ( I'm assuming you do since you already did an implementation ), then it should be relatively easy to pick up the basics of cascade shadow mapping. Its just an extension of your vanilla shadow map with a few caveat.

There are a ton of resources available on the topic:

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307%28v=vs.85%29.aspx
http://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html

..and much more.





"First looked into the Cascaded Shadow Map sample of Microsoft DirectX SDK(June 2010) called CascadedShadowMaps11 to get the basic idea. But what I actually understand is nothing. Uff! This sample is really really complex ( for me )."

..Not to come off sounding like an ass, but therein lies the issue. Its seems like every day a post pops up sound like this.. "I'm trying to do X and I looked at this/that example to learn....". Without understanding the underlying theory/basis of how Cascade Shadow Maps works...looking at the code will only serve confuse and potentially discourage you from whatever goal you intend to achieve.

I you truly understand how shadow mapping works in theory ( I'm assuming you do since you already did an implementation ), then it should be relatively easy to pick up the basics of cascade shadow mapping. Its just an extension of your vanilla shadow map with a few caveat.

There are a ton of resources available on the topic:

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307%28v=vs.85%29.aspx
http://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html

..and much more.





Of course I did look at those resources but the fact is I'm struggling to understand how I can implement them practically and that is why I need help.. I really need help.

Have you implemented basic shadow mapping yet?

Have you implemented basic shadow mapping yet?

Yes, I have. In fact my posted codes work just fine as a basic shadow map.

Here is the screenshot -

http://postimg.org/image/wlo7nircr/be298c65/

Note that the above screenshot is also showing split colors. I think they are just fine?

Isn't there anybody to help me? :unsure:

Actually the Nvidia paper describes it pretty well on the 7th page.

if you have a directional light, you can calculate a view matrix from zero to the light direction (light position is actually irrelevant).

When you have this generic view matrix, you calculate the camera's frustum corners for each split. These corners are then transformed into the light's space (with the previous view matrix and a generic ortho projection matrix). A light-space bounding box is calculated from the transformed corners. Then the C crop matrix is calculated (as shown in the Nvidia paper) and the projection matrix becomes P = Pz * C, where Pz is an orto proj matrix with the calculated min and max z values.

So in short:

- calculate view once

- calculate proj and view*proj for each split

- render shadow map for each split with the calculated viewProj matrix

- use the shadow map textures to render the shadowed scene

But this is described better in the Nvidia paper, check it out again! You can find your answers there!

sorry for my bad english

Actually the Nvidia paper describes it pretty well on the 7th page.

if you have a directional light, you can calculate a view matrix from zero to the light direction (light position is actually irrelevant).

When you have this generic view matrix, you calculate the camera's frustum corners for each split. These corners are then transformed into the light's space (with the previous view matrix and a generic ortho projection matrix). A light-space bounding box is calculated from the transformed corners. Then the C crop matrix is calculated (as shown in the Nvidia paper) and the projection matrix becomes P = Pz * C, where Pz is an orto proj matrix with the calculated min and max z values.

So in short:

- calculate view once

- calculate proj and view*proj for each split

- render shadow map for each split with the calculated viewProj matrix

- use the shadow map textures to render the shadowed scene

But this is described better in the Nvidia paper, check it out again! You can find your answers there!

Many thanks for your kind information. Can you give me the link of Nividia paper?

Thanks again.

This topic is closed to new replies.

Advertisement