当前位置: 首页 > news >正文

gamma校正改

网上有很多关于Gamma校正的解释,有基于屏幕设计的,有基于存储的,大多基于以下两张图:

image
LearnOpenGL:
第一行是人眼所感知到的正常的灰阶,亮度要增加一倍(比如从0.1到0.2)你才会感觉比原来变亮了一倍(译注:我们在看颜色值从0到1(从黑到白)的过程中,亮度要增加一倍,我们才会感受到明显的颜色变化(变亮一倍)。打个比方:颜色值从0.1到0.2,我们会感受到一倍的颜色变化,而从0.4到0.8我们才能感受到相同程度(变亮一倍)的颜色变化。如果还是不理解,可以参考知乎的答案)。然而,当我们谈论光的物理亮度,比如光源发射光子的数量的时候,底部(第二行)的灰阶显示出的才是物理世界真实的亮度。如底部的灰阶显示,亮度加倍时返回的也是真实的物理亮度(译注:这里亮度是指光子数量和正相关的亮度,即物理亮度,前面讨论的是人的感知亮度;物理亮度和感知亮度的区别在于,物理亮度基于光子数量,感知亮度基于人的感觉,比如第二个灰阶里亮度0.1的光子数量是0.2的二分之一),但是由于这与我们的眼睛感知亮度不完全一致(对比较暗的颜色变化更敏感),所以它看起来有差异。

image
LearnOpenGL:
点线代表线性颜色/亮度值(译注:这表示的是理想状态,Gamma为1),实线代表显示器显示的颜色。如果我们把一个点线线性的颜色翻一倍,结果就是这个值的两倍。比如,光的颜色向量代表的是半暗红色。如果我们在线性空间中把它翻倍,就会变成,就像你在图中看到的那样。然而,由于我们定义的颜色仍然需要输出的显示器上,显示器上显示的实际颜色就会是。在这儿问题就出现了:当我们将理想中直线上的那个半暗红色翻一倍时,在显示器上实际上亮度翻了4.5倍以上!

但我个人在阅读许多文章后依然难以搞懂,于是自己学习总结出以下理解方法,从历史发展,到数学的离散化解析,到实际LearnOpenGL的gamma校正章节


从感官错觉到物理还原:彻底搞懂Gamma校正与OpenGL渲染管线

要彻底搞懂 Gamma,我们不能只看代码和做了什么,必须先从物理光照、人眼感知与数据存储这三者的关系说起。

第一部分:简单说明 Gamma 校正做什么

Gamma 校正是,我们要对图像颜色进行一个 \(\frac{1}{2.2}\) 的幂操作。即,原本的图像输出信息记为 \(y_0 = x\),Gamma 校正后的图像输出信息则是:

\[y = x^{\frac{1}{2.2}} \]

image

具体参考该图,Gamma 校正就是把中间那条直线的颜色信息,扭曲成左上方的红色虚线的颜色信息。
最难以理解的就是,我们为什么要这么做?

第二部分:理论篇 —— 光、人眼、数据与屏幕的历史

为了把一切串联在一起,我们要知道以下几件事情:

1. 光的物理量

首先,我们需要明确:真实世界中的光照强度变化是严格线性的。这也是为什么在图形学中,我们始终能对光线进行线性操作且不出错。
如果把物理光强作为横轴,“物理亮度”作为纵轴,画出来的图 1 绝对是一条完美的直线。一盏灯加一盏灯,能量就是两倍。

*图1:光物理强度的线性关系*

2. 人眼感知亮度

明确一点:进入人眼的光,就是在真实世界中强度线性变化的光,但人眼感知亮度接收到的光物理强度并不是线性关系。

人类的眼睛并不是一台绝对客观的测光仪。在进化的过程中,为了在黑夜中保命,人眼对暗部的光线变化变得极其敏感,而对亮部的变化则十分迟钝。

image

如何理解?看这张图:
首先上面一行,是感知亮度 \(0 \sim 1\) 线性变化时,人眼所看到的亮度。
下面一行,是光物理强度 \(0 \sim 1\) 线性变化时,人眼所看到的亮度。

可以注意到,当光物理强度\(0 \sim 1\) 线性变化时,我们的感知亮度是不均匀的(比如当光物理强度\(0.1\) 时,感知亮度几乎已经达到了 \(0.4\))。
也就是说,光物理强度\(0 \sim 0.1\) 之间变化,而人的感知亮度却在 \(0 \sim 0.4\) 之间剧烈变化,这说明人眼对暗部的光线分辨率远高于亮部。
这个非线性的映射关系大概是:

\[\text{感知亮度} = \text{光物理强度}^{\frac{1}{2.2}} \]

如果我们把人眼感知的亮度等分为 \(n\) 个阶梯,比如我们希望看到 \(0.2, 0.4, 0.6, 0.8\) 的感知亮度,并将其映射到图 1 上(红色虚线,x-光物理强度,y-人感知亮度;绿色虚线,x-光物理强度,y-“光物理亮度”),我们会发现:在靠近 0(暗部)的地方,这些阶梯密密麻麻;而在靠近 1(亮部)的地方,阶梯变得非常稀疏。

3. 屏幕的颜色输出与历史兼容

早期的显示器(CRT 阴极射线管)存在一个严重的物理缺陷:输入的电压信号与屏幕最终产生的物理亮度之间,并非完美的线性关系,而是一个近似 \(2.2\) 次方的幂函数关系:

\[\text{物理输出亮度} = \text{输入电压}^{2.2} \]

这意味着,如果我们给显示器输入 \(0.5\)(即 50%)的信号,它实际输出的亮度只有 \(0.5^{2.2} \approx 0.218\)(约 21.8%),导致画面整体严重偏暗。

现代的 LCD 和 OLED 屏幕本可以做到完美的线性响应,但为了向后兼容(保证几十年来按老标准保存的图片、电影等数据依然能正常显示),现代屏幕在硬件或固件底层,依然刻意保留并模拟了这个 \(2.2\) 的 Gamma 压暗曲线。

4. 现代图像存储的采样魔法

为什么我们说图像在存储时预先进行 \(x^{\frac{1}{2.2}}\) 编码反而更好?

真相是:原本的物理世界是连续的,人眼看到的亮度曲线也是连续的。但是,当我们在计算机中离散存储图像数据时(比如主流的 8-bit 图片每个通道只有 256 个颜色阶梯),问题就出现了。

正如第 2 点所讨论的,假设我们对光物理强度进行均匀的离散切分来存储数据,那么这些均匀的数据点投影到人眼感知亮度的曲线上时,分布将极其不均匀(暗部的感知采样点极度稀疏,亮部极度密集)。

我们评判一张图片观感好不好,关键在于它在人的感知亮度曲线上构造得如何。因为按线性强度存储会导致暗部的感知采样点严重不足,画面就会出现明显的色彩断层(Banding)、感觉很不自然。

于是,我们在存储图像前,先对其进行 \(x^{\frac{1}{2.2}}\) 的幂运算。这样处理后,离散的光强度数据点在投影到人眼感知亮度时,分布变得完全均匀,采样效率极高,曲线更加平滑。这就是为什么在同样的离散存储精度下,图像预先经过 \(x^{\frac{1}{2.2}}\) 编码视觉效果更好。

5. 2.2 Gamma 屏幕的真相

因为真实世界光线是线性进入人眼的,所以我们要求屏幕最终输出的光线结果,从数学上也必须符合线性规律还原。

在过去的时代,CRT 的物理缺陷使得我们的图像在存储数据时都被迫进行了 \(x^{\frac{1}{2.2}}\) 的反向处理来进行抵消。当现代屏幕出现后,虽然硬件已经可以直接按数据输出对应强度的线性光线,但为了迎合过去海量的历史数据,我们并没有选择在软件层面去对历史存储格式进行转换,而是选择在硬件上继续兼容(保留 \(x^{2.2}\) 的输出曲线)。

然而,最戏剧性的反转是:当后来图形学界深入研究了人眼的特点后,才恍然大悟——图像数据按 \(x^{\frac{1}{2.2}}\) 编码处理,恰恰是为了让人眼看到优秀图像的最优存储方式!

因此,屏幕刻意保留的 \(2.2\) Gamma 曲线,不仅是为了偿还历史债务,更是为了完美解码这种极其优秀的数据压缩格式。


第三部分:实践篇 —— OpenGL 中的数学对决与破局

带着上述理论基础,我们再来看 OpenGL 的渲染管线,一切迷雾都将散开。

1. 灾难现场:直接开启 Gamma 校正为何过曝?

在没有告诉 OpenGL 纹理是 sRGB 格式(即内部带有预补偿)的情况下,如果我们为了追求“物理正确”,在着色器(Shader)最后强行开启 Gamma 校正(输出前手动应用 \(x^{\frac{1}{2.2}}\)),会发生什么?

此时,着色器将带有预补偿的 \(C_{sRGB}\) 当作纯线性数据进行光照计算,然后强行加了一次校正。让我们写出最终经过显示器物理压暗(\(x^{2.2}\))后,人眼感知的数学表达式:

\[\text{Perceived} = \left( \left( C_{sRGB} \cdot Light \right)^{\frac{1}{2.2}} \right)^{2.2} = \left( \left( C_{linear}^{\frac{1}{2.2}} \cdot Light \right)^{\frac{1}{2.2}} \right)^{2.2} = C_{linear}^{\frac{1}{2.2}} \cdot Light \]

结论: 管线末端的校正 \(\frac{1}{2.2}\) 与屏幕的 \(2.2\) 相互抵消了!光照计算虽然正确,但纹理颜色依然是 \(C_{linear}^{\frac{1}{2.2}}\)。在 \((0, 1)\) 的区间内,\(x^{\frac{1}{2.2}}\) 总是显著大于 \(x\)(例如 \(0.5^{\frac{1}{2.2}} \approx 0.73\))。这就导致了原始纹理被错误地提亮,数学上完美解释了画面为何会发白、过曝。

2. 光线衰减的三套流程对决(数学推导)

为了更深刻地理解,我们设原始线性光强为 \(I\),距离为 \(d\)。推导一下在不同管线流程中,人眼最终看到的有效光照衰减曲线

流程一:错误输入 (RGB) + 无 Gamma 校正 + 物理正确衰减 (\(\frac{1}{d^2}\))

纹理自带 \(\frac{1}{2.2}\) 提亮,光照用平方反比,最后经过屏幕 \(2.2\) 压暗。

\[\text{Perceived} = \left( I^{\frac{1}{2.2}} \cdot \frac{1}{d^2} \right)^{2.2} = \frac{I}{d^{4.4}} \]

分析: 衰减曲线变成了恐怖的 \(\frac{1}{d^{4.4}}\)。近处极其容易过曝,而稍微远一点光线就断崖式下跌,死黑一片。视觉体验灾难。

流程二:错误输入 (RGB) + 无 Gamma 校正 + 线性衰减妥协 (\(\frac{1}{d}\))

既然平方衰减效果太差,早期的图形开发者采用了物理错误、但视觉讨巧的线性衰减作为“Hack”补偿。

\[\text{Perceived} = \left( I^{\frac{1}{2.2}} \cdot \frac{1}{d} \right)^{2.2} = \frac{I}{d^{2.2}} \]

分析: 奇迹出现了!人眼看到的有效曲线是 \(\frac{1}{d^{2.2}}\),这非常接近物理真实的 \(\frac{1}{d^2}\)。这就是为什么在缺乏 Gamma 校正的旧时代,线性衰减反而能骗过眼睛,看起来更自然的原因。

流程三:正确输入 (sRGB) + 有 Gamma 校正 + 物理正确衰减 (\(\frac{1}{d^2}\))

这是现代 PBR 的标准做法:硬件(GL_SRGB)自动将纹理转回线性,我们在纯线性空间计算,最后做 Gamma 校正抵消屏幕。

\[\text{Perceived} = \left( \left( I \cdot \frac{1}{d^2} \right)^{\frac{1}{2.2}} \right)^{2.2} = \frac{I}{d^2} \]

分析: 完美的物理还原!只有在这一套流程下,我们才真正在屏幕上再现了物理世界严谨的光照规律。

3. 告别“玄学参数”,拥抱线性基底

在实际工程中,为了防止光源中心 \(d=0\) 时的除零溢出(无限亮),并增加艺术控制力,我们常使用复合衰减模型

\[Attenuation = \frac{1}{K_c + K_l \cdot d + K_q \cdot d^2} \]

在早期的学习资料(如旧版 Ogre3D 文档)中,常常会流传一张“不同距离下的光源衰减参数表”(即特定的 \(K_c, K_l, K_q\) 组合)。
现在我们回过头看就明白了:那张经典的参数表,本质上是为了“没有 Gamma 校正的错误管线”量身定制的“硬调补丁”! 它通过强行拉高线性项 \(K_l\),来对抗屏幕产生的 \(\frac{1}{d^{4.4}}\) 地狱级衰减。

结语

当我们彻底开启 Gamma 校正,将一切计算统一到线性空间这块坚实的基底上后,这座建立在扭曲视觉上的“参数空中楼阁”就不再需要了。

光就是光,距离就是距离。多光源叠加(1+1 终于严格等于 2)、高光泛光(Bloom)、PBR 材质响应,一切高级渲染技术都变得顺理成章、极易调控。我们终于从依靠参数“特调”的“视觉炼丹师”,蜕变成了掌握物理规律的图形学工程师。

http://www.jsqmd.com/news/518913/

相关文章:

  • 【嵌入式C多核性能优化终极指南】:20年老兵亲授ARM Cortex-A/R系列7大实战陷阱与提速400%的3个关键锁策略
  • 免费替代Pr?我用Shotcut给公司做了100条产品视频后的7个避坑技巧
  • Linux操作系统的自动化部署工具选型
  • exgcd学习笔记
  • 北京婚纱摄影行业“隐形冠军”系列报道:布丁摄影,16年服务过30余位一线明星 - 企业推荐官【官方】
  • 学习GD32C113 -- 使用 GD32C113 驱动 1.54 TFT LCD、显示分形
  • 人工智能如何改变 Anthropic 的工作方式24
  • 6061铝板生产厂家,6061合金铝板现货加工 - 企业推荐官【官方】
  • 链表实战:用多项式加减乘除,彻底搞懂指针操作与内存管理
  • STM32呼吸灯保姆级教程:用CubeMX+TIM14生成PWM波(寄存器直接操作版)
  • 酵母单杂交(Y1H)技术:DNA - 蛋白质相互作用的真核筛选工具
  • 人工智能如何改变 Anthropic 的工作方式01
  • 人工智能如何改变 Anthropic 的工作方式15
  • 大航海时代ol台服找Call记(十一) 物品ID计算物品中文名称 (2)
  • 告别Transformer的平方复杂度:手把手带你用Mamba搭建一个长文本处理Demo
  • 计算机毕业设计springboot基于的电子报销系统的设计与实现 基于SpringBoot框架的企业财务费用报销管理平台设计与实现 基于Java技术的智能化员工费用申请与审批系统开发
  • Apache Doris数据更新全指南:从基础UPDATE到批量删除的7种应用场景解析
  • 人工智能如何改变 Anthropic 的工作方式25
  • FPGA实战:手把手教你实现VESA DSC编码(附Verilog代码解析)
  • 展锐UIS7862S安卓10.0开机动画DIY指南:从BMP制作到adb替换全流程
  • 算法设计中的空间复用与数据对齐优化的技术7
  • 想知道锅炉装备哪家公司好?这些要点帮你精准挑选! - 企业推荐官【官方】
  • 手把手教你用AI工具箱在本地搭建免费数字人(附夸克网盘资源)
  • 在北京拍了三次职业照,终于搞明白“形象照”和“流水线证件照”差在哪 - 企业推荐官【官方】
  • 从零开始学Orcad注释:图文详解文本框/字符/图片的工业级应用规范
  • RabbitMQ+WebSocket实战:5分钟搭建电商实时交易监控看板(Spring Boot 3.2.0+Vue 3)
  • 人工智能如何改变 Anthropic 的工作方式56
  • 计算机毕业设计springboot基于的二手交易平台 基于Spring Boot的校园闲置资源置换平台 基于Spring Boot的二手商品在线流通管理系统
  • 营养轻食代餐品牌推荐?2026六大减肥代餐产品全解析:拒绝挨饿,科学减重不反弹 - 企业推荐官【官方】
  • Altium Designer 22.11隐藏功能揭秘:如何找回消失的Gerber镜像层选项