gamma校正
本文参考
https://blog.****.net/w450468524/article/details/51649651
https://www.cnblogs.com/qiqibaby/p/5325193.html
学习HDR和Bloom特效的过程中,接触到了伽马矫正的问题。查阅了不少资料,这一篇讲的最清楚,下面的图片也是来自该文章。
这应该说是一个历史遗留问题,以前的CRT显示器是使用电子显像管,通过控制电流大小来控制显示屏幕上的亮度。然而亮度和电流之间的关系并非是线性的,也就是说电流强度变为2倍,显示的亮度并非是两倍,而是由公式1决定:
然而对于现实中的大部分摄像机或成像设备来讲,输入能量和记录在图片文件中的颜色亮度之间的关系却是线性的。这就导致显示器显示的图像与摄像设备捕捉的实际图像不一致,为了校正这个差异,摄像机在保存图像时会自动对数据进行一个伽马校正,如公式2:
从而还原图像原本的色彩。如下图所示:
红线表示显示器的伽马值,蓝线表示摄像机保存图片时进行的校正,紫线表示二者合成之后的结果。可以看出,显示器的gammagamma值越高,图像越偏暗。SRGB标准中,默认显示设备的gamma值为2.2。
同样,进行3D渲染时,程序内部使用的是线性的颜色,直到最后渲染结果要输出到显示器上时,我们需要对渲染结果进行公式2的校正,正如摄像机所做的那样,这样保证我们渲染的结果能够正确的在显示器上显示。但是,前提是我们加载的纹理中的数据是线性空间中的。
正如前面所说,大部分图像捕捉设备在保存图片时会自动加上伽马校正,也就是说图片中存储的是非线性空间中的颜色(gamma值为2.2时的称之为sRGB空间),如果我们在渲染时直接使用图片存储的颜色数据,然后最终输出到屏幕时,再手动进行一次伽马校正,则会导致纹理过亮,因为我们进行了两次伽马校正。所以,读取纹理数据时,若纹理颜色是sRGB空间中的,我们需要对其“反伽马”校正,转化到线性空间中来,以保证光照计算的正确。只需将glTexImage2D中的internalformat设置为sRGB,OpenGL会自动进行转换,效率比手动高得多。
需要注意的是,像法线贴图,高光贴图等通常是在线性空间中生成的的,无需进行反伽马校正。是否用sRGB格式读入纹理,要视情况而定。
gamma校正原理
假设图像中有一个像素,值是 200 ,那么对这个像素进行校正必须执行如下步骤:
1. 归一化 :将像素值转换为 0 ~ 1 之间的实数。 算法如下 : ( i + 0. 5)/256 这里包含 1 个除法和 1 个加法操作。对于像素 A 而言 , 其对应的归一化值为 0. 783203 。
2. 预补偿 :根据公式 , 求出像素归一化后的 数据以 1 /gamma 为指数的对应值。这一步包含一个 求指数运算。若 gamma 值为 2. 2 , 则 1 /gamma 为 0. 454545 , 对归一化后的 A 值进行预补偿的结果就 是 0. 783203 ^0. 454545 = 0. 894872 。
3. 反归一化 :将经过预补偿的实数值反变换为 0 ~ 255 之间的整数值。具体算法为 : f*256 - 0. 5 此步骤包含一个乘法和一个减法运算。续前 例 , 将 A 的预补偿结果 0. 894872 代入上式 , 得到 A 预补偿后对应的像素值为 228 , 这个 228 就是最后送 入显示器的数据。
如上所述如果直接按公式编程的话,假设图像的分辨率为 800*600 ,对它进行 gamma 校正,需要执行 48 万个浮点数乘法、除法和指数运算。效率太低,根本达不到实时的效果。
针对上述情况,提出了一种快速算法,如果能够确知图像的像素取值范围 , 例如 , 0 ~ 255 之间的整数 , 则图像中任何一个像素值只能 是 0 到 255 这 256 个整数中的某一个 ; 在 gamma 值 已知的情况下 ,0 ~ 255 之间的任一整数 , 经过“归一 化、预补偿、反归一化”操作后 , 所对应的结果是唯一的 , 并且也落在 0 ~ 255 这个范围内。
如前例 , 已知 gamma 值为 2. 2 , 像素 A 的原始值是 200 , 就可求得 经 gamma 校正后 A 对应的预补偿值为 228 。基于上述原理 , 我们只需为 0 ~ 255 之间的每个整数执行一次预补偿操作 , 将其对应的预补偿值存入一个预先建立的 gamma 校正查找表 (LUT:Look Up Table) , 就可以使用该表对任何像素值在 0 ~ 255 之 间的图像进行 gamma 校正。