Linear Rendering的意义就不用多说了,Unity的文档中说得非常清楚。
首先看看人眼对亮度感知的函数曲线。这里取最常见的参数2.2
y=power(x, 2.2)
横轴是亮度,纵轴是人眼感知到的亮度。
美术用所见即所得的方式调出来的贴图,实际像素都是被曲线所影响过的,
光照如果也在Gamma空间内计算,相当于也同样受这个曲线影响,结果就是亮部更亮。(LDR管线这个问题更加突出,因为超过1的像素都被一刀切成1了)
我们看看Unity提供的参考效果对比图:
实际在项目中,如果用Gamma空间,美术调亮一点贴图,亮部就会大面积过爆,而调暗一点,暗部就会太黑。非常难以调整。
有时候美术就会调整光照环境,或者要求增加一个光源来给暗部补光。这会带来额外的性能开销,尤其Unity的Forward Rendering会增加一个ForwardAdd pass。
最关键的问题是,用一个错误弥补另一个错误是不对的,只会越来越乱。
硬件实现的LinearRendering的方法是:
1.标记处贴图是否在Gamma空间(sRGB)。
2.shader内采样sRGB时,硬件做一个pow(2.2)操作
3.在线性空间内进行光照计算
4.全部shading完成后,在LDR做pow(1/2.2),转成Gamma空间显示。
根据上面的流程,可以自己在shader中实现
1. 采完颜色贴图后,做pow(2.2)
2. 所有光源同样做pow(2,2)处理
3. 最终输出光照结果时,做pow(1/2.2)(对于LDR)
其中1可以利用Unity的AssetPostProcessor来做。变成在导入颜色贴图时(可以用命名规范来识别),逐像素做pow(2.2)。shader中在linear space做光照计算,最后补个gamma correction的后处理。
测试后发现,上述方法只适用于较亮的贴图。暗的贴图会出现严重的banding artifact。这是因为pow(2.2)之后暗部会被压缩,rgb8的精度有限,细微的变化就损失掉了。引擎中打光会提亮暗部,暴露缺陷。
所以不能直接修改贴图的像素,只能回归shader方案,进行优化。pow(2.2)可以用简单的平方代替,这样
1. shader采样sRGB后,做平方计算
2.光源亮度,色块等可以提前做好平方,传入gpu
3.补上pow(0.5)的gamma校正
效果与unity自带的linear rendering几乎一样,并且不用担心兼容性,gles2.0上也可以运行。