这里的理解仅供参考
伽玛校正
是线性RGB空间
和sRGB空间
(非线性)进行转化的手段
简单的理解可以把gamma ≈ 2.2 (这是近似值,简化后的,实际有些地方会做更精准的计算如:这篇文章)
对颜色值做乘方运算,其中gamma值 == 幂次,比如 ( 0.5 , 0 , 0 ) (0.5,0,0) (0.5,0,0)颜色,应用gamma矫正: ( 0.5 , 0 , 0 ) 2.2 = ( 0.217 , 0 , 0 ) (0.5,0,0)^{2.2}=(0.217,0,0) (0.5,0,0)2.2=(0.217,0,0),gamma = 2.2
我们为什么要做伽马矫正?
- 所有显示器都会自动进行gamma = 2.2的校正,即在你所定义的颜色的基础上 做2.2次幂的乘方运算。
- 比如我在程序中输出颜色
(0.5, 0, 0)
,在显示器上其实是经过显示器矫正后的颜色(0.217, 0, 0)
,偏暗! - 为了消除这种影响,我们要对我们游戏中定义的颜色做gamma = 1/2.2 矫正。我们想要显示器输出的是
(0.5, 0, 0)
,就必须进行伽玛校正,先对颜色做1/2.2的乘方运算,再把颜色给显示器,它会做2.2乘方运算,结果就还是(0.5, 0, 0)
- 过程看下图
一个容易出错的点:
对这句话理解: 显示器输出的颜色是经过gamma2.2矫正后的颜色,现在显示器输出的是(0.5,0,0)
,半红色。艺术家创建纹理的时候,不知道他是0.5R啊,靠感觉为了跟这个颜色一样,他在RGB空间把颜色值调到了(0.729,0,0)
,诶现在感觉两个颜色看起来一模一样了美滋滋,但是这样的后果就是,艺术家定义的这个纹理颜色0.729是在非线性空间的(sRGB空间)。我们在程序中,拿到这个纹理,按照之前所说的 会先进行gamma1/2.2矫正,会得到得到0.866R,然后显示器默认2.2矫正后,显示的颜色是(0.728,0,0)盖了帽了,颜色偏亮!
解决办法:
- 让艺术家们调颜色的时候,调到正确的颜色后,应用2.2gamma矫正后,再把纹理给我们
- 艺术家们不调,我们自己调,对采样到的颜色做2.2矫正,变到线性空间后,再进行后面的操作
vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));
opengl提供的解决方式
为每个sRGB空间的纹理做这件事非常烦人。幸好,OpenGL给我们提供了另一个方案来解决我们的麻烦,这就是GL_SRGB和GL_SRGB_ALPHA内部纹理格式。【这个纹理格式,仅仅是帮我们把传入的纹理图片做一遍2.2gamma矫正而已,没别得东西,因此我们使用这个纹理格式,必须明确知道传入的图片是在sRGB空间定义的,那如果是传入的什么法线纹理、光照高光贴图纹理,诸如此类都是艺术家或我们自己在线性空间定义的,不能使用上述两种格式
】
如果我们在OpenGL中创建了一个纹理,把它指定为以上两种sRGB纹理格式其中之一,OpenGL将自动把颜色校正到线性空间中,这样我们所使用的所有颜色值都是在线性空间中的了。我们可以这样把一个纹理指定为一个sRGB纹理:
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
如果你还打算在你的纹理中引入alpha元素,必究必须将纹理的内部格式指定为GL_SRGB_ALPHA。
因为不是所有纹理都是在sRGB空间中的所以当你把纹理指定为sRGB纹理时要格外小心。比如diffuse纹理,这种为物体上色的纹理几乎都是在sRGB空间中的。而为了获取光照参数的纹理,像specular贴图和法线贴图几乎都在线性空间中,所以如果你把它们也配置为sRGB纹理的话,光照就坏掉了。指定sRGB纹理时要当心。
真正的伽马矫正(不是2.2)
线性 RGB 转换到 sRGB:
-
包括 Unreal Engine 和 Unity 等,默认使用 sRGB 颜色空间进行渲染和颜色处理。这是因为 sRGB 颜色空间广泛应用于显示设备和图像处理,确保了颜色的一致性和准确性。
-
线性 RGB 颜色值
C_linear
,sRGB 颜色值C_sRGB
转换公式是:// 该公式需要应用于 RGB 每个分量。 float linearToSRGB(float c_linear) { if (c_linear <= 0.0031308) { return 12.92 * c_linear; } else { return 1.055 * pow(c_linear, 1.0 / 2.4) - 0.055; } }
sRGB 转换到线性 RGB:
- 当需要转回RGB的时候)
float sRGBToLinear(float c_sRGB) { if (c_sRGB <= 0.04045) { return c_sRGB / 12.92; } else { return pow((c_sRGB + 0.055) / 1.055, 2.4); } }