Gamma Correction vs Color Picker

I have some confusion regarding gamma correction and the output of my shaders. I'll start with the setup, then explain my current understanding of the matter, and finally the question.

Setup

I'm using OpenGL ES 3.2 on Android.

I have a texture (skybox) and an object in the scene. Both are rendered to an off-screen framebuffer that has a texture as color attachment. This texture is then sampled for on-screen rendering.

#version 320 es

precision mediump float;

in vec3 o_textureCoords;

out vec4 o_fragColor;

uniform samplerCube u_skyboxTexture;

vec4 srgbToLinear(vec4 srgbColor) {
return vec4(pow(srgbColor.rgb, vec3(2.2)), srgbColor.a);
}

void main() {
o_fragColor = texture(u_skyboxTexture, o_textureCoords);               // Left
o_fragColor = srgbToLinear(texture(u_skyboxTexture, o_textureCoords)); // Right
}


#version 320 es

precision mediump float;

const vec3 lightColor = vec3(1.0, 1.0, 1.0);
const vec3 objectColor = vec3(0.4, 0.2, 0.1);

out vec4 o_fragColor;

void main() {
// ...
vec3 light = (ambient + diffuse + specular) * objectColor;
o_fragColor = vec4(light, 1.0);
}


#version 320 es

precision mediump float;

in vec2 o_texCoords;

out vec4 o_fragColor;

uniform sampler2D u_texture;

void main() {
vec4 color = texture(u_texture, o_texCoords);
o_fragColor = color;                                           // Left
o_fragColor = vec4(pow(color.rgb, vec3(1.0 / 2.2)), color.a);  // Right
}


Let's consider two cases:

• Left: not concerning myself with colorspace and gamma correction at all
• Right: transforming the texture into linear colorspace and gamma correction at the end

The object color is (0.4, 0.2, 0.1). When I type these values into a color picker I get , which looks similar to the rabbit-color on the left.

My Understanding

1. Assume images (like the skybox) to be in sRGB space most of the time
2. Try to work in linear colorspace as long as possible, and gamma correct at the end

If c is my color, then because of (1) I should assume that when I retrieve a texel it actually is gamma-corrected (c ^ (1/gamma)). So to get c I do (c ^ (1/gamma)) ^ gamma as in srgbToLinear().

Because of (2) I leave my object color as is when shading the object and only apply gamma correction at the very end.

Also, because of gamma correction we can expect the resulting images to look a bit brighter.

Question

With my current understanding I would expect the image on the right to be the correct one. I get that the rabbit should look brighter, but especially when comparing it to what the color picker outputs, I can't help but feel confused about it. Is it simply my expectations that fail me or am I doing something wrong here?

Your understanding is correct, and I think the image on the right looks like the more correct one. I think the root of your confusion is that the constant objectColor you've defined in your shader is being interpreted as a gamma-corrected color in the left image, and the same values are being interpreted as a linear color in the right image. To be more consistent, in the latter case you should apply srgbToLinear to the objectColor as well. Then, I expect, the two images will look more like the same dark reddish color.
Note that if I interpret objectColor as linear, then apply the 1/2.2 gamma to it, it comes out as this color:
BTW, a point of terminology quibble. Gamma 2.2 is not the same thing as the sRGB nonlinear curve. The sRGB curve is the more complicated one given in by $$C_\text{linear}$$ and $$C_\text{srgb}$$ in the formulas here, with the 2.4 power and the 0.055s. Now it's fine to use gamma 2.2 instead if you prefer, but I think you shouldn't call it sRGB, as it's not the curve defined by the sRGB standard and it will cause confusion.