当我们计算出场景中所有像素的最终颜色以后,我们就必须把它们显示在监视器上。过去,大多数监视器是阴极射线管显示器(CRT)
。这些监视器有一个物理特性就是两倍的输入电压产生的不是两倍的亮度。输入电压产生约为输入电压的2.2
次幂的亮度,这叫做监视器Gamma
。
第一行是人眼所感知到的正常的灰阶,亮度要增加一倍(比如从0.1到0.2)你才会感觉比原来变亮了一倍。然而,当我们谈论光的物理亮度,比如光源发射光子的数量的时候,底部(第二行)的灰阶显示出的才是物理世界真实的亮度。物理亮度和感知亮度的区别在于,物理亮度基于光子数量,感知亮度基于人的感觉,比如第二个灰阶里亮度0.1的光子数量是0.2的二分之一),但是由于这与我们的眼睛感知亮度不完全一致(对比较暗的颜色变化更敏感),所以它看起来有差异。
一个现象就是监视器会对线性颜色作2.2
次幂处理,导致实际看到的颜色会比输出的暗。
Gamma校正
的思路是在最终的颜色输出上应用监视器Gamma
的倒数,监视器最终会显示出我们在应用中设置的那种线性的颜色。
// 1、每个后续的绘制命令里,在颜色储存到颜色缓冲之前先校正sRGB颜色。
glEnable(GL_FRAMEBUFFER_SRGB);
// 2、
fragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
sRGB textures
当我们基于监视器上看到的情况创建一个图像,我们就已经对颜色值进行了gamma校正,如果在渲染中又进行了一次gamma校正,就会二级校正,导致图片变亮。
OpenGL
给我们提供了另一个方案来解决我们的麻烦,这就是GL_SRGB
和GL_SRGB_ALPHA
内部纹理格式。自动把颜色校正到线性空间中,这样我们所使用的所有颜色值都是在线性空间中的了。
diffuse
纹理,这种为物体上色的纹理几乎都是在sRGB
空间中的。而为了获取光照参数的纹理,像specular
贴图和法线
贴图几乎都在线性空间中,所以如果你把它们也配置为sRGB
纹理的话,光照就坏掉了。