Part of the Khronos Group
OpenGL.org

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

Page 1 of 2 12 LastLast
Results 1 to 10 of 12

Thread: Cascaded shadow mapping and split values

  1. #1
    Intern Newbie
    Join Date
    Jan 2014
    Posts
    33

    Cascaded shadow mapping and split values

    Reposting, as the last topic is bugged and cant be edited.

    I'm having issues where shadows vanish at certain angles and I want to ascertain I am using the correct values to determine the split index.

    Code cpp:
    CameraFrustrum CalculateCameraFrustrum(const float minDist, const float maxDist, const Vec3& cameraPosition, const Vec3& cameraDirection, Vec4& splitDistance, const Mat4& camView)
        {
            CameraFrustrum ret = { Vec4(1.0f, 1.0f, -1.0f, 1.0f), Vec4(1.0f, -1.0f, -1.0f, 1.0f), Vec4(-1.0f, -1.0f, -1.0f, 1.0f), Vec4(-1.0f, 1.0f, -1.0f, 1.0f),
                                   Vec4(1.0f, -1.0f, 1.0f, 1.0f), Vec4(1.0f, 1.0f, 1.0f, 1.0f), Vec4(-1.0f, 1.0f, 1.0f, 1.0f), Vec4(-1.0f, -1.0f, 1.0f, 1.0f), };
     
            const Mat4 perspectiveMatrix = glm::perspective(70.0f, 1920.0f / (float)1080.0f, minDist, maxDist);
            const Mat4 invMVP = glm::inverse(perspectiveMatrix * camView);
     
            for (Vec4& v : ret)
            {
                v = invMVP * v;
                v /= v.w;
            }
     
            splitDistance= ret[4];
            splitDistance+= ret[5];
            splitDistance+= ret[6];
            splitDistance+= ret[7];
            splitDistance /= 4;
     
            return ret;
        }

    The splitDistance.Z is what I use as each split maxdistance. It is multiplied by the main cameras view matrix before being sent to the shader (as the index determination is done in view space). Is this correct?
    Last edited by Dark Photon; 04-14-2014 at 05:53 AM.

  2. #2
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,194
    I don't understand what you're doing here and why. It doesn't mesh with what you say you're trying to do.

    Why aren't you just computing your split distances in eye-space, for instance:

    Code :
          double d_uniform = mix( near, far, percent );  // Linear
          double d_log     = near * pow( ( far / near ), percent );  // Log
          d                = mix( d_uniform, d_log, blend_f ); // Practical split scheme

  3. #3
    Intern Newbie
    Join Date
    Jan 2014
    Posts
    33
    I was talking about the split distances used for the comparison in the shader; I already have the computation of split distances working.

    For example, here's the lighting fragment shader I use:

    Code :
    #version 420                                                                                                                   
     
    const float DEPTH_BIAS = 0.00005;                                                                                               
     
    layout(std140) uniform UnifDirLight                                                                                                     
    {                                                                                                                               
        mat4 mVPMatrix[4];                                                                                                          
        mat4 mCamViewMatrix;    
        vec4 mSplitDistance;                                                                                                    
        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;                                                                 
        vec3 normal   = normalize(texture(unifNormalTexture, texcoord).xyz);                                                        
        vec3 diffuse  = texture(unifDiffuseTexture, texcoord).xyz;                                                                  
     
        vec4 camPos = UnifDirLightPass.mCamViewMatrix * vec4(worldPos, 1.0);                                      
     
        int index = 3;                                                                        
        if (camPos .z > UnifDirLightPass.mSplitDistance.x)                                                                           
           index = 0;                                                                                                              
        else if (camPos .z > UnifDirLightPass.mSplitDistance.y)                                                                      
           index = 1;                                                                                                            
        else if (camPos .z > UnifDirLightPass.mSplitDistance.z)                                                                      
           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;                                    
    }

    So my idea is that splitDistance.z for each splits frustrum (from 1st post) is what I use for mSplitDistance.x/y/z (depending on split). Is this the correct values to compare against?

  4. #4
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,194
    Quote Originally Posted by TheKaiser View Post
    I was talking about the split distances used for the comparison in the shader; I already have the computation of split distances working.
    Ok, I'll take your word for that. The compute logic bothers me.

    I'm having issues where shadows vanish at certain angles and I want to ascertain I am using the correct values to determine the split index.

    Code glsl:
    ...
        int index = 3;                                                                        
        if (camPos .z > UnifDirLightPass.mSplitDistance.x)                                                                           
           index = 0;                                                                                                              
        else if (camPos .z > UnifDirLightPass.mSplitDistance.y)                                                                      
           index = 1;                                                                                                            
        else if (camPos .z > UnifDirLightPass.mSplitDistance.z)                                                                      
           index = 2;                                                                                                              
    ...
    Assuming the distances are correct, this looks reasonable. Have you printed the values to ensure that they are correct?

  5. #5
    Intern Newbie
    Join Date
    Jan 2014
    Posts
    33
    Quote Originally Posted by Dark Photon View Post
    Ok, I'll take your word for that. The compute logic bothers me.
    I know it's confusing, I'm using the term "split distances" in two places, the first one (which I assumed you were refering to in your first post and code) is this:

    Code :
    void CalculateShadowmapCascades(std::array<float, gNumShadowmapCascades>& nearDistArr, std::array<float, gNumShadowmapCascades>& farDistArr, const float nearDist, const float farDist)
        {
            const float splitWeight = 0.75f;
            const float ratio = nearDist / farDist;
     
            nearDistArr[0] = nearDist;
            for (uint8_t index = 1; index < gNumShadowmapCascades; index++)
            {
                const float si = index / (float)gNumShadowmapCascades;
     
                nearDistArr[index] = splitWeight * (nearDist * powf(ratio, si)) + (1 - splitWeight) * (nearDist + (farDist - nearDist) * si);
                farDistArr[index - 1] = nearDistArr[index] * 1.005f;
            }
            farDistArr[gNumShadowmapCascades - 1] = farDist;
        }

    which I borrowed from the nvidia cascaded shadow map sample, and it works fine, for example for near_z = 0.1f and far_z = 100.0f I get the splits {6, 12, 18, 100} which looks reasonable enough to me.


    The second one is the uniform I send to the shader I posted, since I can't just use the unmodified values from CalculateShadowmapCascades() directly. I reason that for each split, while constructing that splits frustrum I sample the average Z of the far corners of the frustrum like this:

    Code :
    splitDistance= ret[4];
    splitDistance+= ret[5];
    splitDistance+= ret[6];
    splitDistance+= ret[7];
    splitDistance /= 4;

    Code :
    splitDistance= lighting.mCameraViewMatrix * splitDistance;
    splitDistances[cascadeIndex] = splitDistance.z;       // splitDistances is sent to the shader

    And that is what I'm not sure if I'm doing correctly. The nvidia sample uses a different comparison for the splits, like this:

    Code :
    far_bound[i] = 0.5f*(-f[i].fard*cam_proj[10]+cam_proj[14])/f[i].fard + 0.5f;

    But me doing deferred shading figured it would be much easier and less code to do the comparison in view space?
    Last edited by TheKaiser; 04-15-2014 at 01:15 AM.

  6. #6
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,194
    Quote Originally Posted by TheKaiser View Post
    The second one is the uniform I send to the shader I posted, since I can't just use the unmodified values from CalculateShadowmapCascades() directly.
    Why not? I do, for forward and deferred shading.

    I reason that for each split, while constructing that splits frustrum I sample the average Z of the far corners of the frustrum like this:

    Code :
    splitDistance= ret[4];
    splitDistance+= ret[5];
    splitDistance+= ret[6];
    splitDistance+= ret[7];
    splitDistance /= 4;

    Code :
    splitDistance= lighting.mCameraViewMatrix * splitDistance;
    splitDistances[cascadeIndex] = splitDistance.z;       // splitDistances is sent to the shader
    Yeah, this is what's confusing to me. First, averaging a bunch of 4D (3D actually) points doesn't make sense to me. Second, the points you're averaging are just the corners of the far clip in NDC. Once you average that, you get a point, not split distances. Third, you want the eye-space Z split distances (which you already computed in CalculateShadowmapCascades, but whatever...), so you really just want to pass through an inverse PROJECTION transform to get that. If you include the VIEWING transform, as you are here, you're going to end up in world space, and that's generally not what you want to work with in your shader. Eye space is preferred for a number of reasons (you call it an MVP here too, which it's not).
    Last edited by Dark Photon; 04-15-2014 at 05:35 AM.

  7. #7
    Intern Newbie
    Join Date
    Jan 2014
    Posts
    33
    Quote Originally Posted by Dark Photon View Post
    Yeah, this is what's confusing to me. First, averaging a bunch of 4D (3D actually) points doesn't make sense to me. Second, the points you're averaging are just the corners of the far clip in NDC.
    Isn't the resulting corners in world-space, after multiplying by inverse viewProj matrix and dividing by W?

    Quote Originally Posted by Dark Photon View Post
    Once you average that, you get a point, not split distances. Third, you want the eye-space Z split distances (which you already computed in CalculateShadowmapCascades, but whatever...)
    OK, as eye-space originates from origo and looks down the -Z axis, {-6, -12, -18, -100} would work to use directly in the shader?

    Quote Originally Posted by Dark Photon View Post
    so you really just want to pass through an inverse PROJECTION transform to get that. If you include the VIEWING transform, as you are here, you're going to end up in world space
    I store my positions in world space (for now; I will probably change it later, but the moment, I'm using world space). Dosn't multiplying the world space by the cameras view matrix put the position in eye space, as below:

    Code :
    vec4 camPos = UnifDirLightPass.mCamViewMatrix * vec4(worldPos, 1.0);

    If so, why would I need the inverse projection (of the main camera?) matrix?

  8. #8
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,194
    Quote Originally Posted by TheKaiser View Post
    Isn't the resulting corners in world-space, after multiplying by inverse viewProj matrix and dividing by W?
    Should be. That pertains to CameraFrustum ret. What I'm talking about is the splitDistance calc below that.

    OK, as eye-space originates from origo and looks down the -Z axis, {-6, -12, -18, -100} would work to use directly in the shader?
    Sure thing. Just make sure you compare an eye-space frag position.

    Also (related issue you didn't ask about), be sure that all fragments in the quad you're rasterizing chose the same split. If you don't, you'll get huge texcoord derivatives, which'll mess up aniso filtering if you have that enabled (or just disable it).


    I store my positions in world space (for now; I will probably change it later, but the moment, I'm using world space). Dosn't multiplying the world space by the cameras view matrix put the position in eye space, as below:

    Code :
    vec4 camPos = UnifDirLightPass.mCamViewMatrix * vec4(worldPos, 1.0);
    Sure.

    If so, why would I need the inverse projection (of the main camera?) matrix?
    You typically wouldn't. I was surprised by seeing this in your C++ code to rederive the splits. I'd just keep them in eye-space and pass them in. You're doing deferred so you're probably going to be recomputing the fragment eye-space position in the shader for lighting anyway, if you're not storing it in the G-buffer that is.

  9. #9
    Intern Newbie
    Join Date
    Jan 2014
    Posts
    33
    You are right, it works just fine using the values from CalculateShadowmapCascades() without any other modification

    So I guess the original problem I had didnt have to do with that.

    I've got the cascaded shadow mapping to work at most angles / distances... for example, see below



    here's the corresponding split debug colors (r->g->b->white)



    But, just looking a little further down, the shadows gets clipped:



    and the debug view:



    Its really strange, and at larger distances its no problem at all

    http://s22.postimg.org/um3tmjwn3/image.png

    Any ideas what could be wrong and causing the shadow clipping at certain distances and angles?

    Here's how I go about creating the lights crop-view-projection by the way:

    Code :
        Mat4 CreateDirLightVPMatrix(const CameraFrustrum& cameraFrustrum, const Vec3& lightDir)
        {
            Mat4 lightViewMatrix = glm::lookAt(Vec3(0.0f), -glm::normalize(lightDir), Vec3(0.0f, 1.0f, 0.0f));
     
            Vec4 transf = lightViewMatrix * cameraFrustrum[0];
            float maxZ = transf.z, minZ = transf.z;
            float maxX = transf.x, minX = transf.x;
            float maxY = transf.y, minY = transf.y;
            for (uint32_t i = 1; i < 8; i++)
            {
                transf = lightViewMatrix * cameraFrustrum[i];
     
                if (transf.z > maxZ) maxZ = transf.z;
                if (transf.z < minZ) minZ = transf.z;
                if (transf.x > maxX) maxX = transf.x;
                if (transf.x < minX) minX = transf.x;
                if (transf.y > maxY) maxY = transf.y;
                if (transf.y < minY) minY = transf.y;
            }
     
            Mat4 viewMatrix(lightViewMatrix);
            viewMatrix[3][0] = -(minX + maxX) * 0.5f;
            viewMatrix[3][1] = -(minY + maxY) * 0.5f;
            viewMatrix[3][2] = -(minZ + maxZ) * 0.5f;
            viewMatrix[0][3] = 0.0f;
            viewMatrix[1][3] = 0.0f;
            viewMatrix[2][3] = 0.0f;
            viewMatrix[3][3] = 1.0f;
     
            Vec3 halfExtents((maxX - minX) * 0.5, (maxY - minY) * 0.5, (maxZ - minZ) * 0.5);
     
            return glm::ortho(-halfExtents.x, halfExtents.x, -halfExtents.y, halfExtents.y, halfExtents.z, -halfExtents.y) * viewMatrix;
        }
    Last edited by TheKaiser; 04-17-2014 at 03:23 PM.

  10. #10
    Senior Member OpenGL Guru Dark Photon's Avatar
    Join Date
    Oct 2004
    Location
    Druidia
    Posts
    3,194
    Ok, looks like the artifact is probably on a split boundary.

    First, are you doing any culling to decide what objects to cast into what split shadow maps? If so, turn that off for now and render all scene objects into all splits. Does the problem go away?

    In general, think about tests you can run to whack off big parts of the problem space and help you narrow down where the bug must be.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •