作者:i_dovelemon
日期:2016-04-10
来源:http://www.nutty.ca/?page_id=352&link=gamma_correction,CSDN
主题:Gamma Correction, Shader
引言
最近在研究PBR(Physical-based Rendering)相关的东西。所以看了几篇关于线性颜色空间相关的文章,同时了解到了一个如何保证我们的shader计算出来的颜色会忠实的被显示器反应出来的概念-Gamma Correction。在网上搜索相关的内容,发现这篇文章讲解的很通俗易懂,所以翻译出来分享给大家!!!
未进行gamma修正和进行gamma修正的渲染
介绍
当你在你的显示器上看一张图片的时候,你所看到的像素实际上是被修改过的,为的是能够更加符合我们人类的视觉体验。人类对于阴影部分更加的敏感,而对于中间色调和高光色调相对来说就不是那么的敏感。硬件生产商们通过对软件传输过去的颜色值释加一个指数曲线,使得颜色并不是呈现线性的变化。而这个具体的指数值就被称之为gamma值。也真是由于显示器生产商做的这个“人性化”的操作,使得我们这些图形游戏开发者想要产生我们预期的正确的图像就需要考虑这个问题。而这篇文章的目的就是为了向大家讲述什么是gamma
correction以及如何进行gamma correction。
Gamma是什么?
Gamma是一种指数曲线,显示器用这个指数曲线来调整真实输出到显示屏幕上的颜色值,以此更好的适应人类的视觉体验。
上面的图反应了一个具有2.2gamma值的指数曲线。上面的曲线能够反应出实际上显示器显示出来的值普遍要比我们真真传递给它的值要暗的多。比如说,当我们传递给显示器强度值为0.5的像素的时候,输出的实际上并不是这个强度的值。0.5^2.2 = 0.21,比输入的强度值的一半还要小。从另外一个角度来看这一条曲线,我们可以发现在这个曲线中50%的颜色强度都低于21%,只有27%的颜色强度高于50%。我们真的是非常喜欢阴影。为了计算出输出的颜色强度值,显示器通过给传递进来的颜色值施加一个指数函数。
O = i^gamma
上面的O就是显示器最终显示的颜色,i就是我们软件传递给显示器的颜色,gamma就是显示器所使用的gamma指数值。
需要注意的一点就是并不是所有的显示器都会使用2.2的gamma值。另外一些常用的gamma值像2.4,2.0,1.8等等。如果能够使用正确的gamma值更好,但是大部分情况下,我们使用2.2的gamma值就已经能够满足要求了。如果还是不能够满足,那么你就只能够让用户自行的调节显示器的显示设定。如果你能够在你的游戏中添加一个类似的小工具,让用户仅仅在你的游戏中的使用gamma修正,这样就不会演影响到用户在桌面系统上的使用了。一旦你获取到了合适的gamma值,你就能够在你的shader中计算出正确的颜色值出来。
Gamma Correction是什么?
Gamma是显示器用来修改我们输入的颜色值的操作,那么gamma correction就是逆转显示器修改的过程,从而能够让我们这些图形开发者在一个真实的线性RGB空间中进行光照和着色。比如说,我们想要一个物体的光照强度是0.5,那么我们不会直接保存0.5作为它的光照强度,而是保存0.5^1.0/2.2 = 0.73。当我们将这个0.73的值发送给显示器之后,显示器会在这个值的基础上施加一个gamma函数,也就是0.73^2.2 = 0.5,而这个值就是我们希望显示器显示的光照强度值。为了实现这样的效果,你就需要施加一个翻转gamma函数。
O = i^1.0/gamma
上面的O就是进行gamma correction之后的颜色值,i是输入的颜色值,gamma是显示器所使用的gamma值。
添加上面的一个函数之后,会产生如下所示的一条曲线。
=
上图中的蓝色线条表示的是我们希望施加的翻转gamma correction曲线。然后当我们施加了这条曲线之后,将结果发送给显示器,显示器又会在这个的基础上施加他的gamma函数,也就是上图中红色的线,最终的结果就变成了中间绿色的那条直线,也就是得到了一个线性的颜色变化。如果不进行这个gamma修正的操作,那么显示出来的颜色值要比真实的线性空间RGB值要暗的多。最重要的是它并不是我们的shader计算出来的颜色值。
下面的图例,展示出来从0-255之间的灰度变化:
不正确的渐变
正确的渐变
上面图中不正确的灰度渐变图就是我们的显示器呈现给我们的。当我们进行了gamma修正之后,线性的渐变变化就会呈现出来。当我们渲染图像的时候,我们实际上是需要一个线性的颜色空间进行工作,但是颜色图像并不是线性的。也就是说,A+B != C。所以在处理贴图的时候,我们首先需要将贴图变得线性化。为了实现这一点,我们必须要施加一个gamma函数(不是gamma修正函数),然后我们才能够正确的对它进行操作(译者注:想象一下,我们在Photoshop软件中绘制出了一张贴图,而这张贴图是以我们的观察为基准来实现的,也就是说如果贴图的颜色亮度我们观察的结果是0.5,那么实际上保存的值是0.73,为了能够让图像变成真实的我们所观察颜色的数据,我们需要对贴图中的数据施加一个和显示器gamma函数一样的函数,这样就能够保证我们后续使用的这个贴图就是我们所观察到的颜色的值,而不是显示器修改过后的值)。一旦我们使用贴图渲染好了场景图像之后,我们在施加一个翻转的gamma
correction函数,得到一张gamma修正过后的图像。接着这个图像又会被显示器进行调节,从而真实的呈现出我们想要的结果出来。
如何实现Gamma Correction?
最好的,也是最健壮的实现gamma修正的方法就是通过使用一个post-processing shader来实现。这个shader将最后渲染好的图像当作一张贴图,然后在这个贴图的基础上计算翻转的gamma correction过后的颜色值,再传递给显示器。这样就需要我们将场景渲染到一个贴图上去,然后发送给gamma correction的shader。如果对性能比较担忧的话,我们也就将gamma correction的操作放在其他的什么地方进行,这样能够从一定程度上提高性能,但是却失去的灵活性。
同样的我们还要记住,我们需要对外部输入进来的贴图施加额外的gamma函数,从而确保在shader中使用的是呈线性变化的贴图颜色值。但是需要注意,并不是所有的贴图都需要经历这个过程,一些自动产生的图像像法线贴图之类的就不需要额外添加gamma函数,仅仅是被gamma编码过的贴图需要额外施加一个gamma函数。
Gamma调节
下面的测试能够用来获取显示器的gamma值。这个测试工具使用一张具有黑白相间和一条经过gamma correction之后的灰色线条的图像。一半的线条是黑色的,另外一半的线条是白色的,所以显示器的平均输出值为50%。50%的RGB值即为128,但是128是一个线性的颜色值。我们需要找到gamma修正过后的颜色值,也就是0.5^1.0/2.2 = 0.729 * 255 = 186,这个值就是中间的灰色方块。当你站在显示器几米之外去看这张图像,如果你的显示器的gamma值是2.2的时候,你就会发现这张图片只有一种颜色。
Gamma 2.2显示器测试图像
为了测试其他的gamma值,你需要将这张图片的中间灰色的方块的颜色值,使用新的gamma值计算出来的颜色替代。这个测试恐怕很难在LCD屏幕上面进行,因为这种显示器会随着观察角度的不同,而呈现不同的变化。同时还要注意不能够过滤这张图像,因为会破坏这个测试。
你没有办法改变一个显示器的gamma值,但是你能够通过调节shader中的计算,使得我们能够产生出一个比较令人满意的灰度变化。这种调节方式需要玩家有一定的辨识能力。也就是说,当我们把一个白色的线条和一条黑色的线条放在一起的时候,玩家需要能够看得出来。对于某些人或者某些糟糕的显示器,这可能是一个非常艰巨的任务。我们往往会将RGB值分成10个亮度变化。
这是一个非常简单的渐变。用户应该能够很容易的识别出来。如果上面的任何一个很难辨别出来,那么用户就需要调节以至于能够很清晰的识别出这10种阶度变化。