I have made a simple demo in OpenGL to better understand gamma correction. I have realized that it breaks some of my assumptions, so I was hopping someone can help me understand.
For reference, I have made this image with GIMP. It shows the the whole black-grey-white spectrum in 10% steps. These is the visual spectrum (gamma).
My OpenGL demo does the following:
- Clear the screen with 50% grey
- Draw a big rectangle with a shader that outputs 50% grey (hard-coded in the shader)
- Draw a smaller rectangle that samples single-pixel, 50% grey, sRGB texture.
Now I'll get into more detail about how I do each of the 3 steps
1) Clear screen
I simply do:
glClearColor(0.5, 0.5, 0.5, 0);
glClear(GL_COLOR_BUFFER_BIT);
2) Draw big rectangle
I draw a rectangle with this pixel shader:
layout(location = 0) out vec4 o_color;
void main()
{
o_color = vec4(vec3(0.5), 1);
}
3) Draw small rectangle
I create a 50% grey, sRGB, one-pixel texture:
u32 tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
const glm::u8vec3 pixelColor(127);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, &pixelColor[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
And draw with this shader:
layout(location = 0) out vec4 o_color;
in vec2 v_tc;
uniform sampler2D u_tex;
void main()
{
o_color = vec4(texture(u_tex, v_tc).rgb, 1);
}
Result
This is the resulting render.
Interpreting the result
This is not the result I expected. I will try to explain why. Hopefully you can help me figure out what's wrong about my reasoning.
My back-buffer has sRGB format (I have verified this with RenderDoc). As far as I know, you should work with linear values in your shaders. When you write the linear color in fragment shader, it will be automatically "compressed" to gamma space (approximately x^(1/2.2)).
The reverse happens when you sample an sRGB texture. The colors are stored in gamma space and, when sampling, the value is automatically "decompressed" to linear space (approximately x^2.2).
1) Clear screen
For the color clear, I wasn't sure what to expect. There were two options:
- It might behave like a fragment shader that writes a linear value. In this case, the color would be automatically converted to sRGB, so I would see a lighter grey.
- It just writes the raw data to framebuffer. In this case I would see a perceptual 50% grey.
From the resulting image, I see perceptual 50% grey. But it bothers me that it's the same shade of grey as the big rectangle.
2) Big rectangle
I see perceptual 50% grey. How is this possible?
I'm writing 0.5 linear value in the fragment shader. That should correspond to ~73% grey in the gamma (perceptual) spectrum.
3) Small rectagle
I see a dark grey (I think it's around ~20% perceptual grey). I wasn't expecting this at all.
What I expected is that the 50% sRGB grey texel would be brought into the fragment shader as ~21.7% linear-scale grey (decompress). When storing that value, the opposite operation be performed (compress), resulting in 50% grey again.
I'm quite confused. And a bit ashamed that after many years working with OpenGL I still haven't grasped the topic. Hopefully you can help me out.
This is the whole code for the demo: https://gist.github.com/tuket/034101ef4132dad7005bbb5d6259ab1f


