Gamma 空间是计算机图形学领域的一个很古老的传说。最近我在看 POV-Ray 的官方指南时才注意到它的存在。这篇文章是我阅读一些资料之后写的一篇笔记,其中有一些内容属于猜测,若有错误,请不吝指正。
显示器的 Gamma 值
若你的系统安装了 POV-Ray 3.7(或未来更新的版本),使用以下命令可生成图片 gamma_showcase.png:
$ povray +w640 +h480 +a0.3 +am1 +fN -d File_Gamma=sRGB Output_File_Name=gamma_showcase.png scene/gamma/gamma_showcase.pov
注:povray 会到一个默认目录中去搜索 .pov 文件。上述命令中的
scene/gamma/gamma_showcase.pov
,在我的机器上实际上是/usr/share/povray/scene/gamma/gamma_showcase.pov
。
若你不知 POV-Ray 为何物,可以参考下面这幅在我机器上生成的图片:
观察图中后面三排球体,其中每个球体,尽管它的右半球有一组平行的横向条纹,但是若轻眯着眼睛去看,左半球与右半球的颜色应该是相同的,否则就说明你的显示器的 Gamma 值需要校准。有些液晶显示器,画面的亮度会受到观察位置的影响。对于这种情况,建议在观察图中某个球体时,调整身体的位姿,让眼睛直视它。
要精确校准显示器的 Gamma 值,从事数字图像处理的专业人士需要借助价值不菲的专业设备与软件。我作为业余人士,就没这么多讲究了。在 Linux 系统中可用 X11 窗口系统提供的 xgamma 工具来校准。
xgamma 的用法很简单:
$ xgamma -gamma n.n
n.n
表示小数(浮点数),用于设定 Gamma 值,默认值为 1.0。尝试不同的 Gamma 值,直至观察上图中的球体的左右半球的颜色相同为止,此时意味着显示器的 Gamma 值被校准为 2.2。大部分现代显示器的 Gamma 值都是这个值,因为这是 sRGB 标准所规定的 Gamma 值。
有些液晶显示器可能支持硬件层面上的 Gamma 值的设定,若能通过这种途径实现 Gamma 值校准为上策。
眼见未必为实
你在显示器中观察一张照片时,大部分情况下你看到的并不是那张照片真正的样子,它真正的样子可能要比你看到的更亮一些。因为大部分数码相机与手机摄像头会对捕捉到的每个像素与一个近似于 0.45 的数值作幂运算,因而所获得的照片要比所拍摄的场景更亮。这个过程被称为 Gamma 编码。
至于为什么要对图片进行 Gamma 编码,存在两种解释。第一种解释是过去为了迎合普遍使用的 CRT 显示器的电压与屏幕亮度形成的非线性关系。第二种解释是基于人眼的生理特性提高照片的信息量。选择哪种解释是无所谓的,任何解释都不会对现实有影响。现实就是,大部分图像捕捉设备都会对捕捉到的图像进行 Gamma 编码。
当我们查看图像时,显示器会对图片进行 Gamma 解码,亦即对每个图片的每个像素与一个近似于 2.2 的数值进行幂运算,结果就将图片的亮度近似还原为所拍摄的场景的真实亮度。
对于图像的 Gamma 编码与解码这两个过程,无论缺少哪一个,最终被观察到的只是一张属于 Gamma 空间(非线性空间)的图片,结果要么是太亮,要么是太暗。若你用的数码相机未对所捕捉的图像进行 Gamma 编码,那么你可能会认为这个相机太垃圾,因为显示器并不知道你的照片是否经过 Gamma 编码,它恪尽职守,对照片进行了 Gamma 解码,结果就导致你在显示器上观察到的照片要比所拍摄的实际场景暗了许多。
幸好,生产图像捕捉设备的厂家从一开始就知道要对图像进行 Gamma 编码。不幸的是,过去有太多的编写三维游戏的程序员深受 Gamma 空间的折磨。一个典型的问题是,程序员未对光照计算后生成的图像进行 Gamma 编码处理,这相当于他们实现了一个软件版的缺乏 Gamma 编码功能的数码相机,结果导致游戏画面偏暗。另一个典型的问题是三维模型的纹理失真。因为程序员所用的纹理图片大多是经过 Gamma 编码的,亦即这些图片处于 Gamma 空间,而三维光照的计算过程是在线性空间中进行的,由于程序员未对纹理图片进行 Gamma 解码,导致光照计算过程在数学上是错误的,结果就造成了游戏画面偏色而失真的现象。此外,游戏场景中的颜色混合、纹理的 Mipmap 等环节也受到 Gamma 空间的影响而导致游戏画面失真。三维游戏场景渲染中的这些隐疾直至近年来显卡提供了 Gamma 解码与编码功能之后得以杜绝。
未能与时俱进的图像查看软件
可能有些图片格式,至少 PNG 这种图片格式支持用户自行设定 Gamma 编码/解码值。对于此类图片格式,图像查看软件应当根据图片文件中所设定的 Gamma 编码值的倒数为 Gamma 值对图片进行解码,再以用 0.45 这个 Gamma 值对图片进行编码,接下来显示器再以 2.2 这个 Gamma 值进行解码。也就是说,对于此类图片格式,无论图片文件是以哪个 Gamma 值进行编码都没有关系,反正最终都要变成以 0.45 编码,再由显示器上以 2.2 解码。
绕这么多圈子,感觉很别扭。不过,利用这些圈子能够甄别一些落后的图像查看软件。
POV-Ray 在将所渲染的三维场景输出为 PNG 图片时,可以人为设定 Gamma 的编码/解码值。例如:
$ povray +w640 +h480 +a0.3 +am1 +fN -d File_Gamma=8.0 Output_File_Name=gamma_showcase_8.png scene/gamma/gamma_showcase.pov
File_Gamma
的值被设为 8.0,这意味着显示器对图片解码时所用的 Gamma 值为 8,而 POV-Ray 对图片编码时所用的 Gamma 值为 1/8。
我在 Linux 系统中使用图像处理软件 GIMP 打开上述命令生成的 PNG 格式的图片文件 gamma_showcase_8.png,结果为:
这是错误的 Gamma 解码结果。因为 GIMP 并未使用 Gamma 值为 8 对图片进行解码,而是直接以 2.2 这个 Gamma 值进行了解码。
若用 Firefox 或其他现代网页浏览器查看 gamma_showcase_8.png,所看到的图片应该与前文中的 gamma_showcase.png 相同。若我没有猜错,这些网页浏览器对 gamma_showcase_8.png 应该是先用 Gamma 值为 8 进行解码,再以 Gamma 值为 0.45 进行编码,最后交由显示器以 2.2 进行解码并显示。
POV-Ray 的 Gamma 空间
POV-Ray 3.7 提供了 assumed_gamma
这个关键字,可以在 .pov 文件首部以 global_settings
块的形式设定 POV-Ray 的颜色空间。例如
global_settings {
assumed_gamma 1.0
}
可以让 POV-Ray 在线性的颜色空间中工作。
若想让 POV-Ray 像那些主流的图像处理软件那样在 Gamma 值(解码值)为 2.2 的颜色空间中工作,只需像下面这样设定:
global_settings {
assumed_gamma srgb
}
注:在 POV-Ray 3.7 中,若未在 .pov 文件中设定
assumed_gamma
,povray 会给出一个有些严厉的警告。
由于 POV-Ray 针对不同的平台提供了渲染结果预览功能,这相当于 POV-Ray 提供了一个图片查看器。要在这个图片查看器中看到正确解码的图像,需要在 povray.ini 文件中设定显示器内置的 Gamma 值。之所以如此,是因为 POV-Ray 是一个跨平台软件,需要尽可能支持更多的显示器类型。有的显示器的 Gamma 值并非 2.2,譬如苹果的显示器的 Gamma 值是 1.8(这也意味着面向苹果显示器的图片的 Gamma 编码值是 1/1.8)。由于我用的显示器的 Gamma 值是 2.2,所以需要在 povray.ini 文件中作以下设定:
Display_Gamma=sRGB
在 POV-Ray 看来,sRGB
就是 2.2。另外,povray 命令行参数 File_Gamma
的默认值也是 sRGB
。因此,在通常情况下,povray 命令行无需设定 File_Gamma
值。
注:在我的系统中,povray.ini 文件的位于
/etc/povray
目录。
现在有这样一个问题,在 POV-Ray 中,assumed_gamma
值是设为 1.0 好呢,还是设为 srgb
好?我的意见是将其设为 1.0,因为这是线性的颜色空间,是最符合物理世界的颜色空间。不过,采用这种设置也有一个缺点,那就是在这个空间中看到的颜色与我们在 PhotoShop 或 GIMP 之类的图像处理软件中看到的颜色是有偏差的,因为图像处理软件是在 Gamma 值为 2.2 的颜色空间中工作。这些图像处理软件往往提供了颜色拾取器,使用它们能够在屏幕中获取某个像素的颜色值。若在 POV-Ray 中使用这些颜色拾取器获得的颜色值,需要对每种颜色以 Gamma 值为 2.2 进行解码,将其转换为线性空间中的颜色值。若将 assumed_gamma
值设为 srgb
就不会有这个问题了。
结语
被 Gamma 空间绕晕了的现代人,想必会觉得先辈们在硬件受到很大限制的情况下抖擞出来的小聪明,现在发展成了大包袱。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。