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.
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);