1
votes

I'm currently working on an OpenGL project and I'm trying to get shadow mapping to work properly. I could get to a point where the shadow map gets rendered into a texture, but it doesn't seem to get applied to the scenery when rendered. Here's the most important bits of my code:

The shadow map vertex shader, basically a simple pass through shader (also does some additional stuff like normals, but that shouldn't distract you); it basically just transforms the vertices so they're seen from the perspective of the light (it's a directional light but since we need to assume a position, it's basically a point far away):

#version 430 core

layout(location = 0) in vec3 v_position;
layout(location = 1) in vec3 v_normal;
layout(location = 2) in vec3 v_texture;
layout(location = 3) in vec4 v_color;

out vec3 f_texture;
out vec3 f_normal;
out vec4 f_color;

uniform mat4 modelMatrix;
uniform mat4 depthViewMatrix;
uniform mat4 depthProjectionMatrix;

// Shadow map vertex shader.
void main() {
    mat4 mvp = depthProjectionMatrix * depthViewMatrix * modelMatrix;
    gl_Position = mvp * vec4(v_position, 1.0);

    // Passing attributes on to the fragment shader
    f_texture = v_texture;
    f_normal = (transpose(inverse(modelMatrix)) * vec4(v_normal, 1.0)).xyz;
    f_color = v_color;
}

The shadow map fragment shader that writes the depth value to a texture:

#version 430 core

layout(location = 0) out float fragmentDepth;

in vec3 f_texture;
in vec3 f_normal;
in vec4 f_color;

uniform vec3 lightDirection;
uniform sampler2DArray texSampler;

// Shadow map fragment shader.
void main() {
  fragmentDepth = gl_FragCoord.z;
}

The vertex shader that actually renders the scene, but also calculates the position of the current vertex from the lights point of view (shadowCoord) to compare against the depth texture; it also applies a bias matrix, since the coordinates aren't in the correct [0, 1] interval for sampling:

#version 430 core

layout(location = 0) in vec3 v_position;
layout(location = 1) in vec3 v_normal;
layout(location = 2) in vec3 v_texture;
layout(location = 3) in vec4 v_color;

out vec3 f_texture;
out vec3 f_normal;
out vec4 f_color;
out vec3 f_shadowCoord;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

uniform mat4 depthViewMatrix;
uniform mat4 depthProjectionMatrix;

// Simple vertex shader.
void main() {
    mat4 mvp = projectionMatrix * viewMatrix * modelMatrix;
    gl_Position = mvp * vec4(v_position, 1.0);


    // This bias matrix adjusts the projection of a given vertex on a texture to be within 0 and 1 for proper sampling
    mat4 depthBias = mat4(0.5, 0.0, 0.0, 0.5,
                          0.0, 0.5, 0.0, 0.5,
                          0.0, 0.0, 0.5, 0.5,
                          0.0, 0.0, 0.0, 1.0);

    mat4 depthMVP = depthProjectionMatrix * depthViewMatrix * modelMatrix;
    mat4 biasedDMVP = depthBias * depthMVP;

    // Passing attributes on to the fragment shader
    f_shadowCoord = (biasedDMVP * vec4(v_position, 1.0)).xyz;
    f_texture = v_texture;
    f_normal = (transpose(inverse(modelMatrix)) * vec4(v_normal, 1.0)).xyz;
    f_color = v_color;
}                       

The fragment shader that applies textures from a texture array and receives the depth texture (uniform sampler2D shadowMap) and checks if a fragment is behind something:

#version 430 core

in vec3 f_texture;
in vec3 f_normal;
in vec4 f_color;
in vec3 f_shadowCoord;

out vec4 color;

uniform vec3 lightDirection;
uniform sampler2D shadowMap;
uniform sampler2DArray tileTextureArray;

// Very basic fragment shader.
void main() {
        float visibility = 1.0;
        if (texture(shadowMap, f_shadowCoord.xy).z < f_shadowCoord.z) {
            visibility = 0.5;
        }

        color = texture(tileTextureArray, f_texture) * visibility;
}

And finally: the function that renders multiple chunks to generate the shadow map and then renders the scene with the shadow map applied:

// Generating the shadow map
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);

m_shadowShader->bind();
glViewport(0, 0, 1024, 1024);
glDisable(GL_CULL_FACE);

glm::vec3 lightDir = glm::vec3(1.0f, -0.5f, 1.0f);
glm::vec3 sunPosition = FPSCamera::getPosition() - lightDir * 64.0f;
glm::mat4 depthViewMatrix = glm::lookAt(sunPosition, FPSCamera::getPosition(), glm::vec3(0, 1, 0));
glm::mat4 depthProjectionMatrix = glm::ortho<float>(-100.0f, 100.0f, -100.0f, 100.0f, 0.1f, 800.0f);

m_shadowShader->setUniformMatrix("depthViewMatrix", depthViewMatrix);
m_shadowShader->setUniformMatrix("depthProjectionMatrix", depthProjectionMatrix);

for (Chunk *chunk : m_chunks) {
  m_shadowShader->setUniformMatrix("modelMatrix", chunk->getModelMatrix());
  chunk->drawElements();
}

m_shadowShader->unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// Normal draw call
m_chunkShader->bind();
glEnable(GL_CULL_FACE);
glViewport(0, 0, Window::getWidth(), Window::getHeight());
glm::mat4 viewMatrix = FPSCamera::getViewMatrix();
glm::mat4 projectionMatrix = FPSCamera::getProjectionMatrix();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);
glActiveTexture(GL_TEXTURE1);
m_textures->bind();
m_chunkShader->setUniformMatrix("depthViewMatrix", depthViewMatrix);
m_chunkShader->setUniformMatrix("depthProjectionMatrix", depthProjectionMatrix);
m_chunkShader->setUniformMatrix("viewMatrix", viewMatrix);
m_chunkShader->setUniformMatrix("projectionMatrix", projectionMatrix);
m_chunkShader->setUniformVec3("lightDirection", lightDir);
m_chunkShader->setUniformInteger("shadowMap", 0);
m_chunkShader->setUniformInteger("tileTextureArray", 1);

for (Chunk *chunk : m_chunks) {
  m_chunkShader->setUniformMatrix("modelMatrix", chunk->getModelMatrix());
  chunk->drawElements();
}

Most of the code should be self-explanatory, I'm binding a FBO with a texture attached, we do a normal rendering call into the framebuffer, it gets rendered into a texture and then I'm trying to pass it into the shader for normal rendering. I've tested whether the texture gets properly generated and it does: See the generated shadow map here
See the generated shadow map here

However, when rendering the scene, all I see is this.
this
No shadows applied, visibility is 1.0 everywhere. I also use a debug context which works properly and logs errors when there are any, but it seems to be completely fine, no warnings or errors, so I'm the one doing something terribly wrong here. I'm on OpenGL 4.3 by the way.

Hopefully one of you can help me out on this, I've never got shadow maps to work before, this is the closest I've ever come, lol. Thanks in advance.

1

1 Answers

0
votes

Commonly a mat4 OpenGL transformation matrix looks like this:

( X-axis.x, X-axis.y, X-axis.z, 0 )
( Y-axis.x, Y-axis.y, Y-axis.z, 0 )
( Z-axis.x, Z-axis.y, Z-axis.z, 0 )
( trans.x,  trans.y,  trans.z,  1 ) 

So your depthBias matrix, which you use to convert from normalized device coordinates (in ranage [-1, 1]) to texture coordinates (in range [0, 1]), should look like this:

mat4 depthBias = mat4(0.5, 0.0, 0.0, 0.0,
                      0.0, 0.5, 0.0, 0.0,
                      0.0, 0.0, 0.5, 0.0,
                      0.5, 0.5, 0.5, 1.0); 

or this:

mat4 depthBias = mat4(
    vec4( 0.5, 0.0, 0.0, 0.0 ),
    vec4( 0.0, 0.5, 0.0, 0.0 ),
    vec4( 0.0, 0.0, 0.5, 0.0 ),
    vec4( 0.5, 0.5, 0.5, 1.0 ) ); 


After you have transformed a vertex position by the model matrix, the view matrix and the projection matrix, the vertex position is in clip space (homogeneous coordinates). You have to convert from clip space to normalized device coordinates (cartesian coordinates in range [-1, 1]). This can be done by dividing, by the w component of the homogeneous coordinate:

mat4 depthMVP  = depthProjectionMatrix * depthViewMatrix * modelMatrix;
vec4 clipPos   = depthMVP * vec4(v_position, 1.0);
vec4 ndcPos    = vec4(clipPos.xyz / clipPos.w, 1.0);
f_shadowCoord  = (depthBias * ndcPos).xyz;


A depth texture has one channel only. If you read data from the depth texture, then the data is contained in the x (or r) component of the vector.

Adapt the fragment shader code like this:

if ( texture(shadowMap, f_shadowCoord.xy).x < f_shadowCoord.z) 
    visibility = 0.5;

The Image Format specification of Khronos group says:

Image formats do not have to store each component. When the shader samples such a texture, it will still resolve to a 4-value RGBA vector. The components not stored by the image format are filled in automatically. Zeros are used if R, G, or B is missing, while a missing Alpha always resolves to 1.


see further:


Addition to the solution:

This is an important part of the solution, but there was another step needed to properly render the shadow map. The second mistake was using the wrong component of the texture to compare against f_shadowCoord.z: it should've been

texture(shadowMap, f_shadowCoord.xy).r

instead of

texture(shadowMap, f_shadowCoord.xy).z