伽马空间与线性空间详解
随着真实性更高的基于物理渲染(PBR)的到来,线性空间(Linear space)光照计算也越来越被经常提及。虽然线性空间和与之“对立”的伽马空间(gamma space)是简单而重要的概念,但很多开发者对它们的真正意义并不了解。这篇文档将会介绍伽马空间和线性空间、它们之间的区别以及在Unity引擎中的应用。
LINEAR SPACE
线性空间是什么?简单的说,在线性空间对数字化的颜色和光照强度进行相加相乘计算得到的结果依然能与真实的结果一致。不具备这条性质就被称为“非线性”空间。见下图:
GAMMA SPACE
伽马(gamma)的产生原因主要有两点:第一是CRT显示器的历史原因,对显示图像的处理是非线性的。第二是因为人眼对明暗色域的辨识度不一样,有时需要对图像进行非线性处理来获得合适的辨识度,这些非线性处理叫做伽马纠正(gamma correction),具体实现是对图像中的每个像素做一次幂函数运算。伽马特指这个幂函数的指数值。见下图:
上图中,很显然除1以外的伽马值都是非线性的。下图展示了用上图中的不同伽马值对图像做伽马纠正得到的结果:
重要!!!现实中绝大部分的图片都是被伽马值为0.45纠正过的,也就是说图片文件存储的都是伽马纠正过的颜色值。做伽马纠正几乎是每台相机、每个图像编辑软件在存储图像时默认要做的事。
但为什么图像很够正确显示,而不是像左图一样看起来过爆呢?这是显示器显示的时候做了非线性处理导致的。CRT显示器显示图像的时候做了伽马值为2.2的纠正,之后的LCD显示器为了兼容性也做了同样的纠正。2.2为0.45的倒数,互相抵消得到了与原始一致的图像。如果图片没有做伽马纠正,显示的图像将是像右图一样,颜色被压黑。
COLOR SPACES AND THE RENDERING PIPELINE
在实时渲染管线中,如果采用伽马空间,那么伽马纠正过的贴图在着色器(shader)中直接参与光照计算,得到的图像被显示器伽马纠正后输出显示。这个过程很简单,但物理上是不正确的。在真实世界中,光照行为是线性的,着色器中的数字化的光照计算也是线性的,而输入的贴图和颜色是非线性的,这意味说着色结果是不准确的。在越来越追求画面沉浸感和真实性的今天,伽马空间的光照计算满足不了需求。
因此,PBR使用了线性空间,贴图和颜色去除了伽马纠正转换到线性空间,再传递给着色器进行光照计算着色,之后的后期处理(post effects)也同样在线性空间下进行,最后获得的图像再做伽马值0.45纠正,以抵消显示器显示时2.2的伽马纠正。下图是渲染管线采用伽马空间和线性空间的流程和结果对比:
上图中简单球体的渲染差异可以注意到:伽马空间高光的更亮,衰减范围更大。这些都是缺乏真实性的渲染结果。
COLOR SPACES IN UNITY
Unity引擎渲染管线默认采用伽马空间,目前只有PC、 Xbox和PlayStation平台支持线性空间,可在编辑的此处进行切换:
Edit -> Project Settings -> Player -> Other Settings -> Color Space
切换到线性空间后,传递给着色器的将是无伽马纠正的贴图和颜色。如果在开发中的项目进行切换,场景看起来跟切换前会不一样,需要重新调整光照和贴图来达到想要的效果,灯光贴图也需要重新烘焙。
如果同时设置了线性空间和HDR,那Unity所有的后期处理(post effects)全将在线性空间进行。如果只设置了线性空间,那Unity仍使用伽马帧缓冲(framebuffer),但当对帧缓冲进行读或写时,Unity会自动对颜色进行纠正来保证后期处理仍然在线性空间进行。
尽管Unity默认对移动平台不支持线性空间。但可以在着色器里来做类似实现:对传进来的贴图做幂为2.2的pow()函数处理,然后在颜色返回值前做幂为0.45的pow()函数处理。这种实现有计算开销,不能滥用。
CONCLUSION
掌握伽马空间和线性空间的不同,这样在实际游戏项目中可以规避一些颜色管理上的常识性错误,从而保证画面质量。总之,线性空间的使用是获得画面沉浸感和真实性上的重要一环。