起因
前段时间尝试在SP中实现unity中的效果,碰到一个问题就是我们项目的Unity使用的是gamma空间(因为低端机对于linear空间是不支持的,但这种情况下应该手动gamma校正,不过我们项目没有对颜色做任何处理),而SP只能使用linear,所以为了在SP中实现Unity的「gamma」效果,需要在SP的shader中手动反向gamma校正。
先来尝试一下一般的情况,也就是Unity设置为gamma空间,但通过在shader中手动校正,达到线性空间的效果,目标是使得linear的效果=gamma+手动校正。
原理简介
这方面找到了冯乐乐写的这篇博文我理解的伽马校正(Gamma Correction),非常详细的讲解了原理和方法。如果只是想知道怎么gamma校正,这里做一个能够被快速接受的解释。
首先看一下 真实场景 -> 摄像机捕捉 -> 显示器显示 这个过程中经过的变换
其中,display gamma是显示设备的技术客观存在的,为了校正这个误差,在图像捕捉设备中加了一个encoding gamma,使得end-to-end gamma,也就是两者的乘积,为1,这样真实场景的亮度值就等于显示器的值了。
后来,微软联合爱普生、惠普提供了sRGB标准,推荐显示器中display gamma值为2.2,那么encoding gamma的值为1/2.2。而对于渲染的人来说,并没有图像捕捉设备这一环节,但是显示器的2.2却客观存在,所以我们需要在输出显示颜色之前,使用encoding gamma,才能达到模仿真实环境,人眼看着更舒服的效果。
Unity的颜色空间设置
Unity中可以设置两种颜色模式,在Edit->Project Settings->Player->Color Space,其中gamma代表什么都不干,一般颜色会暗很多。而选择linear的话,Unity会把所有的输入颜色,包括纹理等,默认使用sRGB,先转换到线性空间,也就是pow2.2(sRGB也可以关掉,但是gamma空间下默认都是关,点击开关不会产生影响),在线性空间下做各种运算,然后在输出前pow1.0/2.2,最后交给显示器自动pow2.2,最后看到的就是线性的了。所以实际上,选择linear的话,shader输出的是gamma空间的颜色,是显示器把它变成了线性(有点绕),而选择gamma空间的话,关键不是空间错了,而是它在非线性空间下做了混合,最后显示器转成线性空间就会出问题。也就是说,如果不做混合,这两个选项的结果是一样的。
冯乐乐的文章中给出了gamma颜色模式下,颜色混合产生的问题,左边为gamma,右边为linear。
所以如果不设置成linear,颜色会变得难看不仅是变暗这么简单,有些地方还会出现明显不符合常理的颜色,比如上图交界处居然出现了蓝色。
而遗憾的是,根据Unity官方文档关于线性空间的描述,Android设备需要支持OpenGL ES 3.0和至少Android 4.3的系统,IOS需要Metal graphics API(系统至少为IOS 8),所以除非放弃这一部分用户,否则只能使用gamma,但是为了正常的颜色表现,我们需要在shader中进行手动的gamma校正。
实践
我们来试试看,将空间设置为gamma,然后手动校正,在输出前pow1.0/2.2,由于我忽略了贴图没有转到线性空间的问题,所以得到了这样的结果。
将贴图pow2.2后就使linear的效果=gamma+手动校正了
最后尝试在SP中手动反向校正,也就是使gamma=linear+反向校正,顺利使SP的效果和gamma下的Unity一致。
提高效率
Unity实际上有提供关于颜色空间转换的接口,函数名不带exact的就是近似算法
1 | inline float GammaToLinearSpaceExact (float value) |