很多文章已经介绍了伽马矫正是怎么一回事了,这里我就只介绍下,我实际工程中怎么用的。顺便安利一下一篇伽马矫正的博客,我觉的这个举的例子就挺通俗易懂的,特别是关于混合那块https://www.jianshu.com/p/e15932c40bea
首先常用的姿势就是将采样用的图片都设置成SRGB,这样硬件就能将图片转换到线性空间进行计算,然后在渲染流程的最后一步加一个全屏的伽马矫正,将所有线性空间的颜色转换到伽马空间。以下是颜色转换到线性空间和转换到伽马空间的公式
linearColor = pow(color, 2.2);
gammaColor = pow(color, 1.0/2.2);
我用农民点的说法解释一下上面这个操作,首先你有一张你觉得“看起来颜色没有问题的图片”,事实上它已经被伽马矫正过了,所以显示器上看色彩完全正确, 你采样完图片之后获得一个像素颜色,像素的RGBA值在[0.0, 1.0],你用pow(color, 2.2)的做法将它手动转成SRGB格式,然后进行光照计算混合等一系列操作,最后对这个操作执行pow(color, 1.0/2.2),就完成了所谓的伽马矫正。
视觉效果上看就是,当你执行pow(color, 2.2)的时候整张图片会变暗(可以理解成两个小数相乘值变小,所以会偏黑),最后的pow(color, 1.0/2.2)会让图片变亮。如果不进行一系列光照混合等操作时,直接pow(color, 1.0/2.2)等同于复原回它原来的样子。而如果有光照计算等混合操作时,相当于它是在线性空间进行计算,这是个正确的做法。至于为什么要在线性空间进行混合处理,而不在伽马空间进行混合处理,可以看我上面安利的那篇博客,它最后有进行介绍
坑
在实际游戏开发中,它其实带来了很多坑,由于一些硬件不支持,使得我们没办法用SRGB格式的贴图,那么我们只能在shader里手动转换到线性空间,也就是shader里执行pow(color, 2.2)
然后是硬件不支持,只能用R8G8B8A8去表示颜色,没办法用R16G16B16A16,后者明显比前者有精度上的优势。当我们在shader里将值转到线性空间进行计算时,只要shader还没结束,一切都岁月静好,一旦shader结束颜色就会被存到贴图里。这有什么问题吗,问题就在于存到R8G8B8A8的贴图里的时候,它只能表示0-255的颜色,也就是说它其实只有255种颜色,精度很有限,而shader里它是个浮点数,可以人为控制其精度。而且别忘了,它执行了pow(color, 2.2),也就是说如果最后值太小,会被直接认为是0进行截断。所以当你用一张深色的光晕图片先渲染一遍到线性空间,再渲染一遍转到伽马空间,就会出现很严重的色斑。效果就跟TeamViewer远程桌面,看到的锁屏界面的颜色差不多,就像这个样子。这个我没想到太好的解决方法,要么用R16G16B16A16的格式,要么别用太暗的图片(对美术要求太高),要么牺牲掉线性空间的混合,在shader最后转换到伽马空间,去掉最后一步的全屏伽马矫正。
如果采用最后做全屏伽马矫正的话,像素填充率会比较高,毕竟遍历了屏幕的所有像素,手机上可能会有性能问题。所以一般都建议直接shader里完成当场转换到线性空间,shader结束转换回伽马空间