如何理解Gamma校正?
网上很多人讲了Gamma校正的东西,好多都是说由于CRT显示器的电压与亮度不是线性关系的特性导致的,但总感觉怪怪的。
韩世麟同学写出了我的困惑。
凡是说Gamma 2.2来自于老式CRT显示器物理特性的解释,都是误解。 这个误解一般会这么讲解Gamma的来龙去脉:当年老式的CRT显示器内置Gamma 2.35左右,解码的时候会把输入信号压暗,所以我们呢,为了保证总Gamma接近1,就要预先在编码的时候把输入文件的信号提亮,而且这样一来呢,刚好顺应了人眼对暗部感兴趣的特点,把暗部的信息多多记录了下来,充分利用了文件的空间,真是美妙的巧合啊。讲起来顺畅,听起来也很美妙,我也曾经这么给别人讲,但是我发现我没法说服我自己,人类就被一个老式硬件的物理特性决定了后世的工业标准?这逻辑不对。
所以我觉得需要再梳理下Gamma校正的前因后果,以便更好地理解。
图像保存与图像显示用的Gamma校正#
人眼对亮度的感觉#
首先从人眼对亮度的感受出发。
下图是摄影箱常用的黑白灰卡。假定人眼感受到的白色是100%灰阶,黑色是0%的灰阶,那灰卡上的中灰是介于黑色和白色之间50%处的灰阶。
用反射率测量仪器测量各卡的反射率,假定白卡的反射率为100%。那么黑卡的反射率为4%,中灰卡的反射率是18%。并不是我们通常认为的的50%。
再看一个韩世麟同学提到了《粉刷匠的故事》,用以更好地理解灰阶。
假如你是一位粉刷匠,你拥有充足的白油漆和充足的黑油漆,那么现在要求你完成一个任务:
把黑白油漆混合成各种不同灰度的油漆,并且把它们排成一排,直到形成黑到白的均匀过渡。
在理想的情况下,会发生什么样的情况?你会不断地调出灰色的油漆,并且把它和已有的油漆相比较,如果它是一个新灰色,你就会把它插入队列,如果是重复的灰色(肉眼难以分辨,达到了你灰阶分辨能力的极限),你就会把它丢掉。
最终,功夫不负有心人,你面前的灰阶将会形成从黑到白的均匀过渡,此时的灰油漆种类将会是几百个甚至更多,那么最中间那一个,就是中灰的油漆:
之前我们已经测量过中灰的反射率为20%左右。从上面这个图可以得到结论:从纯黑到中灰,与中灰到纯白,这两部分人眼能分辨出来的灰阶数目是一样的。
对这些灰阶进行物理反射率的测量,可以得到自然界中亮度(可以用反射率近似)与人眼感受到的灰阶的对应关系。
从上面的曲线对应关系可以得到结论:
- 人眼对自然界亮度的感受不是线性的,对应关系近似幂次函数
。 - 人眼感受 0 ~ 0.5的灰阶与0.5 ~ 1.0的灰阶,分别对应了反射率0 ~ 0.2, 0.2 ~ 1.0,也就是说人眼对暗的亮度变化感受比较明显!
人眼对亮度的感受非线性可以由韦伯定律解释。
韦伯定律是由德国著名的生理学家与心理学家E.H. 韦伯发现,韦伯-费希纳定律是表明心理量和物理量之间关系的定律,即感觉的差别阈限随原来刺激量的变化而变化,而且表现为一定的规律性,用公式来表示,就是△Φ/Φ=C,其中Φ为原刺激量,△Φ为此时的差别阈限,C为常数,又称为韦柏率。
举例,距离在一个黑暗的房间里,假定点第1支蜡烛时人眼感受到的亮度为1,那么想要让人眼感受的亮度增加到2,就需要再点燃2支蜡烛,而不是1支。同样地,人眼想要感受亮度为3,就需要再加4支蜡烛。人的感受变化不是随着刺激线性变化的。生活中有很多这样的例子,人对疼痛的感受,味觉等等。
人眼对暗的亮度变化感受比较明显,在图像大小受限的条件下,可以花更多的空间存储颜色较暗的部分,能起到提高图片质量的效果。
到这,先记住两个结论:
- 人眼对自然界亮度的感受不是线性的,对应关系大约是幂次函数pow(0.45) 。
- 人眼对暗的亮度变化感受比较明显,也就是暗部细节对视觉更重要。
接下来从讲讲CRT显示器的故事。
CRT显示器#
CRT显示器大概长这样,也被称为“大背头”。
CRT的由来,来自百度百科。
1897,诺贝尔奖获得者、著名物理学家和发明家KarlFerdinand Braun(卡尔·布劳恩)创造了第一个CRT (Cathode Ray Tube,阴极射线管)。其工作原理是:电子枪发射高速电子,经过垂直和水平的偏转线圈控制高速电子的偏转角度,最后高速电子击打屏幕上的磷光物质使其发光,通过电压来调节电子束的功率,就会在屏幕上形成明暗不同的光点形成各种图案和文字。但是,此时的CRT大部分还是用来验证粒子、电子等现象的设备,似乎同显示毫无关系。
直到1925年,约翰·洛吉·贝尔德(John Logie Baird)在伦敦的一次实验中使用CRT器材“扫描”出木偶的图象成为一个转折点,其被称为电视诞生的标志。
CRT显示器的工作原理:
CRT显示器用电子束来进行控制和表现三原色原理。电子枪工作原理是由灯丝加热阴极,阴极发射电子,然后在加速极电场的作用下,经聚焦极聚成很细的电子束,在阳极高压作用下,获得巨大的能量,以极高的速度去轰击荧光粉层。这些电子束轰击的目标就是荧光屏上的三基色。为此,电子枪发射的电子束不是一束,而是三束,它们分别受电脑显卡R、 G、 B三个基色视频信号电压的控制,去轰击各自的荧光粉单元。 电子束的电流是受显示信号控制的,信号电压高, 电子枪发射的电子束流也越大,荧光体发光亮度也越高。
这种显示器有个特性,显示器显示出来的亮度与电压并不是线性关系。
电压与亮度的关系为
在这里我们定义Gamma:输出与输入的幂次关系。
Gamma校正:主动对输入信号做一个幂次函数变换。
可以这么理解这个公式,想让显示器显示20%的物理亮度,如果显示器按照给20%的电压,那么最终显示器显示的亮度为2.9%,显示器你这不搞笑吗?你得给我解决这个问题。
显示器厂商可以这样做,反正显示器电压和亮度不成线性关系在技术上也没法整,那不如就在现成条件下,对输入信号做一下处理。比如想要在显示器上显示20%的亮度,那显示器直接用20%的电压整肯定不行,那显示20%的亮度多大的电压呢?查这个曲线嘛,需要50%。那显示器提供50%的电压,就可以显示出50%的亮度了。
也就是说显示器厂商可以对输入信号做一个CRT gamma的逆校正,就可以让显示器得到正确的亮度。即
那成,显示器可以按照这个方法解决物理亮度显示不正确的问题。
摄像设备保存物理真实亮度到图片里,电脑读取图片,并让显示器显示。虽然显示的亮度大致上没问题,但艺术家很挑剔,感觉你这图片咋感觉那么别扭呢,丢失了好多细节,于是找技术看看怎么回事。
还记得之前说的人眼对亮度感受的结论吗?
人眼对暗的亮度变化感受比较明显,也就是暗部细节对视觉更重要。
图像工程师经过分析后,知道原因了:图片保存亮度就一个字节,只能有255个数值,按照真实亮度从0%到100%均匀地分配。但是中灰只对应20%的亮度,也就是说人眼更敏感的暗部只占了很小一部分的存储空间!
可以这样处理,按照人眼的亮度感受曲线,把真实亮度编码之后,再存储到贴图上,理论上图片的质量比之前更好了。这个编码我们叫它Encoding Gamma。 处理公式可以近似成:
但有一个问题,这时候图片里存储的已经不是物理的亮度了,而上面说的显示器的假定的输入是物理亮度,这不就出问题了嘛。这还涉及到显示器供应商,那得开个会啊。
于是微软、惠普、互联网企业和供应商开了个会,显示器别整那别扭的CRT gamma逆校正了。那样高质量图片(encoding gamma = 0.4545),正好能在显示器的CRT gamma(gamma = 2.2)作用下显示出自然界中的样子,多棒的巧合(encoding gamma * CRT gamma = 1)!
于是他们提出了sRGB标准,作为互联网的通用色彩标准。
当然sRGB标准比上面说的要复杂,它提出了几个概念:
- Viewing gamma:是整个从图片拍摄到最终显示器上显示的gamma,是camera gamma 与 display gamma的乘积。
- Camera gamma:摄像或感光元件对物理亮度的转换gamma,也就是上面提到的encoding gamma,图片里保存的是转换后的值。
- CRT gamma:物理显示器的gamma。
- LUT gamma:帧缓冲的颜色映射表的gamma,不是shader里用的LUT,猜测是显示器自己实现的色彩校正的东西。
- Display gamma:CRT gamma * LUT gamma,提交帧缓存的显示gamma。
所以我们需要关注的是
下图是真实场景产生图像到显示器显示图像的完整流程,如果想要显示器显示的图像与看到的真实场景完全一样,保证viewinggamma为1就可以了。
当然在viewgamma并不要求一定为1,当在昏暗的电影院时,viewgamma为1.5我们人看起来更爽、更舒服,而在明亮的办公室这种环境中更适合1.125。
sRGB标准帮我们处理了gamma,解决了图像生成和显示的问题:
- 在图像生成的时候做了encoding gamma的编码。
- CRT显示的时候执行display gamma。
虽然给工业界提供了很大的便利,但同时也会引发其他的问题。
sRGB标准引起的问题及如何解决#
着色计算#
自然界20%的亮度,存储在贴图里的值经过encoding gamma后约保存为50%。
现在有个效果需求,对这个亮度效果翻倍。正确的亮度是 20% * 2 = 40%。
而现在的渲染流程会发生什么?shading里读取贴图得到的值为 50%,在shader里乘以2,得到100%,经过显示器显示它是100%的亮度!错了!
现在整理下渲染流程,并定义几个空间便于理解。
- 物理亮度值所在的空间,是物理空间,我们称它为线性空间。
- 物理亮度经过encoding gamma后,结果所在的空间成为encoding gamma空间。
- 贴图经过shading,输出到framebuffer,结果所在的空间为仍然为encoding gamma空间。
- Framebuffer经过显示器的display gamma后,结果所在的空间为viewing gamma空间(viewing gamma = encoding gamma * display gamma)。
发现问题了吗?我们期待的结果是在线性空间中将亮度乘以2。但是在渲染流程里面,是在encoding gamma空间中乘以2,结果必然出问题。
所以应该在shading计算前将贴图转换到线性空间,再将最后的framebuffer转换到encoding gamma空间,结果就对了。
在图形API中,可以通过创建贴图的时候用sRGB后缀的格式将贴图转换到线性空间,GPU会自动转换;当然也可以在shader里执行pow(tex, 1.0/encoding gamma) 完成转换。
颜色混合#
借用博客——我理解的伽马校里的例子:
在Quad上画了个边缘模糊的圆,然后使用了混合模式来会屏幕进行混合。我们在场景中画三个这样不同颜色的圆,三种颜色分别是(0.78, 0, 1),(1, 0.78, 0),(0, 1, 0.78):
在不同颜色的交接处出现了不正常的渐变。例如,从绿色(0, 1, 0.78)到红色(0.78, 0, 1)的渐变中,竟然出现了蓝色。
正确的显示结果应该是:
原因跟上面着色的例子是一样的,混合时的颜色值不在线性空间,正确的做法应该先把颜色转换到线性空间,进行混合,然后再转换到encoding gamma空间。
Youtobe上有一个视频讲解混合出现的问题, Color is broken,可以看看。
我们使用一个稍微扩展的数学公示来解释:假定
把
而目前图形API采用的混合方式,是直接对贴图的颜色值进行了混合,得到的结果是
正确的结果是线性空间的,图形API混合的结果在encoding gamma空间。为了方便比较,我们将线性
空间的结果也转换到encoding gamma空间:
由 琴生不等式可得,
也就是说在encoding gamma空间混合的结果比真实的结果要暗的。
不过各种软件和厂商的混合算法,都是拿直接拿贴图或framebuffer做的混合,而贴图或framebuffer在什么空间,GPU是不知道的,所以想要得到正确的结果,需要图形程序员自行做空间的转换。
总结#
由于人眼对自然界亮度的非线性感受,并且对暗部比较敏感,再加上CRT显示器电压与显示亮度非线性关系,最终工业界提出了sRGB通用颜色空间,作为互联网的通用标准,用以解决图像质量和显示器显示的一系列问题。
而之后长时间在选中光照和混合长时间都不在线性空间中计算,造成失真。解决这个问题需要在shader计算前把贴图转换到线性空间,在线性空间中光照、混合!最后再转换到encoding gamma空间,才能得到正确的渲染结果。
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)