假设你正在使用RGB颜色:每个颜色由三个强度或亮度表示。你需要选择“线性RGB”和“sRGB”之间的一个。现在,我们将简化问题,忽略三种不同的强度,只假设你只有一种强度:也就是说,只处理灰度。
在线性颜色空间中,你存储的数字与它们所表示的强度之间的关系是线性的。实际上,这意味着如果你把数字加倍,那么强度(灰度的亮度)就会加倍。如果你想要将两个强度加在一起(因为你正在计算两个光源的强度贡献,或者因为你正在在不透明物体上添加透明物体),你可以简单地将这两个数字相加。如果你进行任何2D混合、3D阴影或几乎所有的图像处理,那么你需要的是线性颜色空间中的强度,这样你可以通过加、减、乘、除数字来对强度产生相同的影响。大多数颜色处理和渲染算法只能使用线性RGB才能得到正确的结果,除非你为所有内容添加额外的权重。
听起来非常简单,但是存在一个问题。人类眼睛对光的敏感度在低强度下更加细微,而在高强度下则更粗糙。也就是说,如果你列出所有你可以区分的强度,那么较暗的强度比较亮的强度更多。换句话说,你可以更好地区分灰色的深浅。特别地,如果你使用8位来表示你的强度,并且在线性颜色空间中进行表示,你会得到过多的亮色阴影,而较暗的区域则出现带状图案,同时在亮区域中,你浪费了许多位数来区分用户看不出差异的近白色阴影。
为了避免这个问题,并最大程度利用这8位,我们倾向于使用sRGB。sRGB标准告诉你使用哪种曲线来将颜色变成非线性的。曲线在底部越浅,就可以拥有更多的深灰色,而在顶部则越陡峭,使你拥有更少的浅灰色。如果你把数字加倍,则强度增加的不止一倍。这意味着如果你将sRGB颜色相加,最终结果会比预期的轻。如今,大多数显示器解释它们的输入颜色为sRGB。因此,当你要在屏幕上显示一种颜色或将其存储在每通道8位的纹理中时,请将它存储为sRGB,以便最大限度地利用这8位。
现在我们遇到了一个问题:我们想要在线性空间中处理颜色,但是要将其存储在sRGB中。这意味着你需要在读取时进行sRGB到线性转换,并在写入时进行线性到sRGB转换。由于我们已经说过,线性8位强度没有足够的深灰色,因此这会引起问题,因此还有一条实用规则:如果可以避免使用8位线性颜色,则不要使用8位线性颜色。现在约定俗成的做法是,8
float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);
反过来,如果你想将一张图片写成sRGB格式,对于每个线性强度值应使用下面的公式:
float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )
在这两种情况下,浮点值s的范围都从0到1,因此如果您正在读取8位整数,则首先要除以255,如果您正在写入8位整数,则最后要乘以255,与通常的操作方式相同。这就是您需要了解的所有关于使用sRGB的内容。
到目前为止,我只处理了一种强度,但是有更聪明的方法来处理颜色。人眼可以更好地分辨不同的亮度而不是不同的色调(更确切地说,它具有比色度更好的亮度分辨率),因此您可以通过将亮度与色调分开存储来更好地利用您的24位。这就是YUV、YCrCb等表示方法尝试做的事情。Y通道是颜色的总体亮度,并且使用的比其他两个通道更多的位数(或具有更高的空间分辨率)。这样,您就不需要像处理RGB强度那样总是应用曲线。YUV是一个线性颜色空间,因此如果您在Y通道中的数字翻倍,您将使颜色的亮度翻倍,但是您无法像使用RGB颜色那样将YUV颜色相加或相乘,因此它不用于图像处理,只用于存储和传输。
我认为这回答了您的问题,因此我会以一条快速的历史注释结束。在sRGB之前,旧的CRT显示器内建有非线性性。如果您为像素加倍电压,则亮度不仅会加倍。对于每个监视器,这个变量都不同,称为
伽马。这种行为很有用,因为它意味着您可以获得比光更暗的颜色,但是这也意味着除非您事先进行校准,否则您无法确定用户的CRT上颜色的亮度。
伽马校正表示转换您开始使用的颜色(可能是线性),并将其转换为用户CRT的伽马。OpenGL来自这个时代,这就是为什么它的sRGB行为有时会让人感到困惑的原因。但是现在GPU供应商往往遵循我上面描述的约定:当您在纹理或帧缓冲区中存储8位强度时,它是sRGB的;当您处理颜色时,它是线性的。例如,在OpenGL ES 3.0中,每个帧缓冲区和纹理都有一个“sRGB标志”,您可以打开它以在读取和写入时启用自动转换。您根本不需要显式执行sRGB转换或伽马校正。