2
\$\begingroup\$

My understanding of gamma correction is as follows: We want to do colour math in linear space so we can lerp etc. between colour values and get the results we expect. But human eyes don't have a linear response to light, so we need to modify the colours just before displaying them. sRGB is then commonly used colour space for this. Furthermore, since paint programs and colour pickers talk about colour in sRGB, the colours typically need to be linearized before math is performed on them.

EDIT 2: Turns out I had the following conversions backwards. The rest of the question is left unchanged for context, but I don't want to lead someone else down the same incorrect path.

The actual curve mapping sRGB to linear space is an exponential curve over the range 0 to 1. Linear to sRGB is raising the colour to the power of 2.2. sRGB to linear is taking the 2.2th root of the colour. So since we are in the range 0 to 1 Linear to sRGB make the colours less bright, and sRGB to linear does the opposite.

In my program I'm pulling values out of an GL_R8 texture, interpreting them as alpha values and taking a vec4 as a vertex attribute we'll call colour and multiplying it by vec4(1.0, 1.0, 1.0, alpha) to produce the final colour in the fragment shader. If I set the colour attribute to a known colour and enable the GL_FRAMEBUFFER_SRGB flag, then the colour I get is too bright, and it is exactly as bright as it would be if the sRGB to linear conversion was not applied.

For example if I pass in the colour #222222 I get #666666 actually rendered to the screen according to an external colour picking tool. If I convert the colour #222222 to three floats by dividing by 255.0 I get three copies of 0.13333333. 0x22 = 34 in base 10, 34.0 / 255.0 = 0.13333333. The 2.2th root of 0.13333333 is 0.40017032. 0.40017032 * 255.0 gives us 102. 102 in hex is the 0x66 we were expecting.

At first I thought that the problem was that it wasn't applying the linear to sRGB conversion to the output of the shader for some reason, since things were too bright. So I did some searching and found this answer but that states that the only way that the linear to sRGB conversion will only be applied if GL_FRAMEBUFFER_SRGB is enabled. I'm getting the opposite behavior: the colours are too bright only if GL_FRAMEBUFFER_SRGB is enabled!

I then realized that openGL can't magically know what the semantics of my colour vertex attribute are. So it can't be automatically converting my colour to linear space, unless it's doing something weird and error prone like checking the name of the attribute or just converting every vec3 or vec4. If that was a desired feature, a shader keyword or something would have been a better way to do that.

So I'm confused as to how I'm getting this result. I'm not currently doing any colour math in the shader besides what I've already mentioned, besides alpha blending. But I might want to do more of that later, including loading more textures in the fragment shader so I would like to sort out this weirdness, rather than just disabling GL_FRAMEBUFFER_SRGB and leaving the same mess for myself if I do end up doing more complicated stuff later.

EDIT 1: My windowing library says that my pixel format is sRGB so I don't think that is the issue. If I run glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_FRONT_LEFT, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &result), result gets set to GL_SRGB. If I check GL_FRONT_RIGHT, GL_BACK_LEFT, and GL_BACK_RIGHT I get GL_LINEAR, GL_SRGB, and GL_LINEAR respectively. But according to this page since I didn't set any stereo rendering settings, then the RIGHT buffers shouldn't matter.

\$\endgroup\$
  • \$\begingroup\$ Is the texture in the GL_SRGB format? \$\endgroup\$ – Bálint Oct 30 '19 at 13:30
  • \$\begingroup\$ @Bálint The texture I'm reading from? That's in GL_R8 since that's what I pass as the third param to glTexImage2D. \$\endgroup\$ – Ryan1729 Oct 30 '19 at 21:56
  • 2
    \$\begingroup\$ You have your mappings backwards, see here: entropymine.com/imageworsener/srgbformula. Also the "known colour" you are passing in is probably in srgb space already, so when you apply the linear to srgb mapping you get a colour that you think is too bright. \$\endgroup\$ – Alex Oct 30 '19 at 22:20
  • \$\begingroup\$ @Alex Ah! Thank you. In that case I think I must be in case three in that other answer I mentioned: "The source buffer has NON-sRGB format, the destination has sRGB format. Conversion from linear to sRGB will be done if, and only if GL_FRAMEBUFFER_SRGB is enabled." Because I'm not reading colours out of a texture at all, there's no place for it to do the sRGB to Linear transformation. It makes sense now. \$\endgroup\$ – Ryan1729 Oct 30 '19 at 22:37
0
\$\begingroup\$

It's not the display colors that got brighter, only the textures. You need to set the internal format of them to GL_SRGB:

glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, ...)
|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ Do you mean the texture I'm reading from? But I'm only using the values in there as alpha values, which AFAIK should not be ever modified due to sRGB settings. So since they won't be converted from linear to sRGB, I don't want them to be converted from sRGB to linear. Sure enough I tried it on those textures anyway and the colours were unchanged but the alpha values got smaller, making the edges of the light things I am rendering against a dark background noticeably dark. If you mean the final framebuffer, I've already enabled sRGB on the windowing library I'm using. I'll look closer at that. \$\endgroup\$ – Ryan1729 Oct 30 '19 at 20:02
  • \$\begingroup\$ @Ryan1729 If you output your own colors, you need to apply the srgb conversion yourself: learnopengl.com/Advanced-Lighting/Gamma-Correction \$\endgroup\$ – Bálint Oct 30 '19 at 23:04
0
\$\begingroup\$

My shaders work in the linear space, and I rely on glEnable( GL_FRAMEBUFFER_SRGB ) to do the conversion.

To drive the monitor (which has a very low response for dark/low values,) OpenGL needs to increase the (dark) values in my framebuffer.

So yes, enabling GL_FRAMEBUFFER_SRGB is supposed to make your rendered image brighter.

Now, where it gets interesting is, that as far as I am aware, none of the 3D graphics fileformats properly specifies the colour space for the colour-per-vertex or colour-per-face that are in the file. So you typically assume them to be non-linear.

So, as you load an OBJ file, you should expect that the author intended the colours to be in non-linear space. Hence, before using them in your shaders, you need to convert them to linear (make them darker.)

OpenGL will then take your framebuffer output and make them lighter again (as you observed.)

I recommend the following sanity check for you:

  • Just render a picture with nothing in it, other than glClear() with colour (0.05, 0.05, 0.05)
  • With GL_FRAMEBUFFER_SRGB disabled, it should be nearly indistinguishable from black.
  • With GL_FRAMEBUFFER_SRGB enabled, it should be grey, because 0.05 is then considered linear, and hence produce 5% of the photons that pure white does: This is plenty of light to be discernible from black.

0.05 as SRGB and as linear

So, yes, with a linear-colour mind-space, you need to be aware that low values in the framebuffer (or low values in your object's model) are actually a lot brighter than what you are used to in the traditional gamma-curved colour space or CRTs and LCDs.

Let me conclude by linking to Tom Forsyth's excellent article on SRGB.

|improve this answer|||||
\$\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.