2
\$\begingroup\$

I am trying to do hdr rendering with two shader programs: one simply draws a hdr texture on a fullscreen quad and the other one does tonemapping. I am trying either linear tonemapping

colorOut = colorIn/(colorIn+1)

or exposure with gamma correction

colorOut = pow(1 - exp(-colorIn*exposure), 1/gamma)

I am currently using OpenTK 1.1.2225 and Magick.NET-Q16-HDRI-AnyCPU. Shader programs are correctly loaded by my LoadShaderProgram function.

The c# code is:

class MyClass: GameWindow
{
    int textureID, colorBuffer, rbo, shaderProgram, tonemapProgram quadVAO;
    float exposure = 1.0f;

    float[] quadVertices = {
         // Positions        // Texture Coords
         -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
         -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
         1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
     };

    public MyClass() : base(1000,700,new GraphicsMode(),"My Title",GameWindowFlags.Default)
    { }

    protected int LoadShaderProgram(string VSSource, string FSSource)
    {
        //tested code, returns the program id
    }

    protected override void OnLoad(System.EventArgs e)
    {
        try
        {
            VSync = VSyncMode.On;
            GL.Enable(EnableCap.Texture2D);
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Less);

            // Setup quad VAO
            quadVAO = GL.GenVertexArray();
            int quadVBO = GL.GenBuffer();
            GL.BindVertexArray(quadVAO);
            GL.BindBuffer(BufferTarget.ArrayBuffer, quadVBO);
            GL.BufferData(BufferTarget.ArrayBuffer, quadVertices.Length * sizeof(float), quadVertices, BufferUsageHint.StaticDraw);
            GL.EnableVertexAttribArray(0);
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), 0);
            GL.EnableVertexAttribArray(1);
            GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));

            MagickImage img = new MagickImage(@"../../Cubemaps/GravelPlaza_REF.hdr");
            int width = img.Width;
            int height = img.Height;
            float[] data = img.GetPixels().ToArray();

            //Load lat/long hdr map
            textureID = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, textureID);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb32f, width, height, 0, PixelFormat.Rgb, PixelType.Float, data);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, Convert.ToInt32(TextureWrapMode.Repeat));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, Convert.ToInt32(TextureWrapMode.Repeat));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, Convert.ToInt32(TextureMinFilter.Linear));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, Convert.ToInt32(TextureMagFilter.Linear));
            GL.BindTexture(TextureTarget.Texture2D, 0);

            //Setup color buffer of framebuffer
            colorBuffer = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, colorBuffer);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f, ClientSize.Width, ClientSize.Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, Convert.ToInt32(TextureWrapMode.ClampToEdge));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, Convert.ToInt32(TextureWrapMode.ClampToEdge));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, Convert.ToInt32(TextureMinFilter.Linear));
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, Convert.ToInt32(TextureMagFilter.Linear));
            GL.BindTexture(TextureTarget.Texture2D, 0);

            //Setup renderbuffer of framebuffer
            rbo = GL.GenRenderbuffer();
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, rbo);
            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent32, ClientSize.Width, ClientSize.Height);
            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);

            ErrorCode er = GL.GetError();
            Console.WriteLine(er.ToString());

            //Setup framebuffer 
            hdrFBO = GL.GenFramebuffer();
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, hdrFBO);
            GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, colorBuffer, 0);
            GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, rbo);
            if(GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) == FramebufferErrorCode.FramebufferComplete)
            {
                Console.WriteLine("framebuffer ok");
            }
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);

            //Load tonemap shader programs
            string vsource = new StreamReader(@"../../Shaders/TonemapVertexShader.glsl").ReadToEnd();
            string fsource = new StreamReader(@"../../Shaders/TonemapFragmentShader.glsl").ReadToEnd();
            tonemapProgram = LoadShaderProgram(vsource, fsource);
           //Load quad shader programs
            vsource = new StreamReader(@"../../Shaders/quadVshader.glsl").ReadToEnd();
            fsource = new StreamReader(@"../../Shaders/quadFshader.glsl").ReadToEnd();
            shaderProgram= LoadShaderProgram(vsource, fsource);

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }

}

    protected override void OnRenderFrame(FrameEventArgs e)
    {
        GL.ClearColor(Color.Purple);
        //HDR PASS
        GL.BindFramebuffer(FramebufferTarget.Framebuffer, hdrFBO);

            GL.UseProgram(shaderProgram);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, textureID);
            GL.BindVertexArray(quadVAO);
            GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
            GL.BindVertexArray(0);
       GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
      //TONEMAPPING
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
      GL.UseProgram(tonemapProgram);
      GL.ActiveTexture(TextureUnit.Texture0);
      GL.BindTexture(TextureTarget.Texture2D,colorBuffer);
      GL.Uniform1(GL.GetUniformLocation(tonemapProgram, "exposure"), exposure);
      GL.BindVertexArray(quadVAO);
      GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
      GL.BindVertexArray(0);
      SwapBuffers();
      }

    public static void Main()
    {
        try
        {
            using (MyClass p = new MyClass())
            {
                p.Run(60);
            }
        }
    }
}

quadVShader and TonemapVShader are the same, they just set glPosition on the screen and pass texcoords:

#version 400
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 texCoords;

out vec2 TexCoords;


void main()
{
    gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
    TexCoords = texCoords;
}

quadFShader is rather simple, just samples the hdr image and writes the color in:

#version 400 
out vec4 color;
in vec2 TexCoords;


uniform sampler2D hdrBuffer;

void main()
{
   color = vec4(texture(hdrBuffer,TexCoords).rgb, 1.0f);
}

In TonemapFShader I comment out the method I'm not using:

#version 400 
out vec4 color;
in vec2 TexCoords;


uniform sampler2D hdrBuffer;
uniform float exposure;

void main()
{
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // reinhard
    //vec3 result = hdrColor / (hdrColor + vec3(1.0));
    // exposure
    vec3 result = vec3(1.0) - exp(-hdrColor * exposure);
    //gamma correct    
    result = pow(result, vec3(1.0 / gamma));

    color = vec4(result, 1.0f);
}

Various error checking don't return any error message and "framebuffer ok" is printed.

What I tried in order to debug is checking the values of result, with the linear tonemapping I get all ones (vec3(1f)), with the exposure+gamma one I get values between 0 and 1 but one problem is that they are all the same while they are being sampled from a texture which is not monochromatic. The second problem is that the screen is completely white, in both cases, even when "result" has components between 0 and 1.

P.S. I checked the code 100 times before posting, I hope there are no typos but I'm not perfect, please don't get mad if you think you got the answer but it's really a typo and I tell you.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Turned out exposure = 1.0f was way too high for the image I was using. The results of tonemapping with these two formulas I found on this tutorial are not so pretty with real hdr images, just with analytic lights.

I got a very bad, but at least visible, result with exposures around 10e-5.

\$\endgroup\$
1
  • \$\begingroup\$ I was having the same problem and stumbled across the root of this problem: I didn't want to use MagickImage so I wrote a wrapper for stbi_load_f and after hours of debugging I noticed that stbi remaps HDR to LDR when using load_f. Now I can use the same formulas like in the tutorial and lighting looks way better aswell. \$\endgroup\$
    – BlakkM9
    Jun 15, 2021 at 15:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .