图形 2.6 伽马校正
伽马校正
Gamma校正
为什么要进行伽马校正?因为人眼对亮度的感知和物理功率不成正比,而是幂函数的关系,这个函数的指数通常为2.2,称为Gamma值,Gamma值可以简单定义为:
Vout = Vingamma
它源于CRT的响应曲线,CRT即阴极射线显像管,这种设备的亮度和电压不成线性关系,而是和gamma值约为2.2类似幂律的关系,由于CRT的这个物理特性,刚好可以把亮度压暗,也就说,左图变亮的情况下,经过右图显示器的压低亮度校正,结果刚好可以显示正常。
因此我们可以知道,Gamma校正本质是指对线性三色值和非线性视频信号之间进行编码的操作:
gamma校正的效果:
编码就是指将光信号转换为视频信号,而解码就是把视频信号解码为线性的光信号,那么我们要怎样做到这步呢?答案则是传递函数。
我们在现实生活中,经常碰到一些常见的颜色空间,如:sRGB,Rec-709,DCI-P3等。具体可以看之前图形部分2.1的色彩空间的讲解。但是像这种sRGB和Rec.709的色域差不多,三原色位置相同的,他们的看起来是一样的,但是传递函数不同。那么传递函数到底是什么呢?我们可以按照字面意思来理解,它用作数据的传递,传递的便是光信号和电信号,用来做信号的转换。
传递函数包含两个部分,一个是光转电传递函数(OETF),用处是把场景的线性光转到非线性的视频信号值;一个是电转光传递函数(EOTF),把非线性视频信号值转到显示光亮度。
可为什么我们不用线性的方式来储存而是要来回转换呢?实际上我们用于显示图像的真彩格式RGBA32,每个颜色通道只有8位用于记录信息,如果想要充分的利用带宽就要用更多的位置去储存暗部值。目前我们所普遍使用的sRGB颜色空间标准,他的传递函数gamma值为2.2(2.4)。即暗部用高精度保存,亮部用低精度保存。而且这和人眼的特性有关,人眼对暗部的变化感应是更敏感的,因此我们需要提到下个概念“韦伯定律”。
韦伯定律
韦伯定律:韦伯定律,即感觉的差别阈限随原来刺激量的变化而变化,而且表现为一定的规律性,用公式来表示,就是△Φ/Φ=C,其中Φ为原刺激量,△Φ为此时的差别阈限,C为常数,又称为韦伯率。
简单来说就是,当所受刺激越大时,需要增加的刺激也要足够大才会让人感觉到明显变化,但是只适用于中等强度的刺激
比如上面这个图,我们正常的人眼来看的话,其实是上面的那一行明暗变化更加均匀,但是实际上按照物理量均匀变化的是下面那一行,正如上面所提到的,我们人眼是对暗部更加敏感的。而gamma编码的曲线,就是把把人感受到的均匀灰阶和自然界线性增长的亮度进行一个映射,自然界中亮度的0.2左右的亮度,对应的就是人眼感受到的中灰色(0.5)。
人心理上感受到的均匀灰阶:
值得注意的是,上述所说的前提是,在条件相同的情况下但明暗不同的环境下,看到的结果可能不同,因此我们取的中灰值,也不是指特定的一个值。所谓的中灰值,并非某个具体的值,而是取决于人眼的具体感受。
如下图,AB两个色块的颜色其实是同一种颜色:
线性工作流
线性工作流全称Linear Workflow(LWF),“工作流”在这里可以当作工作流程来理解。LWF就是一种通过调整图像Gamma值,来使得图像得到线性化显示的技术流程。而线性化的本意就是让图像得到正确的显示结果。设置LWF后会使图像明亮,这个明亮即是正确的显示结果,是线性化的结果。
那么具体什么是线性工作流程呢?线性工作流程就是软件在计算时,所有参与计算的数据都是基于线性的。我们使用的几乎所有图像处理软件都是基于线性的计算方式,即这种流程的正确的前提是它在计算前所有的素材,条件,灯光都是线性数据,1代表的是1,5代表的是5。但是我们以前的流程不是基于LWF,是非线性的(错误的),后期制作也都是基于错误的计算方式。能够接受它是因为我们在错误的基础上让它看起来“好看”(其实并不正确)。
线性工作流程确切的说是匹配软件的线性工作流程。这种流程在2015年-2018年才被广泛应用于电影广告等高端项目。国外大型工作室几乎都是基于这种流程。实际上我们需要物理上正确的计算,那只能是基于线性工作流程。
简单来讲LWF的目的,就是在各个环节正确的使用gamma编码/解码,来达到最终输出的数据和最初输入的物理数据一致。
假设我们不在线性空间下进行渲染工作,不遵循LWF,可能会产生什么问题呢:
亮度叠加
- 非线性空间下亮度叠加出现了过曝(亮度>1的)的情况
- Gamma空间经过gamma编码后的亮度值相对之前会变大
颜色混合
- 如果在混合前没有非线性的颜色进行转换,就会在纯色的边界出现一些黑边。
光照计算
- 在光照渲染结算时,如果我们把非线性空间下(视觉上的)的棕灰色0.5当做实际物理光强为0.5(实际上更大)来计算时,就会出现左边这种情况。
- 在显示空间下是0.5,但在渲染空间下它的实际物理光强为0.18(如右图)
Unity中的颜色空间
在Unity中选择颜色空间
点击菜单 -> Project Settings -> Player页签 -> Other Settings 下的Rendering部分,通过修改Color Space可以来选择Gamma/Linear(线性)
当选择Gamma Space时,Unity不会做任何操作,因为Unity默认就是Gamma。
当选择Linear Space时,引擎的渲染流程在线性空间计算,理想情况下项目使用线性空间的贴图颜色,不需要勾选sRGB;
如果勾选了sRGB的贴图,Unity会通过硬件特性采样时进行线性转换。
硬件支持
目前以下平台可以进行线性空间下的硬件支持
- Windows,Mac OSx ,Linux(Standalone)
- Xbox One
- PS4
- Android(OpenGL ES3.0)
- IOS(Metal)
- WebGL
Unity主要通过以下两个硬件特性来支持
- sRGB Frame Buffer
- sRGB Sampler
sRGB Frame Buffer将Shader的计算结果输出到显示器会前做Gamma校正,作为纹理被读取时会自动把存储的颜色从sRBG空间转换到线性空间。调用ReadPixels()、ReadBackImage()时,会直接返回sRGB空间下的颜色。sRBG Frame Buffer 只支持每通道为8bit的格式,不支持float浮点格式。HDR开启后会先把渲染结果会知道浮点格式的FB中,最后绘制到sRGB FB上输出。
sRGB Sampler将sRBG的贴图进行线性采样的转换。
使用硬件特性完成sRGB贴图的线性采样和shader计算结果的gamma校正,比在shader里对贴图采样和计算结果的校正要快。
资源导出问题
SubstancePainter
SubstancePainter的贴图导出时,其线性的颜色值经过了gamma编码,所以颜色被提亮了。此时这个贴图需要在Unity中勾选sRBG选项,来让它被采样时能还原回线性值。
PhotoShop
从PS中导出图片,如果把灰度系数设为一的话,在Unity中就不需要勾选sRBG选项了。如果是在线性空间下,PS中可以什么都不做设置,在Unity中勾选sRGB选项即可。
不过实际上PS对于颜色的管理是很精准的,我们在Unity中看到的颜色要经过显示器的gamma变换,而PS中的不会,PS会读取显示器的Color Profile,反向补偿回去,也就是说,ps中的是真实的颜色值。因为PS自身有一个系统,会通过灰度值控制颜色的显示,(通常情况下这个值和显示器的gamma值一致,所以看起来会和Unity中看到效果的一样),可以通过改变灰度值来改变最终颜色显示的结果。
对于半透明效果,Unity进行半透明混合时,会先将它们转换到一个线性空间下然后再混合。但PS中图层和图层之间做混合时,每个上层的图层都会读取他们的Color Profile(gamma值),然后经过一个gamma变换再做混合,这样做得结果就会偏暗一些。这个可以在它的工作空间 的设置中进行更改,选择用灰度系数混合RGB颜色,参数设置为一,这样图层才是一个最终直接混合的结果。
扩展作业
这里以Unity作为示例。
Unity中,我们把颜色空间转到线性空间下。
那么线性颜色空间的图片显示是没有问题的。
而经过伽马校正的图片则会更亮一些
原本是线性空间的图片如果勾选了sRGB选项则会更暗
我们可以使用UnityCG.cginc中的函数进行手动校正
//线性空间的伽马校正 inline half3 LinearToGammaSpace (half3 linRGB) { linRGB = max(linRGB, half3(0.h, 0.h, 0.h)); return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.h); } //移除伽马校正的效果 inline half3 GammaToLinearSpace (half3 sRGB) { return sRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h); }
fixed4 frag(v2f IN) : SV_Target { half4 color = tex2D(_MainTex, i.uv); //将线性运算的结果进行 gamma矫正 color.rgb = LinearToGammaSpace(color.rgb); return color; }
参考资料
GAMES101-现代计算机图形学入门-闫令琪-Lecture 20 Color and Perception