OpenGL Depth Cubemap with Geometry Shader Not Rendering Correctly

by kingsapo   Last Updated January 20, 2018 21:13 PM

I was having some trouble with cubemaps in OpenGL, and was hoping to get some help. I've been following a tutorial about point light shadow mapping using cubemaps, where a geometry shader is used to render to all faces of the cubemap once instead of 6 times (once per face). Apparently because of my low reputation, I can't post more than 2 links, so I will edit this question with the site once I gain the reputation for it.

After following the tutorial, I've encountered a few problems. The first is that the different sides of the cubemap don't seem to line up, and the positive and negative y sides render at a very different-looking perspective. Here's a screenshot to illustrate this:

Depth map intersection problem Sorry for the dark image. This was generated by simply projecting the rendered cubemap to the geometry. You can clearly see where the three sides intersect, they don't line up. There is also a problem with incorrect depth values, seen better by the final render:

Final render

Everything's a bit reversed. The shadowed parts are unshadowed and vice-versa for the positive and negative y faces, and no shadows are seen on any of the other 4 faces.

So those are the problems I'm experiencing. Code-wise, I start by creating the cubemap:

glGenTextures(1, &cubemapID);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapID);

for (int i = 0; i < 6; i++) {
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, texture.width, texture.height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
}

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

Then I update the projection and view matrices for the light, and pass them to a render function:

// rendering for shadowmap
void RenderShadowmap(float deltaTime, P_Light_Depth_Shader *p_light_depth_Shader, vector<Mesh> &meshes) {
    if (meshes.size() > 0) {
        glViewport(0, 0, shadowmapResolution, shadowmapResolution);
        glBindFramebuffer(GL_FRAMEBUFFER, depthFBO);
        glClear(GL_DEPTH_BUFFER_BIT);

        // create the depth projection matrix - shared among all 6 sides
        float farPlane = 25.0f;
        depthProjectionMatrix = Matrix4::Perspective(90.0f, 1.0f, 1.0f, farPlane);

        // create the depth view matrices - different for each side
        vector<Matrix4> depthViewMatrices;
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(1, 0, 0), Vector3(0, -1, 0))); // positive x
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(-1, 0, 0), Vector3(0, -1, 0)));    // negative x
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(0, 1, 0), Vector3(0, 0, 1)));  // positive y
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(0, -1, 0), Vector3(0, 0, -1)));    // negative y
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(0, 0, 1), Vector3(0, -1, 0))); // positive z
        depthViewMatrices.push_back(depthProjectionMatrix * Matrix4::LookAt(transform.position, transform.position + Vector3(0, 0, -1), Vector3(0, -1, 0)));    // negative z

        meshes[0].RenderPointShadowMapSetup(p_light_depth_Shader);

        for (int i = 0; i < meshes.size(); i++) {
            meshes[i].RenderPointShadowMap(deltaTime, p_light_depth_Shader, depthViewMatrices, transform.position, farPlane);
        }

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
}

RenderPointShadowMapSetup simply calls glUseProgram on the shader, to prevent redundant calls.

RenderPointShadowMap looks like this:

// point light shadowmap rendering
void RenderPointShadowMap(float deltaTime, P_Light_Depth_Shader *p_light_depth_Shader, vector<Matrix4> &depthViewMatrices, Vector3 lightPosition, float lightAttenuation) {
    glBindBuffer(GL_ARRAY_BUFFER, vbo);                 // vertex buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_vbo);   // Index buffer

    // vertex shader attributes
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "model"), 1, GL_FALSE, modelMatrix);

    // geometry shader attributes
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[0]"), 1, GL_FALSE, depthViewMatrices[0]);
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[1]"), 1, GL_FALSE, depthViewMatrices[1]);
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[2]"), 1, GL_FALSE, depthViewMatrices[2]);
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[3]"), 1, GL_FALSE, depthViewMatrices[3]);
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[4]"), 1, GL_FALSE, depthViewMatrices[4]);
    glUniformMatrix4fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "shadowMatrices[5]"), 1, GL_FALSE, depthViewMatrices[5]);

    // fragment shader attributes
    glUniform3fv(glGetUniformLocation(p_light_depth_Shader->shaderID, "lightPos"), 1, lightPosition);
    glUniform1f(glGetUniformLocation(p_light_depth_Shader->shaderID, "far_plane"), lightAttenuation);

    // buffer the vertex attributes
    glEnableVertexAttribArray(0);   // position
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);

    // Draw the triangles !
    glDrawElements(GL_TRIANGLES, numberOfIndices, GL_UNSIGNED_INT, NULL);

    glDisableVertexAttribArray(0);
}

Shader-wise, here is the vertex shader:

#version 330 core

layout (location = 0) in vec4 position;

uniform mat4 model;

void main()
{
    // transform to world space for geometry shader
    gl_Position = model * vec4(position.xyz, 1.0);
}

Geometry shader:

#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 18) out;

uniform mat4 shadowMatrices[6];

out vec4 FragPos;   // FragPos from geometry shader (output per emitvertex)

void main()
{
    // iterate through each face on the cubemap
    // for each face, render each triangle, transforming it by the relevant shadow view matrix
    // also send the result to the fragment shader (FragPos)
    for (int face = 0; face < 6; face++) {
        gl_Layer = face;    // built-in variable that specifies which face of the cubemap we render
        for (int i = 0; i < 3; i++) {   // for each triangle's vertices
            FragPos = gl_in[i].gl_Position;
            gl_Position = shadowMatrices[face] * FragPos;
            EmitVertex();
        }
        EndPrimitive();
    }
}

Fragment shader:

#version 330 core

in vec4 FragPos;

uniform vec3 lightPos;
uniform float far_plane;

void main()
{
    // get distance between fragment and light source
    float lightDistance = length(FragPos.xyz - lightPos);

    // map to [0;1] range by dividing by far_plane
    lightDistance = lightDistance / far_plane;

    // write this as modified depth
    gl_FragDepth = lightDistance;
}

Finally, in my point light fragment shader, I have a debug function, used to produce the first screenshot, and the actual shadow amount function, used for the final render. The first is:

vec3 fragToLight = worldPos.xyz - pointLight.baseLight.Transform.Position;

float closestDepth = texture(pointLight.DepthTexture, fragToLight).r;

float shadowDebug = closestDepth / pointLight.attenuationRadius;
color.rgb = vec3(shadowDebug); 

And the second is:

vec3 fragToLight = worldPos.xyz - pointLight.baseLight.Transform.Position;
// Use the light to fragment vector to sample from the depth map    
float closestDepth = texture(pointLight.DepthTexture, fragToLight).r;
// It is currently in linear range between [0,1]. Re-transform back to original value
closestDepth *= pointLight.attenuationRadius;
// Now get current linear depth as the length between the fragment and light position
float currentDepth = length(fragToLight);
// Now test for shadows
float bias = 0.05;
shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;

And that's really about it! Apologies if I've made this too long, I tried to post only the relevant parts but it got a bit long anyway. At any rate, if anyone can make any sense of all this, any help would be greatly appreciated. Thank you!



Answers 1


Did you ever find a solution for this? Others mention it might have to do with clearing the depth buffer

MsL
MsL
January 20, 2018 20:53 PM

Related Questions


Use depth bias for shadows in deferred shading

Updated August 16, 2016 08:05 AM


Z-Value of clip-space position is always 1.0

Updated April 03, 2015 21:22 PM