I'm doing cascaded shadow mapping and I have problem. From the cameras position, the splits are [camPos, 6], [camPos+6, 12], [camPos+12, 18] and [camPos+18, 100]. The "mSplitDistance" variable is the camera position + splitFarDistance (in camera space) for each split. So, standing at world pos 0.0f and looking down -Z, the values would be [-6, -12, -18, -100], and turn around and look down +Z means [6, 12, 18, 100]

The shadow mapping is only accurate in the last split, from 18 to 100; there the shadows behave as intended. In the first three splits, it is sometimes accurate and sometimes completely wrong.

I am using deferred renderering, so I am just drawing a quad and then go fragment per fragment and extract the world position / normals / etc from there.

I suspect there is somethign wrong in the lighting fragment shader. Here it is:

#version 430  

 layout(std140) uniform;  

 const float DEPTH_BIAS = 0.00005;  

 uniform UnifDirLight  
     mat4 mVPMatrix[4];     // the bias * crop * projection* view matrices for the directional light 
     float mSplitDistance[4];  
     vec4 mLightColor;  
     vec4 mLightDir;  
     vec4 mGamma;  
     vec2 mScreenSize;  
 } UnifDirLightPass;  

 layout (binding = 2) uniform sampler2D unifPositionTexture;  
 layout (binding = 3) uniform sampler2D unifNormalTexture;  
 layout (binding = 4) uniform sampler2D unifDiffuseTexture;  
 layout (binding = 6) uniform sampler2DArrayShadow unifShadowTexture;  

 out vec4 fragColor;  

 void main()  
     vec2 texcoord = gl_FragCoord.xy / UnifDirLightPass.mScreenSize; 

     vec3 worldPos = texture(unifPositionTexture, texcoord).xyz;  // world position; in world space
     vec3 normal   = normalize(texture(unifNormalTexture, texcoord).xyz);  
     vec3 diffuse  = texture(unifDiffuseTexture, texcoord).xyz;  

     int index = 3;  
     if (worldPos.z > UnifDirLightPass.mSplitDistance[0])  
         index = 0;  
     else if (worldPos.z > UnifDirLightPass.mSplitDistance[1])  
         index = 1;  
     else if (worldPos.z > UnifDirLightPass.mSplitDistance[2])  
         index = 2;  

     vec4 projCoords = UnifDirLightPass.mVPMatrix[index] * vec4(worldPos, 1.0);                                                   
     projCoords.w    = projCoords.z - DEPTH_BIAS;  
     projCoords.z    = float(index);  
     float visibilty = texture(unifShadowTexture, projCoords);  

     float angleNormal = clamp(dot(normal, UnifDirLightPass.mLightDir.xyz), 0, 1);                                                

     fragColor = vec4(diffuse, 1.0) * visibilty * angleNormal * UnifDirLightPass.mLightColor;                                     

Any ideas on what could be wrong? I suspect the might be some issue with the way I select the index, but I havn't figured it out yet.

EDIT: Screenshots to show the problem. The first three show that the shadow mapping works on and off at various distances, and the fourth shows the last split where it works as intended





EDIT2: added more screenshots with debug

The colors show the farDistance (red, green, blue, white) http://s13.postimg.org/asvxzfe3b/image.png http://s23.postimg.org/gew8jmfnv/1_color.png¨

The splits looks okay in the above, but notice how the shadows cut out still... and no shadows on the cube?

http://s21.postimg.org/gc0tn3obr/image.png http://s28.postimg.org/xlmnxe3al/2_color.png


Heres how I create the lights VP matrix used for generating shadowmap (which is then multiplied by a bias matrix before passed on to shader)

Mat4 CreateDirLightVPMatrix(const CameraFrustrum& cameraFrustrum, const Vec3& lightDir)
        const Vec3 lightDirx = glm::normalize(lightDir);
        const Vec3 perpVec1  = glm::normalize(glm::cross(lightDirx, Vec3(0.0f, 0.0f, 1.0f)));
        const Vec3 perpVec2  = glm::normalize(glm::cross(lightDirx, perpVec1));
        Mat4 lightViewMatrix(Vec4(perpVec1, 0.0f), Vec4(perpVec2, 0.0f), Vec4(lightDirx, 0.0f), Vec4(0.0f, 0.0f, 0.0f, 1.0f));

        Vec4 transf = lightViewMatrix * cameraFrustrum[0]; // cameraFrustrum is a std::array<Vec4, 8> and 0-3 is near-points and 4-7 are far points of the frustrum
        float maxZ = cameraFrustrum[0].z, minZ = cameraFrustrum[0].z;
        for (uint32_t i = 1; i < 8; i++)
            transf = lightViewMatrix * cameraFrustrum[i];
            if (cameraFrustrum[i].z > maxZ)
                maxZ = cameraFrustrum[i].z;
            if (cameraFrustrum[i].z < minZ)
                minZ = cameraFrustrum[i].z;

        const Mat4 mvp = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, maxZ, minZ) * lightViewMatrix;

        float maxX = -1000.0f, minX = 1000.0f;
        float maxY = -1000.0f, minY = 1000.0f;
        for (uint32_t i = 0; i < 8; i++)
            transf = mvp * cameraFrustrum[i];

            if (cameraFrustrum[i].x > maxX)
                maxX = cameraFrustrum[i].x;
            if (cameraFrustrum[i].x < minX)
                minX = cameraFrustrum[i].x;
            if (cameraFrustrum[i].y > maxY)
                maxY = cameraFrustrum[i].y;
            if (cameraFrustrum[i].y < minY)
                minY = cameraFrustrum[i].y;

        float scaleX = 2.0f / (maxX - minX);
        float scaleY = 2.0f / (maxY - minY);
        float offsetX = -0.5f * (maxX + minX) * scaleX;
        float offsetY = -0.5f * (maxY + minY) * scaleY;

        Mat4 cropMatrix(1.0f);
        cropMatrix[0][0] = scaleX;
        cropMatrix[1][1] = scaleY;
        cropMatrix[3][0] = offsetX;
        cropMatrix[3][1] = offsetY;

        return cropMatrix * glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, maxZ, minZ) * lightViewMatrix;

Any hints are much appreciated

  • 1
    \$\begingroup\$ Supplying some screenshots of your problem could help. \$\endgroup\$
    – Soapy
    Mar 27, 2014 at 21:18

1 Answer 1


I had similar problem to yours. And I think that you can have something wrong in two places:

  1. Bad light view projection matrix. You should make some preview of each cascade and check if shadow map looks ok. In my case after transform bounding box of frustum split by light view matrix, transformed box position in z axis was wrong and in far distances shadow map was blank. To solve that I substract near plane of main camera (from which you look at) value from min.z (min.z-=camera.near()) and far plane from max.z (max.z-=camera.far()). This gave me proper range for each cascade.
  2. Bad split distance. Even if it looks ok it could be wrong. In your screenshots you can see only scene with bad shadows or scene with colored splits. To get more conclusions it is better to multiply (or add) final fragment by color of split. It can show you more what is a real problem. I'm not sure if your solution is good but I used method presented here. So I multiply each frustum split far plane by main camera projection matrix and compare it with depth buffer content.

Hope it is clear (it is hard to me to explain that, not good in theory) and will help :)


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .