0
\$\begingroup\$

I am implementing the cascaded shadow map technique with OpenGL and C++, I have some strange behaviors I think my issue with calculating the frustum orthographic matrices, corners and cascaded ends, I got shadows but the result changes to unexpected one with camera moving. the first problem is strange terrain shadows : enter image description here

the second one is with moving the camera down as:

enter image description here

the following code shows how I render to the shadow maps and calculate the light view orthographic projections and cascaded ends:

void Lights::CascadedShadow::RenderToShadowMap(GLint programID,std::vector<Geometry::Object*> &objects) {
glUseProgram(programID);

glEnable(GL_DEPTH_TEST);
glCullFace(GL_FRONT);

GLfloat nearClip = camera->getProjection().getNear();
GLfloat farClip = camera->getProjection().getFar();

GLfloat cascadeSplits[NUM_CASCADES] = {};

//Between 0 and 1, change in order to see the results
GLfloat lambda = 0.85f;

//Between 0 and 1, change these to check the results
GLfloat minDistance = 0.0f;
GLfloat maxDistance = 1.0f;

GLfloat clipRange = farClip - nearClip;

GLfloat minZ = nearClip + minDistance * clipRange;
GLfloat maxZ = nearClip + maxDistance * clipRange;

GLfloat range = maxZ - minZ;
GLfloat ratio = maxZ / minZ;

for (unsigned int i = 0; i < NUM_CASCADES; ++i)
{
    GLfloat p = (i + 1) / static_cast<GLfloat>(NUM_CASCADES);
    GLfloat log = minZ * std::pow(ratio, p);
    GLfloat uniform = minZ + range * p;
    GLfloat d = lambda * (log - uniform) + uniform;
    cascadeSplits[i] = (d - nearClip) / clipRange;
}

glm::mat4 invViewProj = glm::inverse(
        camera->getProjection().getProjectionMatrix() * camera->getView().getViewMatrix());
for (unsigned int cascadeIterator = 0; cascadeIterator < NUM_CASCADES; ++cascadeIterator) {
    GLfloat prevSplitDistance =
            cascadeIterator == 0 ? 0 : cascadeSplits[cascadeIterator - 1];
    GLfloat splitDistance = cascadeSplits[cascadeIterator];

    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),};

    for (unsigned int i = 0; i < 8; ++i) {
        glm::vec4 inversePoint = invViewProj * glm::vec4(frustumCornersWS[i], 1.0f);
        frustumCornersWS[i] = glm::vec3(inversePoint / inversePoint.w);
    }

    for (unsigned int i = 0; i < 4; ++i) {
        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;
    }

    glm::vec3 frustumCenter = glm::vec3(0.0f);
    for (unsigned int i = 0; i < 8; ++i)
        frustumCenter += frustumCornersWS[i];
    frustumCenter /= 8.0f;

    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;

    glm::vec3 maxExtents = glm::vec3(radius, radius, radius);
    glm::vec3 minExtents = -maxExtents;

    //Position the viewmatrix looking down the center of the frustum with an arbitrary lighht direction
    glm::vec3 lightDirection =
            frustumCenter - glm::normalize(light->getDirection()) * -minExtents.z;
    glm::mat4 lightViewMatrix = glm::mat4(1.0f);
    lightViewMatrix = glm::lookAt(lightDirection, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f));

    glm::vec3 cascadeExtents = maxExtents - minExtents;

    glm::mat4 lightOrthoMatrix = glm::ortho(minExtents.x, maxExtents.x, minExtents.y,
                                            maxExtents.y, 0.0f, cascadeExtents.z);

    // The rounding matrix that ensures that shadow edges do not shimmer
    glm::mat4 shadowMatrix = lightOrthoMatrix * lightViewMatrix;
    glm::vec4 shadowOrigin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    shadowOrigin = shadowMatrix * shadowOrigin;
    float mShadowMapSize = static_cast<float>(size);
    shadowOrigin = shadowOrigin * mShadowMapSize / 2.0f;

    glm::vec4 roundedOrigin = glm::round(shadowOrigin);
    glm::vec4 roundOffset = roundedOrigin - shadowOrigin;
    roundOffset = roundOffset * 2.0f / mShadowMapSize;
    roundOffset.z = 0.0f;
    roundOffset.w = 0.0f;

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

    //Store the split distances and the relevant matrices
    const float clipDist = farClip - nearClip;
    cascadeEndSpace[cascadeIterator] = (nearClip + splitDistance * clipDist) * -1.0f;

    lightProjectionView[cascadeIterator] = lightOrthoMatrix * lightViewMatrix;

    glViewport(0, 0, mShadowMapSize, mShadowMapSize);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[cascadeIterator],0);

    glClear(GL_DEPTH_BUFFER_BIT);
    glUniformMatrix4fv(glGetUniformLocation(programID, "lightProjectionView"), 1, GL_FALSE,
                       glm::value_ptr(lightProjectionView[cascadeIterator]));
    for (Geometry::Object *object:objects) {
        object->RenderToDepth(programID);
    }
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glCullFace(GL_BACK);
glUseProgram(0);
}

The lambda value affected the final result with the camera far value, for example if the far value is 1000 the shadow appears with the lambda value near 1, if it is 250 the lambda should be 0.2.

I understand the idea of the cascaded shadow map but I don't write this method so I am so confused about the calculations.

Here is the fragment shader shadow calculation method:

        "float readShadowMap(vec3 normal,vec3 lightDir){"
            "    int cascadeIdx = 0;"
            "    for(int i = 0; i < NUM_CASCADES - 1; ++i){"
            "        if(FViewPos.z < cascadeEndSpace[i]){"
            "            cascadeIdx = i + 1;"
            "        }"
            "    }"
            "    vec3 projCoords = LightSpacePos[cascadeIdx].xyz / LightSpacePos[cascadeIdx].w;"
            "    projCoords = projCoords * 0.5 + 0.5;"
            "    float currentDepth = projCoords.z;"
            "    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);"
            "    float closestDepth  = 0.0f;"
            "    float pcfDepth=0.0f;"
            "    float shadow = 0.0f;"
            "    if(cascadeIdx == 0){"
            "       closestDepth  = texture(map_shadow[0], projCoords.xy).r;"
            "       ivec2 iv=textureSize(map_shadow[0], 0);"
            "       vec2 texelSize = 1.0 / vec2(iv.x,iv.y);"
            "       for(int x = -1; x <= 1; ++x){"
            "           for(int y = -1; y <= 1; ++y){"
            "               pcfDepth = texture(map_shadow[0], projCoords.xy + vec2(x, y) * texelSize).r;"
            "               shadow += currentDepth - bias > pcfDepth  ? 1.0 : 0.0;"
            "           }"
            "       }"
            "    }"
            "    else if(cascadeIdx == 1){"
            "       closestDepth  = texture(map_shadow[1], projCoords.xy).r;"
            "       ivec2 iv=textureSize(map_shadow[1], 0);"
            "       vec2 texelSize = 1.0 / vec2(iv.x,iv.y);"
            "       for(int x = -1; x <= 1; ++x){"
            "           for(int y = -1; y <= 1; ++y){"
            "               pcfDepth = texture(map_shadow[1], projCoords.xy + vec2(x, y) * texelSize).r;"
            "               shadow += currentDepth - bias > pcfDepth  ? 1.0 : 0.0;"
            "           }"
            "       }"
            "    }"
            "    else if(cascadeIdx == 2){"
            "       closestDepth  = texture(map_shadow[2], projCoords.xy).r;"
            "       ivec2 iv=textureSize(map_shadow[2], 0);"
            "       vec2 texelSize = 1.0 / vec2(iv.x,iv.y);"
            "       for(int x = -1; x <= 1; ++x){"
            "           for(int y = -1; y <= 1; ++y){"
            "               pcfDepth = texture(map_shadow[2], projCoords.xy + vec2(x, y) * texelSize).r;"
            "               shadow += currentDepth - bias > pcfDepth  ? 1.0 : 0.0;"
            "           }"
            "       }"
            "    }"
            "    shadow /= 9.0;"
            "    if(projCoords.z > 1.0)"
            "        shadow = 0.0;"
            "    return shadow;"
        "}"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.