SRGB 和gamma校正

为了让你直观地感受到 Gamma 校正(Gamma Encoding) 的威力,我们来做一次具体的数值对比。

我们假设使用的标准 Gamma 值为 2.2
公式为:
$$ \text{Gamma值} = \text{线性值}^{(1 / 2.2)} \approx \text{线性值}^{0.4545} $$

这里我们将对比 线性空间(Linear,物理真实亮度)Gamma 空间(sRGB,存入图片或屏幕显示的数值)。为了方便理解,我同时列出了 0.0 - 1.0 (浮点数)0 - 255 (8位整数) 的对应值。

1. 数值对比表

请特别注意暗部的变化幅度:

描述 线性值 (Linear)
物理光照强度
线性 8-bit
(如果直接存)
Gamma 校正后 (sRGB)
存入显存/图片的数值
sRGB 8-bit
(最终色值)
变化分析
纯黑 0.0 0 0.0 0 无变化
极暗部 0.01 (1% 亮度) 2 0.123 31 暴增 12 倍! 保留了暗部细节
暗影 0.1 (10% 亮度) 25 0.351 90 提升约 3.5 倍,从几乎看不见变成了深灰
物理中灰 0.218 (21.8% 亮度) 55 0.500 128 关键点:物理亮度的 21% 对应数据上的 50%
中间色 0.5 (50% 亮度) 128 0.730 186 提升显著,物理的一半亮度看起来像 73% 的白
亮部 0.8 (80% 亮度) 204 0.903 230 提升幅度变小
纯白 1.0 255 1.0 255 无变化

2. 核心观察与图解分析

A. 暗部的“扩容” (最重要的作用)

看表格中的第二行(极暗部):

  • 线性世界:亮度只有 0.01。如果在 0-255 的整数中,它对应 2。这意味着如果你不做 Gamma 校正直接存,0 到 1% 的亮度之间只有 0, 1, 2 这三个台阶,会出现极度明显的色带,暗部细节全部丢失(死黑)。
  • Gamma 校正后:数值变成了 0.123 (对应 8-bit 的 31)。
  • 结论:Gamma 校正把暗部极小的一段物理亮度,强行“拉伸”到了较大的数据空间中。它把宝贵的 8-bit 存储空间更多地分配给了人眼敏感的暗部。

B. 著名的“中灰” (Middle Gray)

看表格中的第四行:

  • 在摄影和渲染中,有一个经典的认知:18%~22% 的物理反射率,人眼看起来是 50% 的灰色。
  • 线性值 0.218 $\rightarrow$ Gamma 值 0.5
  • 这就是为什么如果你错误地使用了线性工作流(比如忘记做 Gamma 校正),直接把 0.218 输出到屏幕,屏幕会显示出很暗的颜色(物理上的 21%),而你原本期望的是一种“正中间的灰色”。

C. 为什么不用 SRGBA8 会导致画面“黑死”?

如果你在 Shader 里算出的光照是 0.1 (线性):

  1. **错误做法 (用 RGBA8)**:写入 0.1 $\rightarrow$ 屏幕收到 0.1 $\rightarrow$ 屏幕 Gamma 2.2 压暗 $\rightarrow$ 最终人眼看到亮度 $0.1^{2.2} \approx 0.006$。
    • 结果:本来该有影子的细节,直接变成了纯黑,画面对比度极高,像烧焦了一样。
  2. **正确做法 (用 SRGBA8)**:写入 0.1 $\rightarrow$ 硬件自动转为 0.35 (存入显存) $\rightarrow$ 屏幕收到 0.35 $\rightarrow$ 屏幕 Gamma 2.2 压暗 ($0.35^{2.2} \approx 0.1$) $\rightarrow$ 人眼看到亮度 0.1
    • 结果:所算即所见,光影柔和自然。

3. 代码模拟

如果把这个过程写成 Shader 代码,区别就是这样:

假设我们计算出的颜色是 vec3 linearColor = vec3(0.218, 0.218, 0.218);

1
2
3
4
5
6
7
8
// 情况 1: 设置了 kEAGLColorFormatRGBA8 (普通格式)
// 你必须自己手动写代码进行 Gamma 校正,否则画面会非常暗
vec3 finalColor = pow(linearColor, vec3(1.0/2.2)); // 手动校正:变成 0.5
gl_FragColor = vec4(finalColor, 1.0);

// 情况 2: 设置了 kEAGLColorFormatSRGBA8 (sRGB 格式)
// 硬件自动处理,你只需要输出线性值
gl_FragColor = vec4(linearColor, 1.0); // 写入时硬件自动变成 0.5

总结

Gamma 校正本质上是一个“劫富济贫”的过程:它压缩了亮部的数据精度,极大地扩充了暗部的数据精度,以符合人眼对暗部细节敏感的生理特性。