Reputation: 103
I'm implementing the Cascaded Shadow Map technique, I get unexpected result
First I initialize the buffer and the textures:
glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glGenTextures(NUM_CASCADES, m_shadowMap);
for (uint i = 0; i < NUM_CASCADES; i++) {
glBindTexture(GL_TEXTURE_2D, m_shadowMap[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, size, size, 0, GL_DEPTH_COMPONENT,
GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_EQUAL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[i], 0);
}
glDrawBuffers(1, GL_NONE);
glReadBuffer(GL_NONE);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Then I render to depth buffer: here I calculate the light projection view matrix for each cascade then render the scene from light perspective.
glUseProgram(programID);
GLfloat minDistance = 0.0f;
GLfloat nearClip = camera->getProjection().getNear();
GLfloat farClip = camera->getProjection().getFar();
GLfloat cascadeSplits[NUM_CASCADES+1] = {nearClip,(farClip-nearClip)*0.08f,(farClip-nearClip)*0.2f,(farClip-nearClip)*0.6f,farClip};
for (unsigned int cascadeIterator = 0; cascadeIterator < NUM_CASCADES; ++cascadeIterator) {
GLfloat prevSplitDistance =
cascadeIterator == 0 ? minDistance : 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),};
glm::mat4 invViewProj = glm::inverse(
camera->getProjection().getProjectionMatrix() * camera->getView().getViewMatrix());
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;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
glViewport(0, 0, mShadowMapSize, mShadowMapSize);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[cascadeIterator],0);
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glCullFace(GL_FRONT);
glUniformMatrix4fv(glGetUniformLocation(programID, "lightProjectionView"), 1, GL_FALSE,
glm::value_ptr(lightProjectionView[cascadeIterator]));
for (Geometry::Object *object:objects) {
object->RenderToDepth(programID);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
glUseProgram(0);
Finally I render the scene:
for (uint i = 0; i < NUM_CASCADES; i++) {
glActiveTexture(GL_TEXTURE4 + i);
glBindTexture(GL_TEXTURE_2D, m_shadowMap[i]);
const char *shadowLoc = (const char *) ("map_shadow[" + Tools::ToString(i)+"]").c_str();
glUniform1i(glGetUniformLocation(programID, shadowLoc), 4 + i);
const char *lightLoc = (const char *) ("lightProjectionView[" + Tools::ToString(i) +
"]").c_str();
glUniformMatrix4fv(glGetUniformLocation(programID, lightLoc), 1, GL_FALSE,
glm::value_ptr(lightProjectionView[i]));
int cascadeSpaceLoc = glGetUniformLocation(programID, (const char *) ("cascadeEndSpace[" +
Tools::ToString(i) +
"]").c_str());
glUniform1f(cascadeSpaceLoc, cascadeEndSpace[i]);
}
Finally the Shader method is:
"float readShadowMap(){"
" float positiveViewSpaceZ = FViewPos.z;"
" int cascadeIdx = 0;"
" for(int i = 0; i < NUM_CASCADES - 1; ++i){"
" if(positiveViewSpaceZ < cascadeEndSpace[i]){"
" cascadeIdx = i + 1;"
" }"
" }"
" vec4 fragmentShadowPosition = LightSpacePos[cascadeIdx];"
" vec3 projCoords = fragmentShadowPosition.xyz / fragmentShadowPosition.w;"
" projCoords = projCoords * 0.5f + 0.5f;"
" float currentDepth = projCoords.z;"
" float pcfDepth = 0.0f;"
" if(cascadeIdx == 0)"
" pcfDepth = texture(map_shadow[0], projCoords.xy).x;"
" else if(cascadeIdx == 1)"
" pcfDepth = texture(map_shadow[1], projCoords.xy).x;"
" else if(cascadeIdx == 2)"
" pcfDepth = texture(map_shadow[2], projCoords.xy).x;"
" float shadow = currentDepth + 0.00001 > pcfDepth ? 0.5 : 1.0;"
" return shadow;"
"}"
I tried changing the texture parameters but nothing changed.
Upvotes: 0
Views: 309
Reputation: 15951
What you're looking at is an aliasing artifact often called "shadow acne". You can find a good explanation with some illustrations here. Basically, what happens is that, due to finite resolution and precision, parts of your object surfaces end up casting shadows on themselves. For each fragment you render from the perspective of the camera, you project its position into the shadow map and compare the depth values. Unless the sampling rates of your camera image and shadow map are perfectly matched (which is basically impossible to achieve with regular sampling unless you massively oversample), there will be areas where multiple fragments end up projecting to the same shadow map pixel. Your fragments all come from a planar surface, which is generally oriented at a different angle towards the camera than it is towards the light. Thus, you end up with multiple neighboring fragments that all have slightly different depth, but all map to the same shadow map pixel, i.e., are compared with the same depth value. About half of these fragments will have a depth less than the pixel in the shadow map, about half greater than the pixel in the shadow map. Add on top of that some rounding error noise and you get the image you posted above.
The classic solution to this problem is to apply a slope-scaled depth bias when rendering either your shadow map or your camera image, e.g., using glPolygonOffset:
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0f, 1.0f);
// render shadow map
glDisable(GL_POLYGON_OFFSET_FILL);
// render scene with shadows
Upvotes: 1