Notes on Gamma

Gamma is a stigma, a curse and downright annoying. RGB colors have been messed with for me ever since someone told me that RGB colors needed to be gamma corrected. Gamma does for digital color what kerning does for typography.

This post is an attempt to keep up with Gamma.

innocence lost

Because, you see, only once I became fully aware of Gamma did things really start to fall apart. In my pre-gamma-aware innocence, I may have done a few things right.

Let me show what I mean:

Here, I generate a linear gradient using the GLSL Fragment shader. Suppose, creating a full-screen quad using this shader code snippet:

1
2
vec3 color = vec3(uv.x);       // increase brightness linearly with x-axis
outFragColor = vec4(color, 1); // output to RGB swapchain image 

G.L.S.L

And voila – a perceptually linear gradient.

    a conceptually linear gradient

A conceptually linear gradient, innocently created

But now, let’s be smart about this: we see that we are In fact Drawing on an sRGB monitor (most desktop monitors these days are), so we should probably use an sRGB image for the swapchain (these are the most common 8bpc Swapchain image format in Vulkan). Thus we need to somehow convert our RGB colors to sRGB. The most elegant way to do this is to use an image attachment with the sRGB format, which (at least in Vulkan) does the conversion automatically and accurately on texel save.

If we create the same shader as before, but now in an sRGB swapchain the non-linear RRGB encoding of the swapchain image should correct the sRGB monitor gamma. The brightness value of the gradient (as measured by the brightness meter) should now increase linearly on the screen. They do. It should look like a linear gradient, right?

Wrong.

Instead, we get this:

    a linear gradient

A perfectly ‘linear’ slope

It’s not like that Look Linear: Shaders do not claim the same space. Instead, it seems as if darker colors are too bright, while brighter colors appear washed out. Here’s what I expected to see:

    a conceptually linear gradient

a conceptually linear gradient

The difference is subtle, but look at both gradients and ask yourself: which appears to have more detail? Who is more balanced?

Even if the first shield Is more linear in terms of physical brightness, the second looks like More linear.

This seems counter-intuitive to me. But where intuition fails, ratio can help; And there is actually a rational explanation: visual perception is non-linear.

And you see those who are in darkness/Those who are in light disappear from sight

sRGB is an elegant form of perceptual compression: images at 8-bit-per-channel and below (for reasons of encoding efficiency). really want To be encoded as sRGB.

And if the monitor can display these sRGB images directly and natively (because the hardware applies the inverse of the sRGB gamma transform) – it’s a perfect match…

Practical applications for rendering using Vulkan

In Vulkan, if you can use the sRGB format for your swapchain image, you do not need to manually correct for sRGB gamma. Your pixels will automatically be stored in non-linear sRGB, and displayed linearly on the screen. This is great for encoding the highest amount of perceptual color contrast detail using a limited amount of bits (sRGB formats are typically about 8 bits per channel).

When image format names have a suffix *_SRGB sRGB gamma transformation has been applied transparently on all read texel And write texel This is useful, because we can only meaningfully mix color in linear space, mixing in non-linear space would not be (physically) correct, The Khronos Data Format specification has some great documentation on this topic,

But this means that if you want to render you have to undo the effect of the implicit sRGB gamma transform on texel write. conceptually linear gradient when using sRGB image backing. The function that the Vulkan driver implements for you on texel write is called srgb_oetfAbbreviation for “sRGB optical-electrical transfer function”.

To neutralize this function, you must apply inverse_srgb_oetfthis is it srgb_eotf “sRGB Electro-Optical Transfer Function” just before storing the texel.

Here’s how it would look using our schematic from earlier:

A perceptually linear gradient using sRGB

A perceptually linear gradient, corrected and encoded using sRGB

Create a perceptually linear gradient in an sRGB swapchain image

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
vec3 srgb_eotf(in const vec3 c) {
    bvec3 cutoff = lessThan(c, vec3(0.04045));
    vec3 lower = c/vec3(12.92);
    vec3 higher = pow((c + vec3(0.055))/vec3(1.055), vec3(2.4));
    return mix(higher, lower, cutoff);
}

void main () {
    vec3 color = vec3(uv.x);
    outFragColor = vec4(srgb_eotf(color),1);
}

G.L.S.L

And voila:

    a conceptually linear gradient

a conceptually linear gradient

Further reading:

Bonus

Thanks for sticking around after the final credits. Here’s some additional information that may come in handy one day in a CGI pub quiz:

Have you ever wondered what the “s” in RGB means? I neither – till now; I assumed that’s what it meant Very goodThe sad reality is more polite: apparently it means Standard“Standard RGB”, What is the standard?

RSS:

Be the first to know about new posts by subscribing to the RSS feed

Further posts:



<a href

Leave a Comment