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.