/ Ch18 颜色

第18章:颜色 — 零基础讲义

讲义说明
本讲义基于 Steve Marschner & Peter Shirley 所著《虎书5》第5版第18章(p.503-524)。
本章是图形学的"色彩"——它告诉你:颜色不是"红绿蓝"这么简单,背后有一整套色彩科学(Color Science)
学完本章,你将真正理解色度学、色域、白点、Gamma 校正、色温等 PBR 渲染的基石。
本版插画采用 Guizang 材质插画风格重新绘制。


18.0 为什么需要学"颜色"?

你可能觉得"颜色就是 RGB"——你已经用了很多年 RGB 了。但 为什么 255, 0, 0 是红色?为什么 255, 255, 255 是白色?为什么显示器上的颜色和打印出来的不一样?

这些问题表面上很简单,但它们通往一门严谨的科学——色彩科学。PBR 渲染、HDR、色彩管理——所有这些现代图形学技术,离开了对颜色的深刻理解就会出 bug。

这一章的目标:

  1. 理解颜色的物理学——光谱 SPD
  2. 掌握色度学(Colorimetry):三刺激值、配色函数
  3. 理解 CIE XYZ 颜色空间及其意义
  4. 掌握色品图(Chromaticity Diagram)——马蹄形
  5. 理解色域(Gamut) 与色域映射
  6. 掌握白点色温
  7. 理解色适应(Chromatic Adaptation)
  8. 掌握Gamma 校正

你有过这些困惑吗?

  • 为什么 PS 里的"色相/饱和度"调整在 RGB 图像上效果那么奇怪?
  • 为什么你的游戏截图在手机上看太暗、在显示器上看太亮?
  • 为什么印刷出来的照片和屏幕上看到的不一样?
  • 为什么同一个 3D 场景在 Unity 和 Unreal 中颜色不同?

所有这些问题的答案都在本章中。

🤔 想一想:你在显示器上看到的一张红色图片 = 显示器发出红色光。但一张红色印刷品 = 纸反射红色光、吸收其他光。这两个"红色"的物理过程完全不同,它们的 RGB 值应该一样吗?
这就是色彩管理的核心问题——不同设备用不同的方式产生颜色,我们要确保它们看起来一致。

18.1 什么是颜色?

18.1.1 颜色的来源

颜色 = 人类视觉系统对光谱的感知

光是一种电磁波。不同的波长对应不同的颜色:

波长 (nm)颜色
380~450紫色 / 蓝色
450~495蓝色
495~570绿色
570~590黄色
590~620橙色
620~780红色

但自然光几乎从不会是单一波长——它是许多波长的混合。描述这种混合的工具叫 SPD(Spectral Power Distribution,光谱功率分布)

struct SPD {
    float power[81];   // 380nm ~ 780nm, 每5nm一个采样
    float operator()(float lambda) {
        // 插值采样
    }
};

18.1.2 颜色 = 光 × 物体 × 眼睛

颜色的产生有三个要素:

  1. 光源:SPD(λ)(如 D65 日光)——决定了有哪些波长的光可用
  2. 物体:反射率 R(λ)(如绿草、红苹果)——决定了哪些波长的光被反射
  3. 眼睛:LMS 锥体响应函数——决定了你怎么感知这些光
最终看到的光 = 光源 SPD(λ) × 物体反射率 R(λ) × 眼睛响应
日常类比:颜色不是物体的固有属性,而是眼睛共同作用的结果。就像"辣"不是辣椒的固有属性——是你的味觉神经对辣椒素的反应。没有你的舌头,辣椒就不"辣"。没有你的眼睛,物体就没有"颜色"。

18.2 人类视觉系统

18.2.1 为什么人眼只用 3 种传感器?

我们的视网膜上有两种感光细胞:

  • 锥体(Cones):负责颜色视觉,有 3 种类型
  • 杆体(Rods):负责暗视觉,只有一种类型(无色)
细胞类型峰值波长敏感性
L cones长波~565 nm
M cones中波~535 nm绿
S cones短波~440 nm
Rods暗视觉~500 nm无色
三刺激值与配色函数——LMS锥体响应
图18-1:LMS 锥体响应曲线——人眼的三种感光细胞对不同波长的敏感度,红色(L)、绿色(M)、蓝色(S)

锥体响应函数——每种锥体对入射光谱按波长积分:

L = ∫ S(λ) · L_bar(λ) dλ
M = ∫ S(λ) · M_bar(λ) dλ
S = ∫ S(λ) · S_bar(λ) dλ
符号含义白话翻译
S(λ)入射光的 SPD(光谱功率分布)到达眼睛的光在每个波长上的能量
L_bar(λ)L 锥体对不同波长的敏感度L 锥体"喜欢"哪些波长
LL 锥体的最终响应值对所有波长的贡献求和
∫ dλ对波长积分把每个波长的贡献加起来
核心洞察:人眼只有 3 种色彩传感器——这就是为什么3 通道 RGB 就够了。这是整个色彩科学的起点。

18.2.2 锥体响应的数学拆解

每种锥体的响应是一个积分——把光谱上每个波长的能量乘以该锥体对该波长的敏感度,然后加起来:

L = ∫380780 S(λ) · (λ) dλ
符号含义白话翻译
S(λ)入射光的光谱功率分布每个波长上的"能量"有多少
L̄(λ)L锥体的光谱敏感度函数L锥体对不同波长的"喜好程度"
S(λ)·L̄(λ)逐波长乘积波长λ处的光 × 该波长的敏感度
∫dλ对所有波长积分把所有波长的贡献加起来
LL锥体的总响应一个数,描述"有多红"

数值例子:假设光谱 S(λ) 只在 565nm 处有能量 = 1,其他地方为 0:

L = S(565) × L̄(565) × Δλ = 1 × 1.0 × 5 ≈ 5.0  (大响应,因为 565nm 是 L 锥体峰值)
M = S(565) × M̄(565) × Δλ = 1 × 0.5 × 5 ≈ 2.5  (中等响应)
S = S(565) × S̄(565) × Δλ = 1 × 0.0 × 5 ≈ 0.0  (无响应)

18.2.3 同色异谱(Metamerism)

同色异谱 = "两个不同 SPD 看起来完全一样"

因为人眼只通过 3 个通道(LMS)来感知颜色,所以完全不同的光谱分布可以产生完全相同的 LMS 三刺激值——从而看起来是"同一个颜色"。

// 两种 SPD 产生完全相同的颜色
SPD spd1, spd2;
float L1 = integrate(spd1, L_bar);  // spd1 的 L 响应
float L2 = integrate(spd2, L_bar);  // spd2 的 L 响应
// L1 == L2, M1 == M2, S1 == S2 -> 颜色完全一致
应用显示器/打印机利用这个原理——它们发出的光谱和自然物体反射的光谱完全不同,但只要 LMS 响应相同,人眼就认为颜色一致。
🤔 想一想:为什么你在超市的荧光灯下买衣服,拿回家在阳光下看颜色不一样了?
因为荧光灯和太阳的 SPD 完全不同!虽然它们都是"白光",但光谱成分不一样,导致物体反射光进入你眼睛的 LMS 响应也不同。这就是同色异谱的局限性——同一个物体在两种光源下可能是不同颜色。所以高端商品看色要用标准光源(如 D65)。

18.3 色度学(Colorimetry)

18.3.1 为什么需要色度学?

人眼有 3 种锥体,所以理论上用 3 个数值就能描述任何颜色。但问题是:这 3 个数值跟具体的锥体响应有关——不同人的锥体敏感度不同怎么办?色度学的目标就是定义标准化的、与人无关的颜色描述体系。

18.3.1 从 LMS 到 CIE 的动机

LMS 是生理学空间——它描述了你眼睛里的 3 种锥体对光的响应。但不同人的锥体敏感度有细微差异。为了工程上的可重复性,CIE 定义了一个"标准观察者"——用一组固定的配色函数代替了个体差异。

换句话说:LMS = "你的眼睛",CIE = "标准化的眼睛"

18.3.2 CIE RGB 配色函数

CIE 1931:用 3 种固定原色"匹配"任意颜色

原色波长颜色
R700.0 nm红色
G546.1 nm绿色
B435.8 nm蓝色

配色函数:对于任意光谱 S(λ),算出匹配这个颜色需要的 R、G、B 量:

R = ∫ S(λ) · r_bar(λ) dλ
G = ∫ S(λ) · g_bar(λ) dλ
B = ∫ S(λ) · b_bar(λ) dλ

问题:某些光谱需要负值 R才能匹配——很不直观。负的红色意味着你需要在被试者那一侧加红光,而不是在测试侧。这在实际使用中很不方便。

18.3.3 CIE XYZ 标准观察者

CIE XYZ:把 CIE RGB 变换到"全正值"空间

CIE 在 1931 年做了一个线性变换,把 RGB 变换到 XYZ 空间,使得所有值都是正数:

[X] [ 0.49000 0.31000 0.20000 ] [R]
[Y] = [ 0.17697 0.81240 0.01063 ] [G]
[Z] [ 0.00000 0.01000 0.99000 ] [B]
符号含义白话翻译
X红色响应(正比于红-绿感知)近似红色的量
Y亮度(Luminance)可见光的"强度"——跟人眼感受到的明亮程度对应
Z蓝色响应近似蓝色的量
关键属性
  • Y = 亮度(Luminance)——唯一包含亮度信息的通道
  • X, Z = 色度(没有亮度信息)
  • 所有值都是正数
  • XYZ 是后续所有色彩空间的基础

18.3.4 色品图(Chromaticity Diagram)

色品图 = "去掉亮度的颜色 2D 表示"

把 XYZ 归一化,去掉亮度信息,就得到了色品坐标:

x = X / (X + Y + Z)
y = Y / (X + Y + Z)

因为 x + y + z = 1,所以只需要 x 和 y 两个值就能完整描述色度(去掉了亮度)。

CIE马蹄形色品图
图18-2:CIE 1931 色品图——马蹄形边界是光谱轨迹(纯光谱色),三角形是 sRGB 显示器的色域
概念含义图形解释
马蹄形边界光谱轨迹(Spectrum Locus)弧线 = 纯光谱色(单波长),马蹄内部 = 混合色
底部直线纯紫线(Line of Purples)连接光谱两端的直线,紫色不在光谱中(不存在"紫色的波长")
内部三角形设备色域显示器能显示的颜色是马蹄内部的一个三角形
核心洞察马蹄形色品图 = "人类视觉能看到的所有颜色"——任何显示器的色域只是其中的一部分。

数值计算示例:SPD → XYZ → xy

假设我们有一个简单的 SPD——只在 500nm 处有能量 1.0,其余为 0:

// SPD:单一波长 500nm(绿色光)
// 查 CIE 1931 配色函数表:
x̄(500) = 0.0049,  ȳ(500) = 0.3230,  z̄(500) = 0.2720

// 三刺激值
X = 1.0 × 0.0049 = 0.0049
Y = 1.0 × 0.3230 = 0.3230  (即亮度)
Z = 1.0 × 0.2720 = 0.2720

// 色品坐标
x = 0.0049 / (0.0049 + 0.3230 + 0.2720) = 0.0082
y = 0.3230 / (0.0049 + 0.3230 + 0.2720) = 0.5385
// → 色品坐标 (0.008, 0.538) ≈ 纯绿色

18.3.5 色域(Gamut)

色域 = "设备能显示的所有颜色"

显示器色域在色品图上是一个三角形——三个顶点对应 R/G/B 原色。

sRGB色域对比——sRGB vs Adobe RGB vs DCI-P3
图18-3:不同色域标准对比——sRGB(最小)、Adobe RGB(中等)、DCI-P3(较大),在色品图上的覆盖范围
标准色域大小应用
sRGB约 35% CIE网页、显示器、手机屏幕
Adobe RGB约 50% CIE印刷、专业摄影
DCI-P3约 45% CIE电影、HDR 显示(iPhone、iPad)
Rec.2020约 75% CIEUHDTV 标准(4K/8K)
ACES大于 100% CIE电影级制作(覆盖所有人眼可见色)

18.3.6 UCS 均匀色品坐标

CIE xy 色品图虽然经典,但有一个严重问题:视觉不均匀。在色品图的不同区域,相同的距离(Δx, Δy)对应的人眼感知差异完全不同。MacAdam 在 1942 年发现了这一点——他在色品图的不同位置画出了一系列"刚好能察觉颜色差异"的椭圆(MacAdam Ellipses),这些椭圆的大小和方向随位置剧烈变化。

解决方案:CIE 1976 UCS(Uniform Chromaticity Scale)图:

u' = 4X / (X + 15Y + 3Z)
v' = 9Y / (X + 15Y + 3Z)
属性CIE xyCIE u'v'
视觉均匀性差(MacAdam 椭圆大小差距 > 10 倍)好(椭圆大小差距 < 3 倍)
用途经典展示、色度学教学实际色域计算、色差测量
是否是线性变换是(从 XYZ 直接投影)否(非线性投影)

18.3.7 白点(White Point)

白点 = "显示器/光源的'白色'坐标"

你可能会想"白色就是白色,还有什么不同的白色?"——但不同的光源有不同的"白色":白炽灯偏黄,日光偏白,阴天偏蓝。白点就是这些白色在色品图上的坐标。

白点色品坐标色温用途
D65(0.3127, 0.3290)6500KsRGB / HDTV 标准
D50(0.3457, 0.3585)5000K印刷标准
D93(0.2830, 0.2970)9300K早期 CRT 显示器

色温:低 K = 暖(偏红),高 K = 冷(偏蓝)。所以 5500K 的闪光灯比 3200K 的白炽灯更"白"。

色温与黑体辐射的关系:

温度颜色例子
~1800 K橙红蜡烛火焰
~2800 K暖黄家用白炽灯
~3200 K暖白摄影卤素灯
~5500 K中白(日光)正午阳光
~6500 K冷白(D65)阴天、sRGB 标准
~9300 K偏蓝早期 CRT 显示器

注意:颜色中的"暖色"(红/橙)对应低色温,"冷色"(蓝)对应高色温——这和日常直觉相反,但在物理学和色彩学中,温度越高颜色越偏蓝。

18.3.10 色域映射

当你把一个高色域的图片显示在低色域显示器上时,超出色域的颜色需要被"映射"到显示器能显示的范围内:

映射策略做法效果
裁剪(Clip)超出色域的颜色直接截断到边界颜色鲜艳但损失细节(色块)
压缩(Compress)整体向色域中心压缩保留细节但整体饱和度降低
感知映射(Perceptual)尽量保持感知差异效果好但计算复杂

18.3.8 色差公式 ΔE

在实际应用中(如质检、印刷),经常需要回答一个问题:"这两个颜色差多少?" 直接算 RGB 距离是不行的——因为 RGB 空间感知不均匀。解决方案是用 Lab 空间的欧几里得距离:

ΔEab* = √[(ΔL*)² + (Δa*)² + (Δb*)²]
ΔE 值人眼感知
< 1几乎看不出差别
1~3仔细对比才能看出
3~6可以明显看出差别
> 6完全不同的颜色

更现代的 CIE DE2000(ΔE00)对蓝色区域的误差做了进一步修正——这是当前色彩科学的标准色差公式。


18.4 Gamma 校正

18.4.1 为什么需要 Gamma?

CRT 显示器亮度 ≠ 输入电压的线性关系

老式 CRT 显示器有一个物理特性:输入电压和显示亮度之间不是线性关系,而是幂律关系:

display_brightness = (input_voltage)γ

典型 CRT 的 gamma = 2.2。所以如果你存了 0.5 这个值,显示器实际输出的亮度是 0.52.2 ≈ 0.22——太暗!

Gamma校正曲线——线性 vs sRGB编码/解码
图18-4:Gamma 校正曲线——蓝色直线是线性响应,红色曲线是 sRGB gamma 编码(弯曲向上补偿显示器的 gamma 2.2)

18.4.2 Gamma 校正的原理

解决方法是:存储时预先做逆变换,显示时 CRT 的自然 gamma 会自动还原:

stored_value = (original_value)1/γ
最终亮度 = (original1/2.2)2.2 = original

完整的 Gamma 校正流程(用数字说话):

步骤操作说明
① 物理亮度你想要的亮度0.5物理上 50% 的亮度
② 编码pow(0.5, 1/2.2)0.73做 gamma 编码,存入图片文件
③ 存储 / 传输存为 0.730.73PNG/JPG 中存的是 0.73
④ CRT 显示pow(0.73, 2.2)0.5CRT 的自然 gamma 还原了亮度
⑤ 最终亮度回到 0.5 ✓0.5完美还原!

如果忘记 gamma 校正:

步骤操作说明
① 物理亮度你想要的亮度0.5
② 直接存储存为 0.50.5没有做 gamma 编码
③ CRT 显示pow(0.5, 2.2)0.22显示器自动做 gamma
④ 最终亮度实际只有 0.220.22太暗了!!

18.4.3 sRGB Gamma 的完整公式

// sRGB 到线性空间(解码)——把 sRGB 图片还原为真实亮度
float sRGB_to_linear(float c) {
    if (c <= 0.04045) return c / 12.92;
    else return pow((c + 0.055) / 1.055, 2.4);
}

// 线性到 sRGB(编码)——把计算结果存为 sRGB 图片
float linear_to_sRGB(float c) {
    if (c <= 0.0031308) return c * 12.92;
    else return 1.055 * pow(c, 1/2.4) - 0.055;
}
简化版(约等于):sRGB ≈ pow(linear, 1/2.2)
线性 ≈ pow(sRGB, 2.2)
核心洞察永远是线性空间做光照运算,最后再 sRGB 输出——这是 PBR 的基础原则。直接在 sRGB 空间做光照计算结果是错的。
🤔 想一想:为什么在 sRGB 空间做光照计算是错误的?
光照计算(如漫反射 = n·l)假设光强度以线性方式叠加——两倍的光就是两倍的亮度。但 sRGB 是经过 gamma 编码的非线性空间,数值 0.5 并不代表"一半的亮度"(实际上是 0.5^2.2 ≈ 0.22)。所以在 sRGB 空间做光照,你的计算结果会完全偏离物理真实。总是先解码到线性空间,算完了再编码回 sRGB。

18.4.4 Gamma 与 sRGB 的实际影响

在现代图形学中,gamma 校正的影响无处不在:

  • 纹理贴图:所有照片和手绘纹理都是 sRGB 编码的。采样后必须解码到线性空间再做光照。
  • 光照计算:所有物理光照模型(Lambert、Phong、PBR)都需要在线性空间计算。
  • 抗锯齿:在 sRGB 空间做抗锯齿混合会导致边缘变暗(因为 0.5 在 sRGB 中不等于一半亮度)。
  • HDR 渲染:色调映射后的结果需要编码到 sRGB 才能正确显示。

18.4.5 sRGB 与 Gamma 的常见误解

误解正确理解
Gamma 只是 CRT 的历史遗留问题现代 LCD/LED 显示器为了兼容 sRGB 标准,仍然模拟了 gamma 2.2 响应
所有图片格式都是线性的PNG、JPG、GIF 等绝大多数图片格式都使用 sRGB gamma 编码
线性空间和 sRGB 空间可以互相替换绝对不行!线性空间的 0.5 ≠ sRGB 的 0.5

18.5 色适应(Chromatic Adaptation)

18.5.1 为什么需要色适应?

你的大脑非常神奇:在暖色灯光下看一张白纸,它看起来还是白的。但实际上白纸反射的光偏黄了——只是你的大脑"自动做了白平衡"。这个现象叫色适应。

在图形学中,如果你渲染一个场景使用了 5000K 的光源(暖色),但显示在 6500K 的显示器上(冷色),你需要把颜色从 5000K 的感知"适应"到 6500K 的显示——这就是 von Kries 色适应模型做的事。

18.5.2 von Kries 系数律

色适应von Kries——白点对齐变换
图18-5:von Kries 色适应——将颜色从源白点变换到目标白点,在 LMS 空间中对每个通道独立缩放

von Kries 模型假设色适应就是在 LMS 空间中独立缩放每个通道的响应:

L' = α · L
M' = β · M
S' = γ · S
符号含义
L, M, S源颜色在 LMS 空间的值
α, β, γ每个通道的缩放因子(由源白点和目标白点决定)
L', M', S'适应后的颜色

计算 α, β, γ 的步骤(以 D65 → D50 为例):

// 源白点 D65 (6500K) 在 XYZ 空间
XYZ_D65 = (0.9505, 1.0000, 1.0890)

// 目标白点 D50 (5000K) 在 XYZ 空间
XYZ_D50 = (0.9642, 1.0000, 0.8249)

// 用 Bradford 变换矩阵 M 转换到 LMS 空间
LMS_D65 = M × XYZ_D65    // → (L_d65, M_d65, S_d65)
LMS_D50 = M × XYZ_D50    // → (L_d50, M_d50, S_d50)

// 缩放因子 = 目标/源
α = L_d50 / L_d65        // LMS 各通道的比例因子
β = M_d50 / M_d65
γ = S_d50 / S_d65

完整的变换流程:

XYZ_adapted = M-1 · diag(α, β, γ) · M · XYZ_source

步骤拆解:

  1. 用 M 矩阵把 XYZ 变换到 LMS 空间
  2. 在 LMS 空间用 α, β, γ 缩放(白点对齐)
  3. 用 M-1 变换回 XYZ 空间
游戏开发实战:Unity HDRP / Unreal 都用色适应来做不同光照条件下的白平衡。

18.5.3 Bradford 与 CAT02 变换矩阵

从 XYZ 到 LMS 的变换有很多种选择,不同的矩阵适合不同的用途:

变换用途特点
Bradford最常用的色适应变换对蓝色通道的适应更准确,广泛应用于 ICC 色彩管理
CAT02CIE 推荐的色适应变换在 CIECAM02 色貌模型中使用
von Kries 原始经典理论模型简单但准确度不如后来者
// Bradford 变换矩阵(XYZ → LMS)
mat3x3 M_Bradford = {
    0.8951, 0.2664, -0.1614,
   -0.7502, 1.7135,  0.0367,
    0.0389, -0.0685,  1.0296
};

// 使用 Bradford 变换做色适应:
// 1. M × XYZ_source → LMS_source
// 2. 按比例缩放 LMS 各通道
// 3. M^(-1) × LMS_adapted → XYZ_adapted
🤔 想一想:为什么从 XYZ 到 LMS 的变换不直接用 X = L, Y = M, Z = S
因为 CIE XYZ 是人为构造的虚拟原色,而 LMS 是生物学上测量的锥体响应。从虚拟到生物需要线性变换来"对齐"。Bradford 和 CAT02 都是基于实际实验数据拟合出来的,它们能把 XYZ 空间旋转到更接近人眼感知的方向。

18.6 颜色模型

18.6.1 RGB 模型

简单直接——每个通道独立,对应显示器硬件。

优势劣势
硬件原生(显示器直接使用)和亮度/饱和度不直观
Shader 友好(直接操作)调整"更亮"需要同时改 R、G、B

RGB 相加混合 vs 颜料相减混合:

混合方式原理例子
加法混色(显示器)RGB 光叠加,越加越亮红+绿=黄,红+绿+蓝=白
减法混色(颜料)吸收光,越混越暗黄+蓝=绿,青+品红+黄=黑

18.6.2 HSV / HSL 模型

更直观——艺术家友好,分离出色相、饱和度、亮度/明度。

分量HSVHSL
H(色相)0°-360°0°-360°
S(饱和度)0-10-1
V / L亮度(Value)明度(Lightness)

问题:和感知无关——饱和度增加 0.1 在黄色和蓝色上的视觉变化完全不同。数学性质也不好(非线性)。

18.6.3 CIE Lab / Lch 模型

感知均匀——Lab 空间中两个颜色的"距离"与人的感知差异成正比。

分量含义
L亮度(Lightness)
a红-绿轴(+红,-绿)
b黄-蓝轴(+黄,-蓝)
C = √(a²+b²)色度(Chroma)
h = atan2(b, a)色相角(Hue angle)

Lab 的数学定义(从 XYZ 变换):

// 辅助函数 f(t)
float f(float t) {
    if (t > (6/29)³)
        return pow(t, 1/3);
    else
        return t / (3 * (6/29)²) + 4/29;
}

// XYZ_n = 白点的 XYZ 坐标
L = 116 * f(Y / Y_n) - 16;  // L 范围 [0, 100]
a = 500 * (f(X / X_n) - f(Y / Y_n));  // 红-绿
b = 200 * (f(Y / Y_n) - f(Z / Z_n));  // 黄-蓝
条件含义
a > 0偏红色
a < 0偏绿色
b > 0偏黄色
b < 0偏蓝色
a = 0 且 b = 0灰色(无色)
应用:图像处理(色彩增强、色调映射)——在 Lab 空间操作最自然,因为它的坐标轴与人的色彩感知对齐。例如"增加对比度"在 Lab 空间中就是拉伸 L 通道的范围,"增强饱和度"就是增大 a、b 的值。
🤔 想一想:为什么 Lab 空间比 RGB 空间更适合做图像编辑?
因为 RGB 空间中,R、G、B 三个通道高度耦合——调整"亮度"需要同时改变三个通道的值,而且很容易产生色偏。Lab 空间把亮度(L)和颜色信息(a、b)完全分离。调整 L 通道只改变亮度,不影响色调;调整 a、b 通道只改变颜色,不影响亮度。这就是 Photoshop 中"明度"和"色相/饱和度"分开调整的原理。

18.7 颜色在图形学中的应用

18.7.1 PBR 中的颜色管理

PBR = 物理正确的颜色管理

Input    → Linearize → PBR Shading → Display Adapt → sRGB Output
(纹理)     (Gamma-)     (物理光)      (色适应)         (Gamma+)
           (校正)       照计算)                       (校正)

这条管线是现代渲染引擎的标配:

  1. Linearize:sRGB 纹理 → 线性空间(解码 gamma)
  2. PBR Shading:在线性空间做物理光照计算
  3. Display Adapt:色适应(从场景白点变换到显示器白点)
  4. sRGB Output:线性结果 → sRGB 编码(编码 gamma)

完整的颜色管线示例(从纹理到屏幕):

// ---- 像素着色器中的颜色管理 ----
// 输入:sRGB 纹理(通道值 0-255,存储时已 gamma 编码)

// 第1步:采样纹理得到 sRGB 颜色
vec4 texColor_sRGB = texture(sRGBTexture, uv);

// 第2步:解码到线性空间(sRGB → 线性)
vec3 texColor_linear = sRGB_to_linear(texColor_sRGB.rgb);

// 第3步:在线性空间做光照计算(所有物理计算都在这里)
vec3 diffuse = kd * lightIntensity * max(0.0, dot(n, l));
vec3 specular = ks * lightIntensity * pow(max(0.0, dot(n, h), 32.0));

// 第4步:合成最终线性颜色
vec3 finalColor_linear = (diffuse + specular) * texColor_linear;

// 第5步:色调映射(HDR → LDR)
vec3 mapped = tonemap(finalColor_linear);

// 第6步:编码回 sRGB 用于显示
vec3 outputColor = linear_to_sRGB(mapped);
fragmentColor = vec4(outputColor, 1.0);
核心原则:整个着色管线都在线性空间运算,只在最后输出时做 sRGB 编码。如果你在 sRGB 空间做光照,物理上是错误的。

18.7.2 色温与白平衡

日光(D65 = 6500K)的色品坐标:

x = 0.3127, y = 0.3290

白平衡调整:如果光源改变,对 RGB 通道分别缩放:

R' = R / R_white
G' = G / G_white
B' = B / B_white

其中 R_white、G_white、B_white 是白点在 RGB 空间的坐标。

18.7.3 ACES 颜色空间

ACES(Academy Color Encoding System)——电影行业的色彩管理标准:

  • 色域超过 CIE XYZ(覆盖所有人眼可见颜色)
  • 包含完整的色彩管理流程(输入→处理→输出)
  • PBR 渲染中的"黄金标准"
🤔 想一想:为什么 ACES 的色域比 CIE XYZ 还大?这怎么可能?
CIE XYZ 的色域并不是"最大"的——它的原色是虚拟的。ACES 使用 AP0 原色,其三角形在色品图上比 CIE 马蹄形还要大,这意味着有些 AP0 颜色其实不在可见光谱内。这是故意设计的——这样任何可见颜色都能用 ACES 表示,不会出现"超出色域"的情况。简单说:ACES 造了一个比可见光谱更大的容器,确保容得下所有颜色。

18.8 全章总结

核心洞察:颜色不是"红绿蓝"——是光谱 × 眼睛 × 显示器的三方博弈

概念一句话关键公式
3 种锥体人眼只用 3 个通道感光,所以 3 通道够用L = ∫S(λ)·L̄(λ)dλ
同色异谱不同光谱、相同颜色LMS 积分结果相同
CIE XYZ标准色彩空间,Y 等于亮度X=∫S·x̄dλ, Y=∫S·ȳdλ
色品图去掉亮度的 2D 颜色图x=X/(X+Y+Z), y=Y/(X+Y+Z)
色域设备能显示的颜色范围色品图上的三角形
Gamma存储非线性,计算线性V_out = V_in^γ
色适应大脑自动白平衡L'=α·L, M'=β·M, S'=γ·S
ACES电影级色彩管理AP0 原色矩阵

关键公式速查

LMS 响应:    L=∫S(λ)·L̄(λ)dλ, M=∫S(λ)·M̄(λ)dλ, S=∫S(λ)·S̄(λ)dλ
CIE XYZ:    X=∫S(λ)·x̄(λ)dλ, Y=∫S(λ)·ȳ(λ)dλ, Z=∫S(λ)·z̄(λ)dλ
色品坐标:   x=X/(X+Y+Z), y=Y/(X+Y+Z)
UCS:        u'=4X/(X+15Y+3Z), v'=9Y/(X+15Y+3Z)
Gamma:      V_out = V_in^γ
sRGB→线性:  c_linear = sRGB_to_linear(c_sRGB)
线性→sRGB:  c_sRGB = linear_to_sRGB(c_linear)
Von Kries:  L'=α·L, M'=β·M, S'=γ·S
Lab:        L=116·f(Y/Yₙ)-16, a=500·(f(X/Xₙ)-f(Y/Yₙ)), b=200·(f(Y/Yₙ)-f(Z/Zₙ))

常见误区

误区正确理解
"颜色是物体的固有属性"颜色 = 光 × 物体反射率 × 眼睛感知——缺一不可
"RGB 值直接代表亮度"sRGB 是 gamma 编码的,0.5 ≠ 一半亮度
"两个颜色看起来一样 = SPD 一样"同色异谱——完全不同的 SPD 可以看起来完全一样
"白色就是白色"不同光源的白点不同(D65=6500K 偏蓝, D50=5000K 偏黄)
"显示器能显示所有颜色"显示器色域仅覆盖 CIE 色品图的 35%~80%

一句话总结:颜色是物理(光谱)× 生物(锥体)× 工程(显示)的交叉学科。理解色彩科学 = 理解 PBR 渲染中为什么每一步颜色处理都至关重要。

下一步:第 19 章《视觉感知》——理解人类视觉如何限制图形学的终极质量。这是从"科学"到"艺术"的桥梁。

目录 X
章节 X