OpenGL sRGB experiment

116 Views Asked by At

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).

enter image description here

My OpenGL demo does the following:

  1. Clear the screen with 50% grey
  2. Draw a big rectangle with a shader that outputs 50% grey (hard-coded in the shader)
  3. Draw a smaller rectangle that samples single-pixel, 50% grey, sRGB texture.

enter image description here

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.

enter image description here

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

0

There are 0 best solutions below