1996年,HP和Microsoft联合发布了一个名为sRGB的色彩空间规范。这份文档的初衷很简单:让数字图像在不同设备上显示一致。将近三十年后,这个目标依然没有完全实现——你在电脑上精心调整的照片发到手机上看起来完全不同,设计师交付的作品在客户显示器上"面目全非"。
问题的根源不在于设备质量,而在于一个被大多数开发者忽视的概念:色彩空间与伽马校正。这不是设计师的玄学问题,而是一个有着明确数学定义和物理原理的工程问题。
一个直觉性的问题:RGB(128,128,128)真的是50%亮度吗
打开任意图像编辑软件,创建一个RGB值为(128, 128, 128)的灰色。直觉上,这应该是"中等灰色"——介于纯黑(0,0,0)和纯白(255,255,255)的正中间。但物理上,这个像素发出的光能量只有纯白色的约22%,而不是50%。
这不是显示器的缺陷,而是人眼感知的特性。人眼对亮度的感知是非线性的:要让一个光源看起来比另一个"亮一倍",实际光能量需要增加约4.5倍(更精确地说是2.2次方的变化)。换句话说:
- 物理亮度翻倍 → 人眼感知只增加约40%
- 人眼感知翻倍 → 物理亮度需要增加到约218%
这个非线性关系可以用幂函数描述:
$$L_{\text{perceived}} \approx L_{\text{physical}}^{0.45}$$其中$L_{\text{physical}}$是物理光能量,$L_{\text{perceived}}$是人眼感知的亮度。这个指数约等于$1/2.2 \approx 0.45$,这正是伽马校正的核心数值。
CRT显示器的意外遗产
伽马值为2.2的历史来源是一个有趣的巧合。
早期的CRT(阴极射线管)显示器有一个物理特性:电子枪的输入电压与屏幕亮度之间存在非线性关系。具体来说,亮度的对数与电压的对数呈线性关系,斜率约为2.2到2.5。
$$I \propto V^{\gamma}$$其中$I$是亮度,$V$是电压,$\gamma$(gamma)是幂指数,典型值约为2.2。
这个特性源于CRT的物理工作原理:电子从阴极发射后,需要被加速电压加速才能轰击荧光粉。电子束的电流与控制电压的关系服从Child-Langmuir定律,这是一个幂律关系。
在CRT时代,这个非线性被视为一个需要"补偿"的问题。电视广播系统在发送信号时预先对图像进行"伽马编码"(提升暗部),让CRT的非线性响应将其"解码"回来。结果就是:广播系统和CRT显示器形成了一个互相抵消的非线性变换。
然后人们发现了一个有趣的巧合:CRT的伽马响应(约2.2)正好与人眼感知亮度特性的逆函数(约1/2.2)非常接近。这意味着:
- 如果图像数据是"伽马编码"的(已经过$1/2.2$变换)
- CRT显示器的伽马响应会自动将其转换回线性亮度
- 最终结果恰好匹配人眼的感知特性
这是一个"两个错误变成一个正确"的典型案例。CRT的非线性原本是个缺点,但恰好与人类视觉的非线性互补。
sRGB的诞生:妥协的标准化
1996年,HP和Microsoft面临一个实际问题:网络上的数字图像在不同设备上显示得乱七八糟。每个显示器有自己的伽马值,每个扫描仪有自己的色彩响应,没有任何统一标准。
他们的解决方案是sRGB(standard RGB)。这个色彩空间的设计目标很明确:最小化色彩管理的开销。
sRGB规范包含三个核心定义:
1. 色域(Gamut)
定义了RGB三个原色的精确坐标,基于当时主流CRT显示器使用的荧光粉:
- R: x=0.64, y=0.33
- G: x=0.30, y=0.60
- B: x=0.15, y=0.06
这些数值来自ITU-R BT.709标准,原本是为高清电视制定的。
2. 白点
D65标准光源,色温约6500K,坐标x=0.3127, y=0.3290。这代表了"平均日光"的颜色。
3. 传递函数(Transfer Function)
这是最关键的部分。sRGB的传递函数不是纯粹的幂函数,而是一个分段函数:
编码(线性→sRGB):
$$C_{\text{sRGB}} = \begin{cases} 12.92 C_{\text{linear}} & C_{\text{linear}} \leq 0.0031308 \\ 1.055 C_{\text{linear}}^{1/2.4} - 0.055 & C_{\text{linear}} > 0.0031308 \end{cases}$$解码(sRGB→线性):
$$C_{\text{linear}} = \begin{cases} C_{\text{sRGB}} / 12.92 & C_{\text{sRGB}} \leq 0.04045 \\ ((C_{\text{sRGB}} + 0.055) / 1.055)^{2.4} & C_{\text{sRGB}} > 0.04045 \end{cases}$$注意这个分段设计:在极暗区域($C \leq 0.0031308$),函数是线性的。这避免了幂函数在零点处的斜率无穷大问题,让数值计算更稳定。
sRGB的整体伽马值约等于2.2,但不是纯粹的$x^{2.2}$。这个细微差异在某些精确计算中会产生影响。
为什么sRGB能节省存储空间
理解伽马编码的另一个角度是位深的利用效率。
假设用8位(256个等级)存储亮度信息。如果按物理亮度线性编码:
- 等级128代表的物理亮度是255的50%
- 人眼对这个亮度的感知约为73%(因为$0.5^{0.45} \approx 0.73$)
- 这意味着暗部只用了128个等级,亮部却浪费了128个等级
人眼对暗部变化更敏感。在暗环境下,人眼可以分辨约1-2%的亮度差异;在亮环境下,需要5-10%的变化才能察觉。这意味着暗部需要更多的编码精度。
伽马编码恰好解决了这个问题:
- 等级128代表约22%的物理亮度
- 人眼感知到的亮度约为50%
- 编码等级均匀分布在感知空间中

上图展示了线性编码(左)和伽马编码(右)在8位精度下的差异。线性编码在暗部出现明显的条纹,因为256个等级在暗部分布过于稀疏。伽马编码则能呈现平滑的渐变。
实际上,要用线性编码达到同样的暗部精度,大约需要11-12位的数据深度——这就是伽马编码带来的"免费"压缩效果。
线性工作流:为什么游戏引擎要"多此一举"
现代游戏引擎(Unity、Unreal)都强调"线性渲染"的重要性。这意味着所有光照计算都在线性色彩空间进行,而不是直接在sRGB空间计算。
光照计算的物理正确性
考虑一个简单的光照场景:一个红色光源(强度0.5)照射到一个白色表面(反射率0.8)。物理上,结果是两者的乘积:
$$L_{\text{out}} = L_{\text{in}} \times R = 0.5 \times 0.8 = 0.4$$但如果数据是sRGB编码的,直接相乘会得到错误结果:
- sRGB值0.5对应的线性值约为0.218
- sRGB值0.8对应的线性值约为0.604
- 正确结果是$0.218 \times 0.604 \approx 0.132$
- 转回sRGB约为0.39
而直接在sRGB空间相乘得到0.4,差异显著。
渐变与混合的问题
更明显的问题出现在颜色混合上。假设要在一个线性渐变中混合纯红(1,0,0)和纯绿(0,1,0):
- 线性混合(正确):中点是黄色(0.5, 0.5, 0),物理上确实是两种光的叠加
- sRGB混合(错误):中点看起来偏暗,因为sRGB空间的插值没有考虑物理意义

图片来源: bottosson.github.io
上图展示了三种混合方式的差异:感知混合(上)、线性混合(中)、sRGB直接混合(下)。sRGB直接混合产生了不自然的暗色过渡。
抗锯齿的陷阱
抗锯齿通过对边缘像素进行颜色混合来平滑锯齿。如果这个混合在sRGB空间进行,边缘会出现异常的暗边:
- 假设边缘两侧是纯黑(0,0,0)和纯白(1,1,1)
- 理想的抗锯齿应该产生物理亮度50%的灰色
- 在sRGB空间直接混合得到(0.5, 0.5, 0.5)
- 显示时,这对应约22%的物理亮度,而不是50%
- 结果:边缘看起来"脏"且过暗

图片来源: blog.johnnovak.net
上图右侧展示了伽马错误导致的文字渲染过粗、边缘发暗的问题。
正确的线性工作流程
要在图形渲染中正确处理色彩空间,需要遵循以下流程:
1. 输入处理
纹理类型区分:
- 颜色纹理(diffuse、base color):通常是sRGB编码的,需要解码到线性空间
- 数据纹理(normal、roughness、metallic):通常是线性编码的,不需要转换
现代图形API(OpenGL、DirectX、Vulkan)都支持sRGB纹理格式,硬件会自动进行转换:
// OpenGL中创建sRGB纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
2. 中间计算
所有光照、混合、滤波操作都在线性空间进行。这是物理正确的计算方式。
3. 输出处理
最终输出到显示器前,将线性数据编码为sRGB。同样,现代API支持sRGB帧缓冲:
// 启用sRGB帧缓冲
glEnable(GL_FRAMEBUFFER_SRGB);
或手动转换:
vec3 gammaCorrect(vec3 linearColor) {
return pow(linearColor, vec3(1.0/2.2));
}
为什么LCD和OLED显示器仍然使用伽马
CRT显示器已经消失,但伽马校正依然存在于所有现代显示器中。原因有两点:
1. 向后兼容
几十年来积累的图像、视频、网页都是sRGB编码的。如果显示器改为线性响应,这些内容都会显示错误。
2. 感知优化的编码效率
即使在CRT消失后,伽马编码的价值依然存在:它让有限的位深得到更合理的分配。8位的sRGB图像在视觉上可以达到接近10位线性图像的质量。
LCD和OLED显示器通过内部查找表(LUT)来模拟伽马响应。显卡驱动也会参与这个过程,确保输出符合sRGB标准。
广色域显示器带来的新问题
近年来,支持P3色域的显示器越来越普及。P3色域比sRGB大约25%,能够显示更饱和的红色和绿色。
这带来了新的问题:
-
内容不匹配:大多数网页和应用假定显示器是sRGB的。如果显示器配置为P3,sRGB内容会被"拉伸"到P3空间,导致颜色过饱和。
-
色彩管理缺失:很多软件(尤其是老软件)不考虑色彩管理,直接把RGB值送到显示器。在广色域显示器上,这些值会被错误解释。
-
工作流不一致:设计师的显示器可能设置为sRGB,客户的显示器可能是P3,同一张图片看起来截然不同。
解决方案是色彩管理:图像文件嵌入ICC配置文件,操作系统和应用程序正确解释这些配置,并在显示时进行色彩空间转换。
HDR时代的传递函数
sRGB是为SDR(标准动态范围)设计的,最大亮度约80-100 nits。HDR显示器可以达到1000甚至4000 nits峰值亮度,需要新的传递函数。
PQ(Perceptual Quantizer)
定义在SMPTE ST 2084中,设计目标是覆盖0.0001到10,000 nits的亮度范围。PQ函数比伽马函数更复杂,设计时考虑了人眼在不同亮度下的对比敏感度。
$$L = \left(\frac{\max(V^{1/m_2} - c_1, 0)}{c_2 - c_3 V^{1/m_2}}\right)^{1/m_1}$$其中$m_1, m_2, c_1, c_2, c_3$是常数,$V$是信号值,$L$是亮度值。
HLG(Hybrid Log-Gamma)
由BBC和NHK联合开发,目标是兼容SDR显示。HLG在暗部使用传统的伽马曲线,在亮部使用对数曲线:
$$V = \begin{cases} a \ln(1 + bL) + c & L > 1 \\ rL & L \leq 1 \end{cases}$$HLG的优势是:即使在不支持HDR的显示器上,也能显示合理的图像,只是高光被压缩。
实践中的色彩管理建议
对于开发者
- 始终启用线性渲染:在游戏引擎中开启线性色彩空间选项
- 正确标记纹理:颜色纹理使用sRGB格式,数据纹理使用普通格式
- 最后一步进行伽马校正:确保中间计算都在线性空间
- 测试不同显示器:在sRGB和P3显示器上都测试你的应用
对于设计师
- 使用色彩管理软件:Photoshop、Lightroom等都有完整的色彩管理
- 嵌入ICC配置文件:导出图像时选择嵌入配置文件
- 校准显示器:使用校色仪创建显示器ICC配置文件
- 选择合适的色彩空间:网页内容用sRGB,打印用Adobe RGB或P3
对于内容消费者
- 理解显示器预设:很多显示器有不同的色彩模式(sRGB、P3、电影等),根据用途选择
- 保持系统色彩管理开启:Windows和macOS都有色彩管理,不要关闭
- 避免随意调整:显示器的亮度、对比度调整会影响色彩准确性
色彩空间的数学本质
从数学角度,色彩空间是一个从三维实数空间到感知颜色的映射。sRGB的定义包括:
- 线性变换矩阵:从CIE XYZ空间转换到线性RGB空间
- 非线性传递函数:从线性RGB编码为非线性RGB
- 原色和白点定义:定义了矩阵的具体数值
完整的转换链:
$$\text{XYZ} \xrightarrow{\text{矩阵}} \text{Linear RGB} \xrightarrow{\text{传递函数}} \text{sRGB}$$逆转换:
$$\text{sRGB} \xrightarrow{\text{逆传递函数}} \text{Linear RGB} \xrightarrow{\text{逆矩阵}} \text{XYZ}$$理解这个链条,就能理解为什么色彩空间转换会产生误差,以及为什么"两个错误有时能变成一个正确"。
参考资料
- Stokes, M., Anderson, M., Chandrasekar, S., & Motta, R. (1996). A Standard Default Color Space for the Internet - sRGB. W3C.
- IEC 61966-2-1:1999. Multimedia systems and equipment - Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB.
- Novak, J. (2016). What every coder should know about gamma. https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
- Gritz, L., & d’Eon, E. (2007). The Importance of Being Linear. GPU Gems 3, Chapter 24. NVIDIA.
- Poynton, C. (1998). Rehabilitation of Gamma. SPIE Conference on Human Vision and Electronic Imaging III.
- Wikipedia. sRGB. https://en.wikipedia.org/wiki/SRGB
- Wikipedia. Gamma correction. https://en.wikipedia.org/wiki/Gamma_correction
- Cambridge in Colour. Understanding Gamma Correction. https://www.cambridgeincolour.com/tutorials/gamma-correction.htm
- Ottosson, B. (2020). How software gets color wrong. https://bottosson.github.io/posts/colorwrong/
- LearnOpenGL. Gamma Correction. https://learnopengl.com/Advanced-Lighting/Gamma-Correction
- Nine Degrees Below. History of the Very Odd sRGB Color Space. https://ninedegreesbelow.com/photography/srgb-history.html
- International Color Consortium. Display calibration. https://www.color.org/displaycalibration.xalter
- EIZO. Understanding Monitor Gamma & Gamma Correction. https://www.eizo.com/library/basics/lcd_display_gamma/
- ITU-R BT.2100. Image parameter values for high dynamic range television for use in production and international programme exchange.
- SMPTE ST 2084. High Dynamic Range Electro-Optical Transfer Function of Mastering Reference Displays.
- Wikipedia. Weber-Fechner law. https://en.wikipedia.org/wiki/Weber%E2%80%93Fechner_law
- Wikipedia. Stevens’s power law. https://en.wikipedia.org/wiki/Stevens%27s_power_law
- Wikipedia. CIE 1931 color space. https://en.wikipedia.org/wiki/CIE_1931_color_space
- Unity Documentation. Linear or gamma workflow. https://docs.unity3d.com/Manual/LinearRendering-LinearOrGammaWorkflow.html
- NVIDIA Developer. The Importance of Being Linear. https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-24-importance-being-linear
- Medium. Why DCI-P3 and not Adobe RGB? https://news.ycombinator.com/item?id=11546949
- W3C. PNG Specification: Gamma Tutorial. https://www.w3.org/TR/PNG-GammaAppendix.html
- Wikimedia Commons. CIE1931xy gamut comparison. https://commons.wikimedia.org/wiki/File:CIE1931xy_gamut_comparison_of_sRGB_P3_Rec2020.svg