Not being able to render into FBO with Depth Attachment

First of all: I am using Java together with LWJGL to render in OpenGL.

I am trying to implement shadow mapping for a few days now and I think I am starting to see the real issue at hand: I have an FBO with a Depth Texture attached to it, for the light pass of shadow mapping, however nothing is being rendered in it. Not even hardcoded values.

I have double checked the part of the program that visualizes the depth texture, and that is working with regular textures, moreover I see no shadows appearing in my scene, so I think something is definately wrong.

init:


        //shadow FBO and texture
        depthFBO = new FrameBufferObject().create().bind();
        depthTexture = new Texture2D().create().bind()
                .storage2D(12, GL_DEPTH_COMPONENT32F, 4096, 4096)
                .minFilter(GL_LINEAR)
                .magFilter(GL_LINEAR)
                .compareMode(GL_COMPARE_REF_TO_TEXTURE)
                .compareFunc(GL_LEQUAL);
        depthFBO.texture2D(GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, depthTexture, 0)
                .checkStatus().unbind();
        depthTexture.unbind();

render:


        depthFBO.bind();
        glViewport(0, 0, 4096, 4096);
        glEnable(GL_POLYGON_OFFSET_FILL);
        glPolygonOffset(4.0f, 4.0f);
        glDrawBuffer(GL_NONE);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepthf(1f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        Drawable.drawAllLightPass(drawables, lightNormalProgram, lightTessellationProgram);
        glDisable(GL_POLYGON_OFFSET_FILL);
        depthFBO.unbind();

Code in direct gl* calls.

init:


	int frameBufferObjectId = GL30.glGenFramebuffers();
	GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, frameBufferObjectId);

	int textureId = GL11.glGenTextures();
	GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
	GL42.glTexStorage2D(GL11.GL_TEXTURE_2D, 12, GL_DEPTH_COMPONENT32F, 4096, 4096);
	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
	GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

	GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, textureId, 0);
	if (GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) != GL30.GL_FRAMEBUFFER_COMPLETE) {
		throw new RuntimeException(this + " is not complete.");
	}
	GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
	GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);

render:


	GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, frameBufferObjectId);
	glViewport(0, 0, 4096, 4096);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(4.0f, 4.0f);
	glDrawBuffer(GL_NONE);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepthf(1f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//appropiate calls to glDraw* are being done, this is confirmed to work as it works for displaying data

	glDisable(GL_POLYGON_OFFSET_FILL);
	GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);

Now the most important shaders:

light-normal.vs.glsl


    #version 430 core

    layout(location = 4) uniform mat4 light_model_matrix;
    layout(location = 5) uniform mat4 light_view_matrix;
    layout(location = 6) uniform mat4 light_projection_matrix;

    layout(location = 0) in vec4 position;

    void main(void) {
        gl_Position = light_projection_matrix * light_view_matrix * light_model_matrix * position;
    }

light-normal.fs.glsl


    #version 430 core

    layout(location = 0) out float depth;

    void main(void) {
        depth = gl_FragCoord.z;
    }

Also even hardcoding depth to either 0.0f, 0.5f or 1.0f neither work.

Just to clarify if my expectations are correct: When I move my camera close to an object that is being rendered on the screen, then the color of the depth buffer should change? As depth will be close to 0.0f then, and far away from 1.0f?

Also another clarification: What I see being rendered as the ‘texture’, is an area that is white everywhere.

Also yeah, this is a direct copy of my question on StackOverflow, however on there very few people help with graphics problems.

Debug tips:

  • Use glGetTexImage or glReadPixels(…DEPTH_COMPONENT…) to verify texture contents on the CPU (not in the shader.)
  • Ignore all rendering other than clear. Verify that changing glClearDepth changes the texture content (.: FBO is set up correctly.)
  • When rendering, verify that DEPTH_TEST is enabled. Otherwise nothing will be written to the depth buffer (read the spec.)
  • When using COMPARE_REF_TO_TEXTURE, you must use a shadow sampler. When not using COMPARE_REF_TO_TEXTURE, you must not use a shadow sampler (undefined behavior.)

[QUOTE=arekkusu;1257610]Debug tips:

  • Use glGetTexImage or glReadPixels(…DEPTH_COMPONENT…) to verify texture contents on the CPU (not in the shader.)
  • Ignore all rendering other than clear. Verify that changing glClearDepth changes the texture content (.: FBO is set up correctly.)
  • When rendering, verify that DEPTH_TEST is enabled. Otherwise nothing will be written to the depth buffer (read the spec.)
  • When using COMPARE_REF_TO_TEXTURE, you must use a shadow sampler. When not using COMPARE_REF_TO_TEXTURE, you must not use a shadow sampler (undefined behavior.)
    [/QUOTE]

Verified points 3 and 4, fixed point 4 in my code adhoc aswell. Didn’t check 1 yet.

However, 2 is giving me something very interesting. The glClearDepth value does matter, however my shaders writes do not matter a single thing.

To explain further:

My lighting shader and code:


        //light model matrix
        lightModelMatrix.identity().multiply(modelMatrix);
        Uniforms.setUniformMatrix4(UNIFORM_LIGHT_MODEL_MATRIX, false, lightModelMatrix.writeToFloatBuffer(lightModelMatrixBuffer));
        
        //light view matrix
        lightViewMatrix.identity().lookAt(new Vector3f(-20f, 7.5f, -20f), Vector3f.O, Vector3f.Y);
        Uniforms.setUniformMatrix4(UNIFORM_LIGHT_VIEW_MATRIX, false, lightViewMatrix.writeToFloatBuffer(lightViewMatrixBuffer));
        
        //light projection matrix
        lightProjectionMatrix.identity().frustum(-1f, 1f, -1f, 1f, -200f, 200f);
        Uniforms.setUniformMatrix4(UNIFORM_LIGHT_PROJECTION_MATRIX, false, lightProjectionMatrix.writeToFloatBuffer(lightProjectionMatrixBuffer));

Result is that lightModelMatrix = Identity, lightViewMatrix = a lookAt matrix from the start of the directional light to the origin with Y as up vector, lightProjectionMatrix = A frustum.

light-normal.vs.glsl:


    #version 430 core

    layout(location = 4) uniform mat4 light_model_matrix;
    layout(location = 5) uniform mat4 light_view_matrix;
    layout(location = 6) uniform mat4 light_projection_matrix;

    layout(location = 0) in vec4 position;

    void main(void) {
        gl_Position = light_projection_matrix * light_view_matrix * light_model_matrix * position;
    }

light-normal.fs.glsl


    #version 430 core

    layout(location = 0) out float depth;

    void main(void) {
        depth = gl_FragCoord.z;
    }

Can it be that everything is being drawn outside the input area (-1 <= x <= 1, -1 <= y <= 1, 0 <= z <= 1)?

More clarification, output in column major order:


lightModelMatrix = Matrix4f(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0)
lightViewMatrix = Matrix4f(-0.70710677, 0.0, 0.70710677, 0.0, 0.18123662, 0.9665954, 0.18123662, -4.7683716E-7, -0.68348616, 0.2563073, -0.68348616, -29.261753, 0.0, 0.0, 0.0, 1.0)
lightProjectionMatrix = Matrix4f(-200.0, 0.0, 0.0, 0.0, 0.0, -200.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 200.0, 0.0)

  • Use your from-light-view matrices to render normally, into a color buffer. Ignore all the depth stuff, and just debug the transforms.

  • Manually transform one vertex yourself (on the cpu) and verify the result is within the clip volume.

  • Or use transform feedback to capture the transformed vertex positions. Map the resulting buffer and print the results from the CPU.

  • Use a debugger to view each stage of your rendering. A good debugger will let you view texture/depth buffer contents with min/max scaling, so you can visualize the effective use of the depth range.

[QUOTE=arekkusu;1257614]* Use your from-light-view matrices to render normally, into a color buffer. Ignore all the depth stuff, and just debug the transforms.

  • Manually transform one vertex yourself (on the cpu) and verify the result is within the clip volume.

  • Or use transform feedback to capture the transformed vertex positions. Map the resulting buffer and print the results from the CPU.

  • Use a debugger to view each stage of your rendering. A good debugger will let you view texture/depth buffer contents with min/max scaling, so you can visualize the effective use of the depth range.[/QUOTE]

I have substituted the light-MVP matrices by the regular matrices, no change… Does that tell me anything? Maybe it was too naïve to only change that and nothing else.
Transform feedback looks like a good candidate, however have never used them yet.

And for debugging I have the ‘issue’ that I run it with Java/LWJGL, and I do not know how to get it running inside a debugger.

I changed my glDepthFunc(GL_LEQUAL) to use GL_NOTEQUAL and stuff changed! The box I’ve drawn on normalized device coordinates now suddenly shows up, and some other interesting stuff starts to happen.

However why exactly does it now show other behaviour, is this the correct setting even?

Keep debugging your transforms to understand the resulting window Z.

Remember that the gl_Position output of your shader is not NDC, it is clip space (-w…w). After the shader runs, homogenous divide by w produces NDC, and that result is scaled by glViewport and glDepthRange to produce window coords. That window Z (or optionally, your replacement value written to gl_FragDepth) is used during the depth test.

You can debug all of those transforms yourself on the CPU, and compare with the real Z written to the depth buffer, via GetTexImage or ReadPixels, until you understand your transforms.

[QUOTE=arekkusu;1257618]Keep debugging your transforms to understand the resulting window Z.

Remember that the gl_Position output of your shader is not NDC, it is clip space (-w…w). After the shader runs, homogenous divide by w produces NDC, and that result is scaled by glViewport and glDepthRange to produce window coords. That window Z (or optionally, your replacement value written to gl_FragDepth) is used during the depth test.

You can debug all of those transforms yourself on the CPU, and compare with the real Z written to the depth buffer, via GetTexImage or ReadPixels, until you understand your transforms.[/QUOTE]

I’m using the programmable pipeline, and setting w to 1.0 to use homogeneous coordinates, so that should not be an issue. GlViewport runs from 0, 0 to 4096, 4096, so it should write into that range in the texture and glDepthRange has not been modified.

I do have to post the code of the tutorial here, of the book called OpenGL SuperBible: Sixth Edition


/*
 * Copyright © 2012-2013 Graham Sellers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <sb6.h>
#include <vmath.h>

#include <object.h>
#include <sb6ktx.h>
#include <shader.h>

#define DEPTH_TEXTURE_SIZE      4096
#define FRUSTUM_DEPTH           1000

class shadowmapping_app : public sb6::application
{
public:
    shadowmapping_app()
        : light_program(0),
          view_program(0),
          show_light_depth_program(0),
          mode(RENDER_FULL),
          paused(false)
    {
    }

protected:
    void init()
    {
        static const char title[] = "OpenGL SuperBible - Shadow Mapping";

        sb6::application::init();

        memcpy(info.title, title, sizeof(title));
    }

    void startup();
    void render(double currentTime);
    void render_scene(double currentTime, bool from_light);
    void onKey(int key, int action);

    void load_shaders();

    GLuint          light_program;
    GLuint          view_program;
    GLint           show_light_depth_program;

    struct
    {
        struct
        {
            GLint   mvp;
        } light;
        struct
        {
            GLint   mv_matrix;
            GLint   proj_matrix;
            GLint   shadow_matrix;
            GLint   full_shading;
        } view;
    } uniforms;

    GLuint          depth_fbo;
    GLuint          depth_tex;
    GLuint          depth_debug_tex;

    enum { OBJECT_COUNT = 4 };
    struct
    {
        sb6::object     obj;
        vmath::mat4     model_matrix;
    } objects[OBJECT_COUNT];

    vmath::mat4     light_view_matrix;
    vmath::mat4     light_proj_matrix;

    vmath::mat4     camera_view_matrix;
    vmath::mat4     camera_proj_matrix;

    GLuint          quad_vao;

    enum
    {
        RENDER_FULL,
        RENDER_LIGHT,
        RENDER_DEPTH
    } mode;

    bool paused;
};

void shadowmapping_app::startup()
{
    load_shaders();

    int i;

    static const char * const object_names[] =
    {
        "media/objects/dragon.sbm",
        "media/objects/sphere.sbm",
        "media/objects/cube.sbm",
        "media/objects/torus.sbm"
    };

    for (i = 0; i < OBJECT_COUNT; i++)
    {
        objects[i].obj.load(object_names[i]);
    }

    glGenFramebuffers(1, &depth_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo);

    glGenTextures(1, &depth_tex);
    glBindTexture(GL_TEXTURE_2D, depth_tex);
    glTexStorage2D(GL_TEXTURE_2D, 11, GL_DEPTH_COMPONENT32F, DEPTH_TEXTURE_SIZE, DEPTH_TEXTURE_SIZE);
    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_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);

    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex, 0);

    glGenTextures(1, &depth_debug_tex);
    glBindTexture(GL_TEXTURE_2D, depth_debug_tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F, DEPTH_TEXTURE_SIZE, DEPTH_TEXTURE_SIZE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, depth_debug_tex, 0);

    glBindTexture(GL_TEXTURE_2D, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glEnable(GL_DEPTH_TEST);

    glGenVertexArrays(1, &quad_vao);
    glBindVertexArray(quad_vao);
}

void shadowmapping_app::render(double currentTime)
{
    static const GLfloat zeros[] = { 0.0f, 0.0f, 0.0f, 0.0f };
    
    static double last_time = 0.0;
    static double total_time = 0.0;

    if (!paused)
        total_time += (currentTime - last_time);
    last_time = currentTime;

    const float f = (float)total_time + 30.0f;

    vmath::vec3 light_position = vmath::vec3(20.0f, 20.0f, 20.0f);
    vmath::vec3 view_position = vmath::vec3(0.0f, 0.0f, 40.0f);

    light_proj_matrix = vmath::frustum(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 200.0f);
    light_view_matrix = vmath::lookat(light_position,
                                      vmath::vec3(0.0f), vmath::vec3(0.0f, 1.0f, 0.0f));

    camera_proj_matrix = vmath::perspective(50.0f,
                                            (float)info.windowWidth / (float)info.windowHeight,
                                            1.0f,
                                            200.0f);

    camera_view_matrix = vmath::lookat(view_position,
                                       vmath::vec3(0.0f),
                                       vmath::vec3(0.0f, 1.0f, 0.0f));

    objects[0].model_matrix = vmath::rotate(f * 14.5f, 0.0f, 1.0f, 0.0f) *
                              vmath::rotate(20.0f, 1.0f, 0.0f, 0.0f) *
                              vmath::translate(0.0f, -4.0f, 0.0f);

    objects[1].model_matrix = vmath::rotate(f * 3.7f, 0.0f, 1.0f, 0.0f) *
                              vmath::translate(sinf(f * 0.37f) * 12.0f, cosf(f * 0.37f) * 12.0f, 0.0f) *
                              vmath::scale(2.0f);

    objects[2].model_matrix = vmath::rotate(f * 6.45f, 0.0f, 1.0f, 0.0f) *
                              vmath::translate(sinf(f * 0.25f) * 10.0f, cosf(f * 0.25f) * 10.0f, 0.0f) *
                              vmath::rotate(f * 99.0f, 0.0f, 0.0f, 1.0f) *
                              vmath::scale(2.0f);

    objects[3].model_matrix = vmath::rotate(f * 5.25f, 0.0f, 1.0f, 0.0f) *
                              vmath::translate(sinf(f * 0.51f) * 14.0f, cosf(f * 0.51f) * 14.0f, 0.0f) *
                              vmath::rotate(f * 120.3f, 0.707106f, 0.0f, 0.707106f) *
                              vmath::scale(2.0f);

    glEnable(GL_DEPTH_TEST);
    render_scene(total_time, true);

    if (mode == RENDER_DEPTH)
    {
        glDisable(GL_DEPTH_TEST);
        glBindVertexArray(quad_vao);
        glUseProgram(show_light_depth_program);
        glBindTexture(GL_TEXTURE_2D, depth_debug_tex);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    else
    {
        render_scene(total_time, false);
    }
}

void shadowmapping_app::render_scene(double currentTime, bool from_light)
{
    static const GLfloat ones[] = { 1.0f };
    static const GLfloat zero[] = { 0.0f };
    static const GLfloat gray[] = { 0.1f, 0.1f, 0.1f, 0.0f };
    static const vmath::mat4 scale_bias_matrix = vmath::mat4(vmath::vec4(0.5f, 0.0f, 0.0f, 0.0f),
                                                             vmath::vec4(0.0f, 0.5f, 0.0f, 0.0f),
                                                             vmath::vec4(0.0f, 0.0f, 0.5f, 0.0f),
                                                             vmath::vec4(0.5f, 0.5f, 0.5f, 1.0f));
    vmath::mat4 light_vp_matrix = light_proj_matrix * light_view_matrix;
    vmath::mat4 shadow_sbpv_matrix = scale_bias_matrix * light_proj_matrix * light_view_matrix;

    if (from_light)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo);
        glViewport(0, 0, DEPTH_TEXTURE_SIZE, DEPTH_TEXTURE_SIZE);
        glEnable(GL_POLYGON_OFFSET_FILL);
        glPolygonOffset(4.0f, 4.0f);
        glUseProgram(light_program);
        static const GLenum buffs[] = { GL_COLOR_ATTACHMENT0 };
        glDrawBuffers(1, buffs);
        glClearBufferfv(GL_COLOR, 0, zero);
    }
    else
    {
        glViewport(0, 0, info.windowWidth, info.windowHeight);
        glClearBufferfv(GL_COLOR, 0, gray);
        glUseProgram(view_program);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, depth_tex);
        glUniformMatrix4fv(uniforms.view.proj_matrix, 1, GL_FALSE, camera_proj_matrix);
        glDrawBuffer(GL_BACK);
    }

    glClearBufferfv(GL_DEPTH, 0, ones);

    int i;
    for (i = 0; i < 4; i++)
    {
        vmath::mat4& model_matrix = objects[i].model_matrix;
        if (from_light)
        {
            glUniformMatrix4fv(uniforms.light.mvp, 1, GL_FALSE, light_vp_matrix * objects[i].model_matrix);
        }
        else
        {
            vmath::mat4 shadow_matrix = shadow_sbpv_matrix * model_matrix;
            glUniformMatrix4fv(uniforms.view.shadow_matrix, 1, GL_FALSE, shadow_matrix);
            glUniformMatrix4fv(uniforms.view.mv_matrix, 1, GL_FALSE, camera_view_matrix * objects[i].model_matrix);
            glUniform1i(uniforms.view.full_shading, mode == RENDER_FULL ? 1 : 0);
        }
        objects[i].obj.render();
    }

    if (from_light)
    {
        glDisable(GL_POLYGON_OFFSET_FILL);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    else
    {
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

void shadowmapping_app::onKey(int key, int action)
{
    if (action)
    {
        switch (key)
        {
            case '1':
                mode = RENDER_FULL;
                break;
            case '2':
                mode = RENDER_LIGHT;
                break;
            case '3':
                mode = RENDER_DEPTH;
                break;
            case 'R': 
                load_shaders();
                break;
            case 'P':
                paused = !paused;
                break;
        }
    }
}

void shadowmapping_app::load_shaders()
{
    GLuint vs;
    GLuint fs;

    vs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-light.vs.glsl", GL_VERTEX_SHADER);
    fs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-light.fs.glsl", GL_FRAGMENT_SHADER);

    if (light_program)
        glDeleteProgram(light_program);

    light_program = glCreateProgram();
    glAttachShader(light_program, vs);
    glAttachShader(light_program, fs);
    glLinkProgram(light_program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    uniforms.light.mvp = glGetUniformLocation(light_program, "mvp");

    vs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-camera.vs.glsl", GL_VERTEX_SHADER);
    fs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-camera.fs.glsl", GL_FRAGMENT_SHADER);

    if (light_program)
        glDeleteProgram(view_program);

    view_program = glCreateProgram();
    glAttachShader(view_program, vs);
    glAttachShader(view_program, fs);
    glLinkProgram(view_program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    uniforms.view.proj_matrix = glGetUniformLocation(view_program, "proj_matrix");
    uniforms.view.mv_matrix = glGetUniformLocation(view_program, "mv_matrix");
    uniforms.view.shadow_matrix = glGetUniformLocation(view_program, "shadow_matrix");
    uniforms.view.full_shading = glGetUniformLocation(view_program, "full_shading");

    if (show_light_depth_program)
        glDeleteProgram(show_light_depth_program);

    show_light_depth_program = glCreateProgram();

    vs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-light-view.vs.glsl", GL_VERTEX_SHADER);
    fs = sb6::shader::load("media/shaders/shadowmapping/shadowmapping-light-view.fs.glsl", GL_FRAGMENT_SHADER);

    glAttachShader(show_light_depth_program, vs);
    glAttachShader(show_light_depth_program, fs);
    glLinkProgram(show_light_depth_program);

    glDeleteShader(vs);
    glDeleteShader(fs);
}

DECLARE_MAIN(shadowmapping_app)

Light vertex shader


#version 420 core

uniform mat4 mvp;

layout (location = 0) in vec4 position;

void main(void)
{
    gl_Position = mvp * position;
}

Light fragment shader:


#version 420 core

layout (location = 0) out vec4 color;

void main(void)
{
    color = vec4(gl_FragCoord.z);
}

Render vertex shader:


#version 420 core

uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 shadow_matrix;

layout (location = 0) in vec4 position;
layout (location = 1) in vec3 normal;

out VS_OUT
{
    vec4 shadow_coord;
    vec3 N;
    vec3 L;
    vec3 V;
} vs_out;

// Position of light
uniform vec3 light_pos = vec3(100.0, 100.0, 100.0);

void main(void)
{
    // Calculate view-space coordinate
    vec4 P = mv_matrix * position;

    // Calculate normal in view-space
    vs_out.N = mat3(mv_matrix) * normal;

    // Calculate light vector
    vs_out.L = light_pos - P.xyz;

    // Calculate view vector
    vs_out.V = -P.xyz;

    // Light-space coordinates
    vs_out.shadow_coord = shadow_matrix * position;

    // Calculate the clip-space position of each vertex
    gl_Position = proj_matrix * P;
}

Render fragment shader:


#version 420 core

layout (location = 0) out vec4 color;

layout (binding = 0) uniform sampler2DShadow shadow_tex;

in VS_OUT
{
    vec4 shadow_coord;
    vec3 N;
    vec3 L;
    vec3 V;
} fs_in;

// Material properties
uniform vec3 diffuse_albedo = vec3(0.9, 0.8, 1.0);
uniform vec3 specular_albedo = vec3(0.7);
uniform float specular_power = 300.0;
uniform bool full_shading = true;

void main(void)
{
    // Normalize the incoming N, L and V vectors
    vec3 N = normalize(fs_in.N);
    vec3 L = normalize(fs_in.L);
    vec3 V = normalize(fs_in.V);

    // Calculate R locally
    vec3 R = reflect(-L, N);

    // Compute the diffuse and specular components for each fragment
    vec3 diffuse = max(dot(N, L), 0.0) * diffuse_albedo;
    vec3 specular = pow(max(dot(R, V), 0.0), specular_power) * specular_albedo;

    // Write final color to the framebuffer
    color = textureProj(shadow_tex, fs_in.shadow_coord) * mix(vec4(1.0), vec4(diffuse + specular, 1.0), bvec4(full_shading));
}

Another thing I am worried about is that a lot of questions online refer to glFrameRenderBuffer, what exactly is a renderBuffer? And since I’ve seen no one complain about me not using it, it should be no problem?

Also I am using glTexStorage2D instead of glTexImage2D, could that matter anything?

As the depth texture has different levels - mipmaps - could that cause issues?