重新认识Gamma与Linear色彩空间

重新认识Gamma与Linear色彩空间

在Unity的贴图导入中,有这样的一个选项“sRGB(Color Texture)”,当贴图作为颜色来显示,则勾上sRGB;当贴图作为值来计算,则不勾上。

关于Gamma和Linear色彩空间的坑遇到过好几次,每次都需要查阅资料,再看一遍。再到后来索性就按照上面的方式简单的来记忆。

基本上大部分由色彩空间引发的表现偏差的问题,都可以遵循上面做法解决。

什么意思呢?

假设一张贴图有RGBA四个通道,当我们在着色器中采样这张贴图,将采样的值输出,既是作为颜色用途来使用,则应该勾上sRGB;

而另一种情况,假设一张贴图有RGBA四个通道,当我们在着色器中采样这张贴图,采样出来的RGBA四个通道,其实都是0-1的浮点数,如果我们将这个数值用于计算(比如用于消融、纹理Splatting、作为Mask图等),仅用于计算,而不用于最后作为颜色输出,则不勾上sRGB,这样采样出来的数值才是线性的RGBA的值。

为什么会这样?下面基于个人的理解重新认识Gamma和Linear,缕一缕之间的关系:


原理篇

首先理解什么是Gamma?

物理显示设备上亮度的生成,通常和它输入的信号不成正比的,存在非线性的关系。

因为显示器的工作特点以及电压的幂定律(power-law)影响的缘故,最终显示在屏幕上的效果会比原来偏暗, 在传统的CRT显示器上输出的亮度大概是输入的2.5次幂左右(不同厂商做出的显示器略有区别)。而这个指数幂次的指数数值,就是我们俗称的Gamma,在这儿就是Gamma 2.5。

Gamma 2.5只是单纯考虑两者之间的指数关系,若是针对小区间的线性关系做出一定的修正,可以粗略认为Gamma是在2.2左右,这也是实验出来的数值,也和眼睛的感受曲线基本吻合。

输入信号与输出亮度的关系

这种特点在当时被认为并不是一种缺陷,而是作为非常有用的特性保留下来,当然也因为当时的技术和成本考虑。

所以在当时的CRT显示器由于Gamma必定的存在,会使输出的亮度变暗,那么要显示正确的亮度,就只能对输入信号做矫正,将输入的亮度调亮,这一步就是Gamma矫正。

直到如今的液晶显示器为代表开始取代CRT,这些显示器大部分都是输出亮度和输入信号都是类似线性关系的,而非像CRT的幂函数关系,但由于CRT作为主流电视机技术和电脑显示器技术已经存在太多年了,导致大部分的图像、视频以及互联网上的内容,甚至操作系统、浏览器等软件都在制作的时候考虑到CRT的Gamma特性,做出了上面所述的反向补偿(Gamma矫正)。为了保证兼容性,现在的显示器厂家只好在显示器内部加入相应的电路,去模拟过去CRT显示器的Gamma效果。否则就意味着在显示过去和现有的内容的时候,会出现显示不正常,画面过于明亮的效果。

且由于Windows系统假定显示器的伽玛值为2.2,即Windows的标准伽玛值。使用的伽玛值为2.2的显示器就可以达到接近理想的色彩。大多数液晶显示器都是基于2.2的Gamma而设计的。所以直到如今,我们仍需要对输入信号进行Gamma矫正。

Gamma矫正是什么?

Gamma矫正是将输入信号线性的直线,转成与Gamma相反的曲线,也既是做一次Gamma的逆处理(1/gamma = 1/2.2 = 0.45次幂的运算)。

Gamma校正

当我们输入的信号为图片时,可以理解为将图片调亮(仅仅只是存储图片数据时的“调亮”),这样当显示器在Gamma影响下输出颜色时,我们才能看到正确的色彩。

Gamma矫正

sRGB又是啥?

由于显示器Gamma的缘故,我们在显示器看到的色彩也是需要经过Gamma矫正才能显示正确,所有就有了sRGB色彩空间的概念。

对于图片来说,如果是sRGB空间的话,那么它就是被Gamma矫正过的,所以也通常说 sRGB空间 = Gamma 1/2.2。

sRGB(standard RGB)色彩空间是微软和惠普开发的一种标准的RGB格式,对应的就是基于2.2的Gamma而设计的设备,这种标准得到许多业内厂商的支持,显示器、打印机、数码相机,摄像机,扫描仪等都有sRGB的支持,所以一般我们也会默认的认为导入到操作系统里的图片都是sRGB格式的。包括摄像机拍到的照片,默认也会转为标准的JPEG或TIFF格式,也就是编码为Gamma 1/2.2的图像文件,当然,如果选择图片RAW则是保存线性空间的。

线性Raw图像 gamma = 1.0gamma校正的图像 gamma = 1/2.2


回到Unity的Gamma和Linear色彩空间

Gamma色彩空间工作流

Gamma Color Space

Gamma工作流存在的原因是因为以前的移动平台不支持线性渲染,即使现在支持了,但为了更大的兼容性,还是保留了Gamma工作流。

在Gamma工作流下,无论是否勾选了sRGB,都不会有影响。

GPU在采样纹理时,也都不会移除Gamma矫正,着色器会直接将输入的值看作是线性空间进行计算和输出,输出的结果也直接是Gamma矫正后的值。为了确保最终显示到显示器画面上的正确性,Unity编辑器在着色器输出写入Frame Buffer时进行了调整,不再对最终结果再应用一遍Gamma矫正。

Linear色彩空间工作流

Linear Color Space

在Linear工作流下,Unity仍然默认贴图是在Gamma色彩空间中的 (sRGB),但可以通过是否勾选sRGB选项,来决定是否以sRGB的方式进行采样,也决定着将纹理传给着色器之前,是保留Gamma矫正(勾上)还是移除Gamma矫正(不勾)。

如果贴图本身是在Linear空间下制作的(也可以理解是保存数值的),则需要忽略sRGB采样,取消勾选。

Linear工作流同时支持sRGB(Gamma空间)和Linear空间的贴图,如果是sRGB,则当采样纹理时,GPU会自动移除伽马校正,将结果转换为线性空间,然后传递给Shader,在正常情况下,在线性空间中进行照明计算。当将生成的值写入Frame Buffer时,有两种情况,一种是直接进行Gamma矫正,另一种则是仍在线性空间中保留,稍后再进行Gamma校正——这取决于当前的呈现配置。例如,在高动态范围(HDR)中,渲染结果将被保留在线性空间中,等最后再一次性进行Gamma矫正。

Gamma和Linear流程的区别

sRGB模式是在近代的GPU上才有的东西。如果不支持sRGB,我们就需要自己在Shader中进行Gamma矫正。对sRGB(Gamma 2.2)输入纹理的转成线性(Gamma 1.0)通常代码如下:

float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 );

最后输出颜色为了配合显示器的Gamma 2.2 一定要进行如下Gamma矫正计算:

fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);

return fragColor;

Linear 和 Gamma 光的衰减

光照表现一般受光源的距离和法线两个因素影响(在同等光强下)。与在Linear色彩空间下进行渲染对比,Gamma色彩空间下由于Gamma矫正的原因,使得光的半径显得更大。

参考下上图,我们可知Linear色彩空间下的光照计算,才是真正逼近真实世界的光照效果。而Linear和Gamma的光照衰减为何又如此的区别,具体原因可看下面两张图:

在Gamma色彩空间下,输入的信号不会移除Gamma矫正,直接进行光照计算,光照计算后,直接输出颜色,经过显示器的Gamma 2.2后,光照部分就变暗了下去,于是得到不现实的光照衰减结果。

在Linear色彩空间下,输入的信号会移除Gamma矫正,然后在线性空间下进行光照计算,关键的区别在于输出到显示器之前会再进行一次Gamma矫正,此步也是将光照信息进行了Gamma矫正,于是经过显示器的Gamma 2.2之后,输出的光照衰减将会是正确的光照衰减。

Linear 和 Gamma 光照下亮度的区别

Linear 和 Gamma 光照下亮度的区别

当在Gamma色彩空间下渲染时,由于提供给着色器的颜色和纹理已经有了Gamma矫正,所以计算出来的结果会被Linear色彩空间下更亮,这种区别在高亮度的区域更加明显。 而Linear色彩空间,会将Gamma矫正移除之后再进行光照计算,计算完再显示前再Gamma矫正回来,这使得光照计算更加准确,更接近现实。

Linear 和 Gamma 颜色混合的区别

上图: Linear空间混合结果<br/>下图: Gamma空间混合结果

上图是在Linear色彩空间下进行混合的结果,颜色的过渡也是线性的过渡。

下图是在Gamma色彩空间下进行混合,非线性的颜色会混合在一起使用,导致交界处甚至出现了其他颜色。


补充:在Linear色彩空间下,勾选sRGB后,图片颜色变化的原因?

下面展示一张sRGB存储的图片,是否勾选sRGB,在Unity编辑器的预览视图中的对比:

勾选sRGB不勾选sRGB

如果勾选sRGB,则预览时可以直接预览,效果一样。但如果没有勾选sRGB时,也既是标记这张图片没有经过Gamma矫正,是作为数值来传给着色器进行计算的,所以在预览图片时候,在显示时Unity编辑器会再进行一次Gamma矫正,导致显示的颜色更亮了,但如果作为数值来使用结果是正确的。

这一点我们可以在Photoshop保存一张Gamma 1.0(也即是没有Gamma矫正)的图片进行验证。

Photoshop中的设置

勾选sRGB不勾选sRGB

当我们保存线性的图片,Gamma 1.0,勾上sRGB时,Unity编辑器的预览会以为已经进行过Gamma矫正的,所以不会再进行一遍Gamma矫正,直接输出,结果是偏暗的; 当没有勾选sRGB时,Unity编辑器认为它是线性的,没有经过Gamma矫正,所以在显示时多进行了一次Gamma矫正,最终得到正确的结果。

参考

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×