4

I am trying to wrap my head around the cascaded shadow maps and I encountered an issue with near field culling. Basically objects behind the camera get culled and the shadows for that split get culled as well. Take a look at the following image.

enter image description here

I found a way to fix this by doing glEnable(GL_DEPTH_CLAMP); before the shadow pass but now I am having some other issues where we need the far and near clips culled. Sponza model is one example, it seems like everything inside ends up in shadow because the walls appear inside shadow map even when the light should have reach the objects inside.

How do you guys deal with this issue?

Here is how I generate the projection-view matrix for the shadow map

float clipRange = camera->getFarClipPlane() - camera->getNearClipPlane();
    float minZ = camera->getNearClipPlane();
    float maxZ = camera->getNearClipPlane() + clipRange;
    float range = maxZ - minZ;
    float ratio = maxZ / minZ;
    float cascadeSplitLambda = 0.95f;
    // Calculate split depths based on view camera furstum
    // Based on method presentd in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
    cascadeSplits.clear();
    cascadeSplitsNew.clear();
    for (int i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
        auto i_float = static_cast<GLfloat>(i);
        float p = (i_float + 1.f) / static_cast<float>(SHADOW_MAP_CASCADE_COUNT);
        float log = minZ * std::pow(ratio, p);
        float uniform = minZ + range * p;
        float d = cascadeSplitLambda * (log - uniform) + uniform;
        GLfloat cascadeSplit = (d - camera->getNearClipPlane()) / clipRange;
        cascadeSplits.push_back(cascadeSplit);
    }

    directionalLightShadowVPMatrixes.clear();
    for (int casscadeIndex = 0; casscadeIndex < SHADOW_MAP_CASCADE_COUNT; casscadeIndex++) {
        glm::vec3 frustumCornersWS[8] = {
                glm::vec3(-1.0f, 1.0f, -1.0f),
                glm::vec3( 1.0f, 1.0f, -1.0f),
                glm::vec3( 1.0f, -1.0f, -1.0f),
                glm::vec3(-1.0f, -1.0f, -1.0f),
                glm::vec3(-1.0f, 1.0f, 1.0f),
                glm::vec3( 1.0f, 1.0f, 1.0f),
                glm::vec3( 1.0f, -1.0f, 1.0f),
                glm::vec3(-1.0f, -1.0f, 1.0f),
        };

        glm::mat4 invViewProj = glm::inverse(camera->getProjectionMatrix() * camera->getViewMatrix());
        for (unsigned int i = 0; i < 8; i++) {
            glm::vec4 inversePoint = invViewProj * glm::vec4(frustumCornersWS[i], 1.0f);
            frustumCornersWS[i] = inversePoint / inversePoint.w;

            //std::cout << "FrustumCorner oldValues" << i << " is: " << glm::to_string(frustumCornersWS[i]) << std::endl;
        }

        // TODO: Modify this if doing all 4 splits

        for (unsigned int i = 0; i < 4; i++) {
            GLfloat prevSplitDistance;
            if (casscadeIndex == 0) {
                prevSplitDistance = 0.1f / 1000.f;
            } else {
                prevSplitDistance = cascadeSplits.at(casscadeIndex - 1);
            }
            GLfloat splitDistance = cascadeSplits.at(casscadeIndex);
            glm::vec3 cornerRay = frustumCornersWS[i + 4] - frustumCornersWS[i];
            glm::vec3 nearCornerRay = cornerRay * prevSplitDistance;
            glm::vec3 farCornerRay = cornerRay * splitDistance;
            frustumCornersWS[i + 4] = frustumCornersWS[i] + farCornerRay;
            frustumCornersWS[i] = frustumCornersWS[i] + nearCornerRay;
        }


        for (unsigned int i = 0; i < 8; i++) {
           // std::cout << "FrustumCorner new values" << i << " is: " << glm::to_string(frustumCornersWS[i]) << std::endl;
        }


        // Finding frustum center by adding 8 corners and dividing by 8
        glm::vec3 frustumCenter = glm::vec3(0.0f);
        for (unsigned int i = 0; i < 8; i++) {
            frustumCenter += frustumCornersWS[i];
        }
        frustumCenter /= 8.0f;

        //std::cout << "FrustumCenter is at: " << glm::to_string(frustumCenter) << std::endl;

        // Building a sphere that encapsulates the view frustum
        GLfloat radius = 0.0f;
        for (unsigned int i = 0; i < 8; i++) {
            GLfloat distance = glm::length(frustumCornersWS[i] - frustumCenter);
            radius = glm::max(radius, distance);
        }
        radius = std::ceil(radius * 16.0f) / 16.0f;

       // std::cout << "Radius is: " << radius << std::endl;


        glm::vec3 lightDirection = frustumCenter - glm::normalize(lights.at(0)->getDirection()) * radius;
        glm::mat4 lightViewMatrix = glm::mat4(1.0f);
        lightViewMatrix = glm::lookAt(lightDirection, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f));

        glm::mat4 lightOrthoMatrix = glm::ortho(-radius, radius, -radius, radius, 0.f, 2.f * radius);

        // TODO: Maybe calculate splits like this (I believe this is in world pos)
        //std::cout << "split is: " << (camera->getNearClipPlane() + cascadeSplits.at(casscadeIndex) * clipRange) * -1.0f << std::endl;
        cascadeSplitsNew.push_back((camera->getNearClipPlane() + cascadeSplits.at(casscadeIndex) * clipRange));

        // This is used to avoid shimmering when we move the camera
        glm::mat4 shadowMatrix = lightOrthoMatrix * lightViewMatrix;
        glm::vec4 shadowOrigin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
        shadowOrigin = shadowMatrix * shadowOrigin;
        shadowOrigin = shadowOrigin * static_cast<GLfloat>(directionalLightShadowFrameBuffer->getResolutionX()) / 2.0f;

        glm::vec4 roundedOrigin = glm::round(shadowOrigin);
        glm::vec4 roundOffset = roundedOrigin - shadowOrigin;
        roundOffset = roundOffset * 2.0f / static_cast<GLfloat>(directionalLightShadowFrameBuffer->getResolutionX());
        roundOffset.z = 0.0f;
        roundOffset.w = 0.0f;

        glm::mat4 shadowProj = lightOrthoMatrix;
        shadowProj[3] += roundOffset;
        lightOrthoMatrix = shadowProj;

        glm::mat4 directionalLightShadowVPMatrix = lightOrthoMatrix * lightViewMatrix;
        directionalLightShadowVPMatrixes.push_back(directionalLightShadowVPMatrix);
genpfault
  • 51,148
  • 11
  • 85
  • 139
Saik
  • 993
  • 1
  • 16
  • 40

1 Answers1

0

As you have found out, depth clamping is one way to solve this issue.

Another way is to move your light view matrix away from your frustumCenter.

First, your glm::lookAt isn't right. The first parameter is the position of the eyes, not the light direction. The light view matrix should follow you when you move the camera.

To not to clip the tree in your scene you need to move the eyes position away from the center. For example:

lightViewMatrix = glm::lookAt(frustumCenter - lightDirection * factor, ...);

You may need to increase the far plane distance of the last parameter in the glm::ortho() function.

Depending on how your fragment shader is calculating the shadow maps, this should not cause it to stop working, as long as the middle point of the depth map is at the exact center (i.e. value of 0.5f in your depth map should point to the frustumCenter).

You may also calculate the near and far plane frustum based on your scene bounds as described on this MSDN page: Common Techniques to Improve Shadow Depth Maps (section: Light Frustum Intersected with Scene to Calculate Near and Far Planes)

In my own Vulkan engine, I have followed shadowmappingcascade example by SaschaWillems and have ended up with something as the following:

float factor = 16.0f;

glm::lookAt(frustumCenter - lightDir * -minExtents.z * factor, frustumCenter, {0.0f, 1.0f, 0.0f});

glm::orthoLH_ZO(minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, (maxExtents.z - minExtents.z) * factor));

This isn't the proper way to solve this issue, but it works. The recommended approach is the one described in the MSDN page.