级联阴影地图(Cascaded Shadow Map)实现异常问题求助
Let's break down the issues in your Cascaded Shadow Map (CSM) implementation step by step—there are several critical points that could be causing your abnormal results:
1. Incorrect Texture Compare Function (Likely the Biggest Culprit)
You’ve set your shadow texture’s comparison function to GL_EQUAL, which only passes when a fragment’s depth exactly matches the shadow map depth. For shadow mapping, we need to check if the fragment’s depth is less than or equal to the shadow map depth (meaning the fragment isn’t occluded).
Fix this in your texture initialization:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
Additionally, since you’re using GL_COMPARE_REF_TO_TEXTURE, let the GPU handle the comparison automatically instead of manually reading depths. Rewrite your shader’s readShadowMap() function to leverage hardware-accelerated comparison:
float readShadowMap(){ // Assuming FViewPos.z is negative (camera looks along -Z in view space) float viewSpaceZ = FViewPos.z; int cascadeIdx = 0; // Fix cascade index logic (matches corrected cascadeEndSpace values) for(int i = 0; i < NUM_CASCADES - 1; ++i){ if(viewSpaceZ > cascadeEndSpace[i]){ cascadeIdx = i + 1; } } // Calculate shadow space position (use world position directly) vec4 shadowPos = lightProjectionView[cascadeIdx] * vec4(FWorldPos, 1.0); vec3 projCoords = shadowPos.xyz / shadowPos.w; projCoords = projCoords * 0.5 + 0.5; // Convert to [0,1] range // Dynamic bias to fix shadow acne float bias = max(0.005 * (1.0 - dot(FNormal, normalize(lightDirection))), 0.0005); // Use hardware texture comparison float shadow = texture(map_shadow[cascadeIdx], projCoords.xy, 0, projCoords.z - bias); // Clamp shadow for out-of-bounds coordinates if(projCoords.z > 1.0) shadow = 1.0; return shadow; }
2. Broken Cascade Split & End Space Calculation
Your cascade split math has a critical error:
const float clipDist = farClip - nearClip; cascadeEndSpace[cascadeIterator] = (nearClip + splitDistance * clipDist) * -1.0f;
splitDistance is already a distance value (e.g., 0.08*(far-near)), so multiplying by clipDist gives a squared distance—this completely breaks your cascade depth ranges.
First, use a logarithmic split distribution (better for uniform shadow quality) and convert splits to view-space Z values (negative, since camera looks along -Z):
float cascadeSplits[NUM_CASCADES+1]; float near = camera->getProjection().getNear(); float far = camera->getProjection().getFar(); float lambda = 0.9f; // Adjust between 0 (linear) and 1 (logarithmic) for(int i = 0; i <= NUM_CASCADES; i++){ float fraction = (float)i / NUM_CASCADES; float splitWorld = near * pow(far/near, fraction); // Convert to view-space Z (negative value) cascadeSplits[i] = -glm::abs(glm::project(glm::vec3(0,0,splitWorld), camera->getView().getViewMatrix(), camera->getProjection().getProjectionMatrix(), glm::vec4(0,0,1,1)).z); }
Then update cascadeEndSpace to store the split values directly:
cascadeEndSpace[cascadeIterator] = cascadeSplits[cascadeIterator+1];
3. Flawed Directional Light View Matrix
Directional lights are parallel (no position), so your lookAt approach for the light view matrix is incorrect. Instead, create a matrix that aligns the light’s direction with the -Z axis (matching your orthographic matrix’s Z range):
glm::vec3 lightDir = glm::normalize(light->getDirection()); // Align light space to the light's direction glm::mat4 lightViewMatrix = glm::lookAt(glm::vec3(0), lightDir, glm::vec3(0,1,0)); lightViewMatrix = glm::inverse(lightViewMatrix); // Flip to look along -Z // Snap frustum center to texel grid to reduce shadow jitter glm::vec3 frustumCenterLS = glm::vec3(lightViewMatrix * glm::vec4(frustumCenter, 1.0)); frustumCenterLS = glm::round(frustumCenterLS / radius) * radius; lightViewMatrix = glm::translate(glm::mat4(1), -frustumCenterLS) * lightViewMatrix;
4. Incorrect Frustum Corner Adjustment
Your method of scaling frustum corner rays to fit the cascade is flawed. Instead, transform corners to view space, clamp their Z values to the cascade’s near/far, then convert back to world space:
// After converting NDC corners to world space (frustumCornersWS) glm::mat4 view = camera->getView().getViewMatrix(); glm::mat4 invView = glm::inverse(view); glm::vec3 frustumCornersVS[8]; for(int i=0; i<8; i++){ frustumCornersVS[i] = glm::vec3(view * glm::vec4(frustumCornersWS[i], 1.0)); } // Clamp Z to cascade's view-space near/far (negative values) float cascadeNearVS = cascadeSplits[cascadeIterator]; float cascadeFarVS = cascadeSplits[cascadeIterator+1]; for(int i=0; i<8; i++){ frustumCornersVS[i].z = glm::clamp(frustumCornersVS[i].z, cascadeFarVS, cascadeNearVS); frustumCornersWS[i] = glm::vec3(invView * glm::vec4(frustumCornersVS[i], 1.0)); }
5. Clean Up FBO Initialization
Your initial FBO setup binds all shadow maps to the depth attachment in a loop, which only leaves the last texture bound. Remove this loop during initialization—you already rebind the correct texture per cascade during rendering:
// Remove this from initialization: /* for (uint i = 0; i < NUM_CASCADES; i++) { ... glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[i], 0); } */
Start by fixing the texture compare function and cascade split calculation first—these are the most impactful issues. Adjust the bias value and lambda split parameter to fine-tune shadow quality for your scene.
内容的提问来源于stack exchange,提问作者Mohamed Moussa

