浏览量:2,260

伽马校正

本篇文章谢绝转载,也禁止用于任何商业目的。

首先,解释下伽马(Gamma)是什么鬼。

以一个数字照相机为例,光子打到传感器上,显现出图像,如果有双倍的光子打到传感器,就会有双倍的电子信号,它是一个线性的关系。但是,人眼对光的感知并不是一个线性的关系,与照相机相比,人眼对暗色调会更加敏感些,使得人眼能感知的光照范围更加广(我也不知道为什么!!!)。如图1所示[1],图(1)人眼观测到的50%亮度的,实际上只有百分之二十多;图(2)是照相机捕获的50%亮度的颜色,明显比图(1)更亮。

2016-5-13 23-05-22

图1. 人眼观测到的与照相机检测到的亮度对比[1]

这与伽马又有什么关系呢?伽马建立起了一个照相机捕获的亮度与人眼观察到的亮度的对应关系,称之为伽马编码。设输入和输出的光照分别表示为\({{L}_{in}},{{L}_{out}}\),伽马系数为\(gamma\),得到关系等式(1),等式(1)的函数示意图如图2所示。\(gamma\succ 1\)的函数变化曲线如图中的红色曲线所示,\(gamma\prec 1\)的函数变化曲线如图中的蓝色曲线所示。

\({{L}_{out}}={{L}_{in}}^{gamma} \tag{1}\)

2016-5-13 23-05-33

图2. 幂函数的示意图

因为人眼对暗色调(Dark Tone)更敏感,我们在存储图像文件的时候,对颜色进行伽马编码(Gamma Encoding),如图3所示[1],伽马编码产生的颜色带中暗色范围更宽。以相同的位数来表示颜色(例如8位),线性编码(Linear Encoding)可能需要更多的位数才能表现与伽马编码相同的效果。

2016-5-13 23-05-45

图3. 以2.2伽马系数进行编码的颜色表对比图[1]

伽马编码可以使得8位就能有效的描述绝大部分场景,但是它使得图像的存储和呈现过程变得更加复杂。(1)图像的存储过程,颜色通过系数为\(1/gamma\)的幂函数进行处理;(2)图像的显示,存储的颜色,需要通过显示器采用系数\(gamma\)的幂函数进行处理,即与图像存储相反的过程,它是显示设备内置的处理过程,我们可以把它理解为所有的显示设备都会行进伽马编码的逆处理。如图4所示,(1)图像伽马处理,把图像保存为JPEG或TIFF,(所有的JPEG格式的文件都会进行伽马编码[3])会采用系数为\(1/gamma\)的幂函数进行预校正,使得指定的位数有效的表示图像;(2)显示伽马处理,是显示设备对颜色进行的校正,两个过程中和后能有效的呈现的颜色。绝大部分显示设备采用的伽马系数为2.2。

2016-5-13 23-05-55

图4. 伽马编码和显示[1]

如果图像伽马处理与显示伽马处理不能达到中和,就会使得呈现出来的图像过暗或者过亮。如图5所示,蓝色表示图像伽马处理曲线,红色表示显示伽马处理曲线,紫色表示两者叠加处理后的函数曲线图,不同的中和结果就会产生不同的效果。

2016-5-13 23-06-11

图5. 设图像伽马系数为2.2,与不同的显示伽马系数中和作后呈现出来的效果对比图[1]

至此,我们知道,伽马是什么,为什么需要伽马编码,不同的伽马处理对呈现出来的图像的影响。前面阐述的是伽马编码在摄像机(照相机)从现实生活中捕获图像数据,再呈现到显示设备上影响。而在实时渲染的过程中,图像数据的捕获过程就相当于GPU的渲染过程,它可能没有涉及伽马编码,但是显示设备仍然会偷偷滴地帮我们完成伽马编码的逆处理,那么它会引发什么样的问题呢?很多时候,我们渲染出一帧的图像,都会通过显示设备调整渲染出的效果至到它符合我们预期,其实这个过程也考虑到了显示设备隐藏的逆处理,虽然它调试的显示器上显示正常,可能在不同的伽马系数的显示器就会有不同的呈现效果,而且有些情况,我们需要在渲染上对输出到显示设备的帧数据进行校正,特别是有大量动态范围光照的渲染中。这里引入一个概念,伽马校正Gamma Correction[2,4,5],它是对显示设备对最终输出颜色逆处理逆处理过程,没有写错,是逆处理的逆处理。

举个例子来阐述下伽马校正的原理,设颜色\(c=\left( 0.5,0,0 \right)\),显示设备的伽马系数为\(gamma=2.2\)经过显示设备的幂函数处理,实际上我们看到它呈现出来的颜色为\({{\left( 0.5,0,0 \right)}^{2.2}}=\left( 0.218,0,0 \right)\)。但是如果在输出到显示设备前,对它进行伽马校正,即\({{\left( 0.5,0,0 \right)}^{1/2.2}}=\left( 0.730,0,0 \right)\),那么,我们在显示设备上实际看到的颜色为 \({{\left( 0.730,0,0 \right)}^{2.2}}=\left( 0.5,0,0 \right)\),这就是我们真正想表现的颜色。

Vries [2],Gritz等[3],Akenine-Möller[5]都有介绍伽马校正,首先我们要知道伽马的存在,然后在图形渲染,特别是光照计算中,就要留意它可能对呈现出的结果造成的影响。

理解伽马的原理的目的,就是为了使我们能采用合适的伽马校正,达到符合预期的渲染效果,这里再引入一个概念,颜色的线性(Linear)变换和非线性(Nonlinear)变换。设输入值\(x\),经过处理,得到输出值\(f\left( x \right)\),对于线性变换,存在等式:

\(f\left( x+y \right)=f\left( x \right)+f\left( y \right),f\left( c\cdot x \right)=c\cdot f\left( x \right) \tag{2}\)

如果是非线性变换,则等式不成立。如果输入的纹理采用了伽马编码,即它经过幂函数的处理,那么这张纹理上的颜色变化是非线性的,当把它放入Shader处理时,又把它当作线性颜色来处理,则渲染的结果会是错误的,Gritz等[3]阐述了这种错误的处理方式引发的问题。因为,解决伽马引发的问题,需要注意两点:

(1)如果输入的纹理是伽马编码方式存储的,那么把它放入Shader计算前,采用伽马编码的逆处理,保证输入的纹理颜色是线性的;

(2)对于即将输出到显示器的像素颜色,对它进行伽马校正处理。

最后,介绍OpenGL对伽马校正的支持[2],OpenGL规范提供了关于伽马的两个扩展,分别为GL_EXT_framebuffer_sRGB[6]和GL_EXT_texture_sRGB[7]

GL_EXT_framebuffer_sRGB扩展是一种简单粗暴的做法,它把所有即将输入到颜色缓冲区的颜色做伽马系数\(gamma=2.2\)(绝大部分的显示设备采用的伽马系数)的伽马校正,即幂指数为\(1/2.2\)的函数处理,保证颜色缓冲区中的颜色数据一定是经过伽马校正的结果,调用方法就一行代码:

glEnable(GL_FRAMEBUFFER_SRGB);

2016-5-13 23-07-26

图6. 多缓冲区的渲染流程

这种做法存在一个问题,我们知道伽马校正后的颜色是非线性的,有些渲染算法需要多通道渲染,会采用多个颜色缓冲区,即输入到颜色缓冲区中的颜色并不是最终输出到显示设备的颜色,如图6所示。缓冲区1~n得到的颜色是非线性的,当各个缓冲区中的颜色再计算的时候,又把这些颜色当做线性的处理,就会引发错误。因此,另外一种做法,是在片断着色器中,对即将输出到显示设备的颜色做伽马校正,由自己来接管伽马校正[2]

void main()
{
    // do super fancy lighting 
    [...]
    // apply gamma correction
    float gamma = 2.2;
    fragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}

接下来一个扩展是GL_EXT_texture_sRGB,绝大部分纹理的设计者并不会知道伽马的存在,他是基于显示器的显示来设计纹理图案的,该扩展完成的功能是对输入的纹理采用指数为\(2.2\)的幂函数处理,使得输入到着色器的纹理是线性的,基本的代码调用如下所示:

glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

网上[1,8]实现了采用伽马校正的示例DEMO,虽然该示例无法有效论证伽马校正对改进渲染结果的作用,但是介绍了利用OpenGL实现伽马校正的方法,可以从[8]提供的链接下载到示例代码,这里不再赘述。最后,给一个采用伽马校正和未采用伽马校正的效果图示例,图中的颜色是经过多个光源计算后得到的。

2016-5-15 17-50-04

 

图7. 伽马校正和未经过任何处理的对比效果图[2]

参考

[1] Cambridge In Colour. “UNDERSTANDING GAMMA CORRECTION.” website <http://www.cambridgeincolour.com/tutorials/gamma-correction.htm>.
[2] Joey de Vries. “Gamma Correction.” website <http://learnopengl.com/#!Advanced-Lighting/Gamma-Correction>.
[3] Larry Gritz, and Eugene d'Eon. "The importance of being linear." GPU Gems, vol.3, col.4, 2008. website<http://http.developer.nvidia.com/GPUGems3/gpugems3_ch24.html>
[4] Wikipedia. “Gamma Correction.” wesite <https://en.wikipedia.org/wiki/Gamma_correction>
[5] Tomas Akenine-Möller, Eric Haines, and Naty Hoffman. Real-time rendering. CRC Press, 2008.
[6] GL Specification. “EXT_framebuffer_sRGB”. website<https://www.opengl.org/registry/specs/EXT/framebuffer_sRGB.txt >.
[7] GL Specification. “EXT_texture_sRGB”. website< https://www.opengl.org/registry/specs/EXT/texture_sRGB.txt>.
[8] Gihub. “LearnOpenGL.” website<https://github.com/JoeyDeVries/LearnOpenGL>.

spacer

Leave a reply