2.2 颜色模型:RGB、HSV 与伽马校正


2.2 颜色模型:RGB、HSV 与伽马校正

本节摘要:人眼有三种视锥细胞,所以三个数字就足以表示任何颜色感受——这是 RGB 的生理学根基。本节讲 RGB 与 HSV 的互相转换、亮度计算的加权公式,以及最容易踩的伽马陷阱:在非线性空间做线性插值与光照混合,颜色会发灰发暗。承接 2.1 的帧缓冲格式,这一节决定格子里三个数的语义。

读完这节你该会什么

阅读完本节,你应当能够:

  1. 解释三色理论,说出 RGB 为什么是三个分量
  2. 手写 RGB→HSV 转换,并说明 HSV 在拾色器与图像算法里的优势
  3. 用加权公式算亮度,解释三个权重为何不等
  4. 诊断"插值发灰、光照偏暗"的伽马问题,给出线性空间计算的正确流程

为什么三个数字就够了

可见光是连续光谱,波长大约从 380 纳米到 780 纳米,理论上需要无穷多个数才能完整描述。但人眼的视锥细胞只有三种,分别对长、中、短波长敏感,任何光谱进入眼睛都被压缩成三个刺激值。这意味着一个惊人的事实:只要调配三种色光刺激三种视锥细胞到同样的比例,人眼无法区分它与原光谱——三原色混色不是工程妥协,是生理结构决定的捷径。

RGB 立方体沿三个轴张开,原点是黑,对角顶点是白;HSV 则把同一个颜色空间拧成圆柱:色相(Hue)绕圈,饱和度(Saturation)是离中心轴的距离,明度(Value)是高度。两套坐标描述同一世界,各有分工:

模型 擅长的场景 不擅长的场景
RGB 硬件输出、混合、帧缓冲存储 直观调色、按亮度过滤
HSV 拾色器、色调分离、按颜色选区 精确物理光照计算
def rgb_to_hsv(r, g, b): # 输入输出均为 0~1 浮点 mx, mn = max(r,g,b), min(r,g,b) d = mx - mn v = mx s = 0 if mx == 0 else d / mx if d == 0: h = 0 # 灰色,色相无定义 elif mx == r: h = ((g - b) / d) % 6 # 红色区间 elif mx == g: h = (b - r) / d + 2 # 绿色区间 else: h = (r - g) / d + 4 # 蓝色区间 return (h * 60, s, v) print(rgb_to_hsv(1.0, 0.5, 0.0)) # 输出: (30.0, 1.0, 1.0) —— 纯橙,色相30度,全饱和,最亮

转换的几何直觉:把 RGB 立方体沿白到黑的对角线竖起来往下看,投影是个六边形色轮,HSV 就是这个视角下的圆柱坐标。做"把画面里所有红色调暗一点"这类操作时,HSV 下的处理远比 RGB 直接——这也解释了为什么图像软件的拾色器几乎清一色用 HSV 或 HSL。

亮度不是三个分量的平均

按物理能量算,等量 RGB 混出的是偏绿的灰白;人眼对绿色最敏感,对蓝色最迟钝。亮度(明度感知)的加权公式:

Y = 0.2126·R + 0.7152·G + 0.0722·B (线性空间,Rec.709 标准)
def luma(r, g, b): # 感知亮度:绿色占七成权重 return 0.2126*r + 0.7152*g + 0.0722*b print(luma(0, 1, 0), luma(0, 0, 1)) # 输出: 0.7152 0.0722 —— 同样"满格"的绿和蓝,亮度差近十倍

这个权重在把彩色图转灰度、文字对比度计算、第 6 章 Bloom 提取亮部时都要用。直接平均三个分量会得到一张对比度失真的灰度图,蓝天的亮度和草地差不多。

线性空间与伽马空间的分界

线性空间与伽马空间的分界

伽马校正:被低估的坑

CRT 显示器的电压-亮度关系不是直线,大约是幂函数:输出亮度 ≈ 输入值的 2.2 次幂。为了在 CRT 上正确显示,图像标准干脆把存储值预先做了 0.45 次幂的编码——存的是亮度的"平方根"。现代 LCD/OLED 物理上是线性的,但为了兼容几十年积累的素材与习惯,整条链路仍保留这个非线性编码,称为 sRGB 空间。

陷阱在于:光照计算(乘法、点积、积分)只在物理上有意义的线性空间成立。在伽马空间做插值或混合,中间值会偏暗:

def srgb_to_linear(c): # 8位存储值 → 线性物理亮度 c = c / 255.0 return c ** 2.2 def linear_to_srgb(c): # 线性亮度 → 8位存储值 return round((max(c, 0) ** (1/2.2)) * 255) # 红蓝各半的"正确"中间色:应在线性空间取均值 mid_wrong = (200 + 50) // 2 # 直接平均:125 mid_right = linear_to_srgb((srgb_to_linear(200) + srgb_to_linear(50)) / 2) print(mid_wrong, mid_right) # 输出: 125 134 —— 伽马空间直接平均偏暗约 9 个色阶

单个像素差 9 阶看似不大,但在渐变背景、半透明混合、光照累积叠加时,误差层层放大,典型症状是暗部发闷、白色渐变中段出现脏灰色带。正确的流程是:纹理采样后先转线性空间,全部光照与混合计算在线性空间完成,最终输出前再编码回 sRGB。现代图形 API 提供 sRGB 纹理格式自动完成这个转换,省心且不占带宽。

⚠️ 常见坑:美术给的贴图素材几乎都是 sRGB 编码,数据贴图(法线图、金属度图)却是线性的——把法线图当 sRGB 采样,材质高光会整体错误。标记纹理用途再选格式,是渲染管线搭建时的必查项。

常见疑问

问:既然 HSV 这么直观,为什么帧缓冲不用 HSV 存储?
答:存储格式要迁就硬件与混合运算。透明混合、加法叠加、光照乘法在 RGB 空间都是分量级的线性运算,一行代码搞定;HSV 的色相是一个角度,两色混合不能简单平均角度(红 350 度与橙 10 度平均成 180 度的青色,荒谬)。正确姿势是:存算用 RGB(或线性 RGB),给人看、给人调时临时转 HSV。

问:美术说"这图发灰",多半是哪一步错了?
答:八成是伽马。要么纹理按 sRGB 采了却当线性用(画面整体偏暗发闷),要么线性计算的结果没编码直接上屏(整体泛白发灰)。排查顺序:查纹理格式标记、查渲染目标格式、查最后一步有没有做 2.2 次幂编码——三处任何一处漏掉,观感都会"说不出哪里不对但就是不对"。

本节要点回顾

  • 三个分量是生理结果:三种视锥细胞决定了三原色混色的合法性
  • HSV 是给人用的坐标:拾色、调色、按色调分离,效率远高于 RGB
  • 亮度加权:绿重蓝轻(0.2126/0.7152/0.0722),平均三通道是错的
  • 伽马无处不在:存储是 0.45 次幂编码,计算前必须转线性,输出前转回
  • 纹理要分用途:颜色图 sRGB、数据图线性,混用即翻车

格子里填什么数已定,下一节看这些数字如何被硬件变成真实的光。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U