伽马函数笔记
Notes on Gamma

原始链接: https://poniesandlight.co.uk/reflect/gamma/

## Gamma 与感知线性带来的困扰 Gamma 校正作为数字色彩中不可避免的一部分,最初让作者感到沮丧,因为它效果与直觉相反。在图形着色器中看似线性的渐变,渲染到标准 sRGB 显示器上时会显得*不对劲*——暗色调显得过亮,亮色调显得失真,尽管在技术上它们是物理线性的。 这并非缺陷,而是人类感知的必然结果。我们的眼睛对暗部区域的对比度变化更敏感,这意味着一个真正*感知*线性的渐变需要非线性的亮度值分布。 sRGB 编码利用了这一点,有效地压缩亮度信息,以在感知的最敏感区域最大化细节。直接渲染到 sRGB 格式可以自动处理很多问题,但要实现真正意义上的感知线性渐变,必须在输出颜色之前*撤销* sRGB 校正,使用类似 `srgb_eotf` 的函数。 归根结底,理解 Gamma 并非为了追求物理上的精确,而是为了创造出对人眼*看起来*正确的图像,有效地利用有限的位深,以获得最佳的视觉对比度。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Gamma 笔记 (poniesandlight.co.uk) 3点 由 todsacerdoti 1小时前 | 隐藏 | 过去 | 收藏 | 讨论 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

Gamma is a blight, a curse, and utterly annoying. Ever since somebody told me that RGB colours need to be Gamma corrected, RGB colours were spoilt for me. Gamma does to digital colour what kerning does to typography.

This post is an attempt to get even with Gamma.

Lost Innocence 

Because, you see, only once I became fully aware of Gamma, things really started to fall apart. In my pre-gamma-aware innocence, I must have done some things right.

Let me show what I mean:

Here, I generate a linear gradient using a GLSL fragment shader. Say, drawing 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 
glsl

And voila - a perceptually linear gradient.

 A perceptually linear gradient
A perceptually linear gradient, innocently created

But now, let’s get clever about this: We notice that we’re actually drawing to an sRGB monitor (most desktop monitors are, nowadays), so we should probably use an sRGB image for the swapchain (these are the most common 8bpc swapchain image formats in Vulkan). Thus we somehow need to convert our RGB colours to sRGB. The most elegant way to do this is to use an image attachment with an sRGB format, which (at least in Vulkan) does the conversion automatically and precisely on texel save.

If we draw the same shader as before, but now into an sRGB swapchain the swapchain image’s non-linear rRGB encoding should correct the sRGB monitor gamma. The gradient’s brightness values (as measured by a brigthness meter) should now increase linearly on-screen. They do. That should look like a linear gradient, right?

Wrong.

Instead, we get this:

 A linear gradient
A perfectly ’linear’ gradient

This doesn’t look linear: shades don’t seem to claim equal space. Instead, it looks as if dark shades are too bright, while bright shades wash out. Here’s what I’d expected to see:

 A perceptually linear gradient
A perceptually linear gradient

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

Even though the first gradient is more linear in terms of physical brightness, the second one looks more linear.

I find this counter-intuitive. But where intuition fails, ratio may help; and there is indeed a rational explanation: Visual perception is non-linear.

And You See The Ones In Darkness/Those In Brightness Drop From Sight 

sRGB is an elegant form of perceptual compression: images at 8-bits-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) – that’s just a perfect match…

Practical applications for rendering using Vulkan 

In Vulkan, if you can use an sRGB format for your swapchain image, then you don’t have to manually correct for sRGB gamma. your pixels will be automatically stored in non-linear sRGB, and displayed linearly on-screen. this is great for encoding the highest amount of perceptual colour contrast detail using limited amount of bits (sRGB formats are usually about 8 bit per channel).

When image format names contain the suffix *_SRGB the sRGB gamma transform is applied transparently on every texel read and texel write . This is useful, because we can only meaningfully blend color in linear space, blending in non-linear space would not be (physically) correct. The specs on Khronos Data Formats have some great documentation on this topic.

But this means that you need to undo the effect of the implicit sRGB gamma transform on texel write if you want to render a perceptually linear gradient while using an sRGB image backing. The function that the vulkan driver applies for you on texel write is called the srgb_oetf, short for “sRGB optical-electrical transfer function”.

To neutralise this function, you must apply the inverse_srgb_oetf, that’s the srgb_eotf “sRGB electo-optical transfer function”, just before you store the texel.

Here is how this would look like using our schematic from before:

A perceptually linear gradient using srgb
A perceptually linear gradient, corrected, and encoded using sRGB

Draw a perceptually linear gradient into 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);
}
glsl

And voila:

 A perceptually linear gradient
A perceptually linear gradient

Further reading: 

Bonus 

Thank you sticking around past the end credits. Here’s an extra bit of information that might come in handy at a cgi pub quiz one day:

Did you ever wonder what the “s” in RGB stands for? Me neither – until now; I assumed it stood for super. The sad reality is more humble: Apparently it stands for Standard. “Standard RGB”. What a standard.

RSS:

Find out first about new posts by subscribing to the RSS Feed

Further Posts:

联系我们 contact @ memedata.com