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
\$\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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.