先上图
此图出于LearnOpenGl CN。原文可能由于翻译的关系,导致内容其实并不好理解。翻阅了不少资料后(其实就是冯乐乐老师的入门精要,逃...),想对gamma矫正做一个简单的总结。
先剖析一下这张图的含义(此图其实不是重点)。首先,横轴代表,给计算机提供的颜色的物理强度rgb(图片上储存的信息或是shader的输出),纵轴是计算机实际会为这个值提供多少物理强度
中间直的小点虚线代表,你给计算机提供多少,计算机就用多少(实际不会这样)。
下弯的线表示,计算机会使用更低的数字替代你的输入(这是计算机实际的工作方式),在crt时代,这个偏低是利用显示屏硬件上的缺陷自动进行的,而现在这个偏低是故意的。
故意的原因在于数码摄像器材的8位每单位输出为了保存更多的暗部信息,会对实际捕获到的物理强度进行上弯encoding后再保存,以使得256个梯度中有更多的梯度用于表示暗部的细节,计算机在显示照片时为了平衡这个上弯的动作,就需要有一个下弯的decoding。
也就是说,你给计算机提供的颜色值,显示屏在显示前会无脑的下弯。不论你显示的是一张真实拍摄的图片还是在显示屏上制作的图片,又或者是,给他提供的基于物理模拟的渲染结果。
真实的图片由于这套机制就是给他量身定做的,下弯后,正常。显示屏上制作的图片由于输出输入在相同的下弯空间内,所以也正常(如果硬要扣的话,可以想象如下情景。你想要一个实际0.2的红,但其实计算机告诉你他是0.5的,而实际显示出来的时候,计算机又tm把他当作0.2处理...计算机在私底下欺骗了你两次,圆掉了这个谎言)。而当你把物理模拟的结果直接给计算机用于显示的时候,结果就是错误的了(下弯了)。
所以基于物理的渲染器会像摄像机器材做的那样上弯自己的输出,以平衡马上会受到的下弯。同时,这其实也是对自己输出的一个保护,因为帧缓冲一般也是8位的。(可以把渲染想象成拍照..渲染结果同样要保存到一张图片(帧缓冲)中),直接把渲染结果往里塞,同样会失去很多暗部的细节。当然如果开启hdr模式,帧缓冲的精度会扩大,暗部细节可以随意保留,但最后为了平衡显示器下弯,还是需要做一次上弯。
渲染器的输出是正常了,输入呢?
由于我们知道贴图素材其实被上弯过的(不论是被摄像器材还是被计算机欺骗了的隐式上弯(0.2的红被你当成0.5的)),所以在基于物理模拟时,采样这些贴图前就需要提前进行下弯,让他们持有正确的物理信息。unity中的lieanr/gamma设置就是设置上述内容的。当选用linear设置并且采样sRGB的贴图时,会使用下弯操作(贴图也可以不设置sRGB来拒绝被下弯,但是你得保证你的贴图数据真的不需要下弯,比如它是一张高精度的线性颜色空间的图片,又或者是一张法线贴图,法线贴图是你眼睛调出来的吗,笑....),输出渲染结果时,会上弯,随后显示屏下弯。你就能看到正确的物理效果。
那为什么需要设置呢,无脑linear不就好了吗。因为自动对输出进行gamma矫正是硬件行为,且在大多数移动端设备都不支持...所以,移动端上,其实可以自己手动实现一个linear的操作。不嫌麻烦的话,可以在每个fs输出的时候,进行上弯操作(贴图方面,并不确定是不是都支持SRGB的纹理格式,不支持的话就需要手动下弯),但不推荐之后再进行alpha混合(用上弯后的颜色混合会导致错误的物理结果)。比较简便的方法是使用后处理,把渲染结果输出到一个高精度的texture上再做色调映射+上弯(如果仍然是8位的话,还是需要先上弯保护暗部细节,等于还是要在fs里写,所以需要高精度的RTT),显然这就对性能有消耗(前一种方法累,但是省了一定的性能,同时透明变得很尴尬,后一种方法简便直观,除了高精度RTT的消耗其他没有副作用,而且很多情况下都需要高精度RTT,所以也可以认为没有额外消耗)