PISCOnoob

导航

Gamma and Linear Space(色彩空间)

写在前面:

本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。

由于本人水平有限难免出现错误,还请评论区指出,多多指教。

部分图元和素材来源于网络,如有侵权请联系本人删除。

参考资料与链接会在文章末尾贴出。

=======================================================================

linear space和gamma space是什么?
那么这不同的色彩空间到底是什么呢?
首先,在物理世界中我们期望光的强度增强多少,亮度就相应线性增长,但是在显示器中却并不是这样的线性关系。比如一种古老的显示器(阴极射线管)显示图像时,电压增加一倍,但是亮度并不会相应增加一倍,简单来说,输入输出并不成线性关系,而是亮度增加量呈电压增加量的2.2次幂的非线性关系:


2.2则是该显示器的gamma值,现代显示器的gamma值也差不多是2.2。


将gamma2.2下曲线和线性空间比较可以看出暗部(假设小于0.5的部分)占据的数值更多。这样一来,即便经过线性空间计算得出的值,在显示器上也会更暗,这个2.2次幂(不同显示器可能不同,但差不多)也称之为display gamma。

为了解决这个问题我们可以在把图像存到储存空间之前,先把图像计算1/2.2(≈0.45)次幂,这个操作也被称为Gamma Correction,此时该图像存在于sRGB空间(后面会继续提到),屏幕显示时再计算2.2次幂,如此来得来原本想要的颜色。

也可以在图像设备显示之前我们做一个操作,把显示器的问题平衡掉:


这个1/2.2≈0.45也被称为encoding gamma(与上面的2.2次幂互为逆运算),最后的颜色就跟物理空间的一致了。所以一个完整的图像系统需要两个gamma值,encoding gamma和display gamma的乘积就是整个图像系统的end-to-end gamma,但是有的理论标准乘积是1有的是1.125,这就需要大家去做更深入的研究了。

其次,对于我们人眼来说,我们对光的敏感度也并非线性的,我们对暗部细节的敏感度高于对亮部细节。比如在全黑的房间里加一盏灯,两盏灯的效果远强于在1000盏灯的房间再加一盏灯。有人研究过发现,人眼认为的中灰其实不在亮度为0.5的地方,而是在大约亮度为0.18的地方,这观感也跟2.2次幂下曲线非常相像。

然后,什么是sRGB呢?1996年,微软和惠普一起开发了一种标准sRGB(standard Red GreenBlue)色彩空间,这种标准得到许多业界厂商的支持。而我们的个人电脑使用的标准就是sRGB,它使用的encoding gamma大约是0.45(也就是1/2.2)。这个值就是为了配合display gamma为2.5的设备工作的。这样,end-to-end gamma就是0.45 * 2.5 = 1.125了。所以我们可以先记住,sRGB对应的是Gamma0.45所在的空间。

最后,我们再来了解下贴图(又或者叫纹理)的相关知识。在photoshop或其他DDC软件贴图制作时,我们可以看到选项有32,24,16,8位图。32位图能存约1677万种颜色,而8位图只能储存256^3种颜色,但是因为涉及到储存空间,传输效率等因素,8位图算是移动端、页游等图像要求品质不高的平台中比较常用的选择。其次因为人眼敏感度的问题,我们对暗部细节更加敏感,看着32位图我们会认为暗部较少亮部较多,十分不自然,反之8位图十分符合我们认为的“从黑到白渐变”。实际上8位图的0.5(128,128,128)对应的是32位图的0.2左右的位置,0.2-0.5的转换相当于让纹理有了全局提亮,这个过程也就是gamma correction的,整体曲线跟gamma0.45差不多。制作贴图时一般一会选择在sRGB空间下(贴图是否sRGB空间下对后续在unity中操作十分重要)。

 

那跟渲染有什么关系呢?


通过上面的简单讲解,我们可以知道在贴图制作,导入,shader计算,显示等许多方面都涉及到了色彩空间的知识,如果忽视了这个问题,最后游戏画面的渲染效果恐怕也就很难符合我们心中所预想的样子了。尤其是近年业界的PBR风盛行,物理正确数学正确十分关键,这就要求我们在shader计算中确保是线性空间,否则经过gamma矫正后的数值再进行数学计算也是不可能正确的。当然这里要说明下,物理正确并不等于渲染画面就会好看,只是说PBR流程计算结果更加真实,且会更易于形成规范的pipeline,对大项目来说更易于管理。
我们梳理一下后列个流程图:

在unity中的相关操作(工作流)


那么我们在Unity中应该如何操作呢?
首先我们可以在编辑器edit-->projectsetting-->player-->other setting的地方看到这个选项让我们选择gamma空间还是linear空间


选择不同的色彩空间自然会有不同的工作流:

如果选择了Gamma空间,那Unity不会对输入和输出做任何处理,换句话说,Remove Gamma Correction 、Gamma Correction都不会发生,除非你自己手动实现。

如果选了Linear空间,那么就是上面说到线性空间workflow了。对于sRGB纹理,Unity在进行纹理采样之前会自动进行Remove Gamma Correction。

我们举个例子,如果sRGB纹理没有进行Remove Gamma Correction。假如我们有某个像素的值为0.1,有一个对它产生影响的光源强度是2,我们期望最终结果应该是0.2。但如果这个像素(纹理)被存储在gamma空间,经过gamma correction,那么它此时的值应该是0.1^0.45=0.355,当我们乘上光的强度我们会得到0.355*2=0.71,最终显示器输出0.71^2.2=0.47。最终结果0.47比预想0.2要亮得多。

对于Linear纹理则没有这一步,而在输出前,Unity会自动进行Gamma Correction再让显示器输出。
我们可以在import setting勾选是否sRGB纹理,显然如果linear纹理依然勾选了sRGB则会计算出类似上面例子所说的非预期的结果。


当然关于色彩空间unity还有许多细节要注意。
比如平台支持,上述unity色彩空间设置非常方便,无需我们再去手动gamma correction,有效避免错误发生。但是此设置支持的平台有限,对于太老的平台是不支持的,比如安卓平台linear rendering最低要求OpenglES3.0和andriod4.3,IOS linear rendering要求Metal graphics API,WebGL linear rendering要求WebGL graphics 2.0。
mipmap的生成也要确保原始纹理是在linear space,否则也会计算出错误的结果。

 

 

参考资料:

1.官方文档

2.TA百人计划

3.Unity Shder入门精要--冯乐乐

posted on 2022-11-01 10:48  PISCOnoob  阅读(206)  评论(0编辑  收藏  举报