I have been trying to implement shadow mapping. Whilst I think that I am now close, I have come stuck with a strange effect (illustrated below):
As you can see, the shadow region appears too small. There is also an unusual effect on the cube itself.
The geometry being rendered is a cube of dimensions 1.0 on a square plane of dimensions 100.0. The scene contains a single spotlight with an angle (from one side to the other) of 0.5 radians and a range of 100.0. This spotlight orbits about the y-axis and adjusts its rotation to look at the origin.
I setup the framebuffer and depth texture (512 x 512) as follows:
// Create and configure the depth texture.
glGenTextures(1, &m_depthTexture);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
GLfloat border[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)0);
// Assign the depth texture to texture channel 0.
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);
// Create and configure the framebuffer.
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);
GLenum drawBuffers[] = { GL_NONE };
glDrawBuffers(1, drawBuffers);
I then render the scene to the shadow map framebuffer from the perspective of the spotlight. This seems to be working. Inspecting the depth texture using an OpenGL debugging tool revealed the following:
The scene is rendered a second time, where I set the uniforms for the depth texture and shadow matrix:
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_2D, shadowMap.depthTexture());
program->uniform("shadowMap", 1);
const M3D::Matrix4 lightViewMatrix = lightTransformComponent->transformationMatrix().inverse();
const float invTanHalfFov = 1.0f / std::tan(coneAngle * 0.5f);
const float nearClipPlane = 0.3f;
const float farClipPlane = lightLightComponent->range();
const float zRange = nearClipPlane - farClipPlane;
const Matrix4 lightProjectionMatrix(
invTanHalfFov, 0.0f, 0.0f, 0.0f,
0.0f, invTanHalfFov, 0.0f, 0.0f,
0.0f, 0.0f, -(nearClipPlane + farClipPlane) / zRange, 2.0f * nearClipPlane * farClipPlane / zRange,
0.0f, 0.0f, 1.0f, 0.0f
);
const Matrix4 shadowMatrix = lightProjectionMatrix * lightViewMatrix * modelMatrix;
program->uniform("shadowMatrix", shadowMatrix);
I compute the shadow coordinate in the vertex shader:
f_shadowCoordinate = shadowMatrix * vec4(v_position, 1.0f);
Then, in the fragment shader, I project this coordinate and bias it to range in the interval [0, 1].
vec2 projectedShadowCoordinates = (f_shadowCoordinate.xy / f_shadowCoordinate.w) * 0.5f + vec2(0.5f, 0.5f);
float shadowDistance = texture(shadowMap, projectedShadowCoordinates).x;
return vec4(1.0f) * shadowDistance;
The problem was caused by mistakenly setting the projection matrix uniform to the camera's projection matrix (instead of the light's projection matrix) when rendering to the shadow framebuffer.