假设您正在使用RGB颜色:每种颜色都用三种强度或亮度表示。您必须在“线性RGB”和“ sRGB”之间进行选择。现在,我们将通过忽略三种不同的强度来简化操作,并假设您只有一种强度:也就是说,您仅处理灰色阴影。
在线性颜色空间中,您存储的数字与其表示的强度之间的关系是线性的。实际上,这意味着如果将数字加倍,强度(灰色的亮度)就会加倍。如果您想将两个强度加在一起(因为您是基于两个光源的贡献来计算强度,或者是因为您要在不透明对象的顶部添加一个透明对象),则可以通过添加两个数字在一起。如果您要进行任何类型的2D混合或3D着色或几乎任何图像处理,那么您希望在线性色彩空间中获得强度,因此您可以对数字进行加,减,乘和除运算,从而对强度产生相同的影响。除非您为所有内容添加额外的权重,否则大多数颜色处理和渲染算法只能在线性RGB下提供正确的结果。
听起来确实很容易,但是有一个问题。人眼对光的敏感性在低强度下要比在高强度下好。就是说,如果列出所有可以区分的强度,那么暗的比亮的更多。换句话说,与使用浅灰色阴影相比,您可以更好地区分深灰色阴影。特别是,如果您使用8位来表示强度,并且在线性颜色空间中执行此操作,则最终会出现太多的浅色阴影,而没有足够的深色阴影。在黑暗区域中出现条带,而在明亮区域中,您浪费了用户无法分辨的接近白色的不同阴影。
为避免此问题,并充分利用这8位,我们倾向于使用sRGB。sRGB标准告诉您要使用的曲线,以使颜色变为非线性。曲线的底部较浅,因此可以有更多的深灰色,而顶部则较陡峭,所以您的浅灰色较少。如果将数字加倍,则强度会增加一倍以上。这意味着,如果将sRGB颜色加在一起,最终结果将比应有的要浅。如今,大多数监视器将其输入颜色解释为sRGB。因此,当您在屏幕上放置一种颜色或将其存储在每通道8位的纹理中时,请将其存储为sRGB,以便充分利用这8位。
您会注意到我们现在有一个问题:我们希望颜色在线性空间中处理,但存储在sRGB中。这意味着您最终在读取时进行了sRGB到线性的转换,而在写入时进行了线性到sRGB的转换。正如我们已经说过的那样,线性8位强度没有足够的暗度,这会引起问题,因此还有一个更实用的规则:如果可以避免,则不要使用8位线性颜色。遵循8位颜色始终是sRGB的规则已成为惯例,因此,您需要在将强度从8位扩展到16位或从整数到浮点的同时进行sRGB到线性的转换。同样,完成浮点处理后,在转换为sRGB的同时缩小到8位。如果您遵守这些规则,
在读取sRGB图像时,如果需要线性强度,请对每个强度应用以下公式:
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曾经内置了非线性功能。如果将像素的电压加倍,则强度将增加一倍以上。每个监视器有多少不同,该参数称为gamma。此行为很有用,因为这意味着您可以得到比灯光更多的暗度,但是这也意味着您无法分辨出用户CRT上的颜色有多亮,除非您先对其进行校准。伽玛校正意味着要转换您开始使用的颜色(可能是线性的),并针对用户CRT的伽玛对其进行转换。OpenGL来自这个时代,这就是为什么它的sRGB行为有时会有些混乱的原因。但是GPU供应商现在倾向于使用我上面描述的约定:当您在纹理或帧缓冲区中存储8位强度时,它是sRGB,而在处理颜色时,它是线性的。例如,在OpenGL ES 3.0中,每个帧缓冲区和纹理都有一个“ sRGB标志”,您可以打开它以在读写时启用自动转换。您根本不需要显式地进行sRGB转换或伽马校正。