Gamma校正与线性空间

Posted by 蔡华的博客 on November 30, 2017

前言

  • 近期看了一篇文章,主要讲了如何处理lightmap在Android平台下下偏暗的问题。引发了对gamma校正的好奇。

Gamma校正的由来和计算方式

  • 所谓Gamma校正指的是在传统的拍摄相机中会对照片中的亮度进行修正,计算公式为Lout = pow(Lin, 0.45) ,说白了就是如果某个像素的亮度为0.5,那么保存后的照片的亮度就是0.5的0.45次方,即0.732。从下图可以看到绿色的线就是经过校正后的亮度曲线。在这个坐标系中,x轴为原始亮度,y轴是校正后的数值。

image

  • 如果以原始亮度0.5作为一个分界线,很明显的看出来0.5之前的亮度在校正后占据了70%(0.732)以上的数据空间。这也是为何会有gamma校正的根本原因,即:肉眼对较暗区域的变化更为敏感,而0.5为灰度的界限,原始亮度小于0.5的部分可以认为是较暗的区域。因此将0.5之前的亮度经过校正,使得其结果的范围变大到0-0.732,这就可以进一步的放大黑暗区域的范围,从而使得照片更加真实。
  • 但是从这个曲线我们看到,除了0和1以外的像素的亮度整体的变大了,因此就出现了下图的中左边图中的现象,即经过0.45的gamma校正后图片变亮。但是为何我们最后在电视或者电脑屏幕上看到的图像基本和肉眼看到的真实亮度一样呢, 是因为显示器的硬件也会做一次gamma校正,而这个指数是2.2,即pow(0.732,2.2) ≈ 0.5035 ,和0.5有数值上的差异但是基本接近。这只是个巧合。
  • 同理,如果是原始像素的图片不经过gamma校正而直接输出到显示器,其结果就是下图中右边图的结果,也就是亮度整体变暗。这个和上图中蓝色曲线的结果也相同。计算方法也是幂计算。

image

导入unity中的图片

  • 基本上所有的绘图软件在输出的结果上都是做过gamma校正的,否则在电脑屏幕上看到的就是偏暗。
  • 因此在导入到unity后,在作用到shader时也是gamma校正后的图片。
  • 当然我们也可以在绘图软件中就输出不经过gamma校正的图,这个后面说。

unity中的颜色空间

  • unity的颜色空间就是gamma或者liner space
  • gamma选项下默认unity在shader中直接用gamma校正后的图片进行光照计算,然后输出到现实设备时硬件会做一次gamma校正,最终输出结果。
  • 线性空间选项中,unity shader会对输入的图片进行一次gamma校正(2.2),得到真正的亮度值后进行光照计算。最后在进入颜色缓存区之前做一次gamma校正(0.45),然后图像进入显示设备,再次进行gamma校正(2.2)。
  • 两者在最终结果的比对可以参考下图

image

  • 可以看出来线性空间下产生的最终渲染效果比较接近于真实。
  • 我们以一个亮度为1的点为例进行两种模式的计算,对比下结果,假设这个点的法线与点到光源的向量的夹角为60°。根据Lambert公式,此时该点的亮度(用亮度代替像素值)为Clight * Cdiffuse * max(0, dot(n,I)),为了简化计算我们认为Clight也就是光照颜色也是1, n和I都是1。
    • gamma下原始亮度是1的点,Cdeffuse为1,60°的点积是0.5,计算出来的颜色是0.5,输出到屏幕经过gamma校正(2.2)后值为0.218。
    • 线性空间下线移除gamma校正,亮度1的移除后结果也是1,60°的光照结果也是0.5,输出到颜色缓冲区线做一次校正(0.45),到屏幕做一次校正(2.2),期结果应该是0.5035。
  • 可以看出来两者在最后的结果上,线性空间的结果比较接近真实的60°情况下的亮度,这个真实值就是0.5。

线性空间工作流

  • 现在unity中移动平台已经支持线性空间,只是需要OpenGL ES3。那么如果使用线性空间就需要一套类似PBR的流程。
  • 首先就是纹理图是移除了gamma校正的。
  • 在烘焙光照贴图的时候也要移除gamma校正。
  • 后期处理的时候倒是不用额外做什么,因为unity已经对颜色值做了处理。

引用

PS

  • 开头提到的lightmap偏暗的问题,在2017.2.0p1中移动平台lightmap处理LDR时已经加入了是否是gamma的判定,然后根据结果会对LDR的亮度进行不同的补偿。