Search code examples
c#.netopenglframebuffershadow-mapping

Using Depth FBO for shadow map causes weird behaviour


I am using Silk.Net for OpenGL interfacing, loading .obj files for model and material data. Everything works as I would want it, and tried to add Shadow Mapping to the whole thing (following LearnOpenGL tutorial). If I bind the framebuffer, texture and everything, but not do anything with it, the shader and rendering still works fine. But if I even just make a Clear() call, the screen remains black (I've found out that the unset texture in my shader, AlphaTexture returns full 1.0s for some reason, and also some of the calculations just break). I've spent a ton of time on this and am slowly starting to go insane. Please help, here are the relevant code, also the whole repo: Creating the framebuffer and its attachment (in Light.cs):

public unsafe void InitShadowMap(GL gl)
{
    // Generate framebuffer
    _shadowMapFbo = gl.GenFramebuffer();
    gl.BindFramebuffer(FramebufferTarget.Framebuffer, _shadowMapFbo);

    // Generate depth texture
    ShadowMap = gl.GenTexture();
    gl.BindTexture(TextureTarget.Texture2D, ShadowMap);
    gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.DepthComponent, ShadowWidth, ShadowHeight, 0, PixelFormat.DepthComponent, PixelType.Float, null);

    // Set texture parameters
    gl.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToBorder);
    gl.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToBorder);
    gl.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
    gl.TexParameterI(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);

    // Set border color
    float[] borderColor = { 1.0f, 1.0f, 1.0f, 1.0f };
    gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, borderColor);

    // Attach depth texture as FBO's depth buffer
    gl.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, TextureTarget.Texture2D, ShadowMap, 0);
    gl.DrawBuffer(DrawBufferMode.None);
    gl.ReadBuffer(ReadBufferMode.None);

    // Check if framebuffer is complete
    if (gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != GLEnum.FramebufferComplete)
    {
        Console.WriteLine("Framebuffer not complete");
    }

    // Unbind framebuffer
    gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
}

Binding the framebufffer:

public void BindFramebuffer(GL gl)
{
    gl.BindFramebuffer(FramebufferTarget.Framebuffer, _shadowMapFbo);
    gl.Viewport(0, 0, ShadowWidth, ShadowHeight);
    gl.Clear(ClearBufferMask.DepthBufferBit); // even just this breaks everything
}

And using the whole thing (Scene.cs):

public unsafe void Draw(GL gl, Shader shader)
{
    int[] oldViewport = new int[4];
    gl.GetInteger(GetPName.Viewport, (Span<int>)oldViewport);
    Lights[0].BindFramebuffer(gl);
    _shadowShader!.Use(gl);
    var lightSpaceMatrix = Lights[0].GetLightSpaceMatrix();
    gl.UniformMatrix4(_shadowShader.GetUniformLocation(gl, ViewMatrixVariableName), 1, false, (float*)&lightSpaceMatrix);
    // Program.CheckError();
    // // Program.CheckError();
    // foreach (var obj in Objects)
    // {
    //     obj.Draw(gl, _shadowShader, false);
    // }
    gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
    // Program.CheckError();
    shader.Use(gl);
    // Program.CheckError();
    gl.UniformMatrix4(shader.GetUniformLocation(gl, LightSpaceMatrixVariableName), 1, false, (float*)&lightSpaceMatrix);
    // Program.CheckError();
    gl.Viewport(oldViewport[0], oldViewport[1], (uint)oldViewport[2], (uint)oldViewport[3]);
    // Program.CheckError();
    //
    gl.ActiveTexture(TextureUnit.Texture15);
    // Program.CheckError();
    gl.BindTexture(TextureTarget.Texture2D, Lights[0].ShadowMap);
    // Program.CheckError();
    gl.Uniform1(shader.GetUniformLocation(gl, ShadowMapVariableName), 15);
    // Program.CheckError();
    //
    gl.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
    SetViewerPosition(gl, shader);
    SetViewMatrix(gl, shader);
    SkyBox?.Draw(gl, shader);
    foreach (var obj in Objects)
    {
        obj.Draw(gl, shader);
    }
}

Even if I don't add anything shadow related to the shader, it gets ruined, so I don't think it's possible the shader is bad (I've also followed LearnOpenGL tutorial for that), but it's in the Resources/


Solution

  • It turns out I just did my texture mapping wrong in general, not related to any of the shadow-mapping code. When I was setting up the textures, those that were not loaded (for example, a shadow map, roughness map etc) were bound to 0, but I also set the uniform for that texture to 0. That is not correct, since texture unit 0 was a valid texture, in my case, the diffuse texture. So, for every not loaded texture, it was loading the diffuse texture.