在图形中,何时需要考虑伽玛



所以我有一些代码旨在生成两种输入颜色之间的线性渐变:

struct color {
    float r, g, b, a;
}
color produce_gradient(const color & c1, const color & c2, float ratio) {
    color output_color;
    output_color.r = c1.r + (c2.r - c1.r) * ratio;
    output_color.g = c1.g + (c2.g - c1.g) * ratio;
    output_color.b = c1.b + (c2.b - c1.b) * ratio;
    output_color.a = c1.a + (c2.a - c1.a) * ratio;
    return output_color;
}

我还在着色器中编写了(语义相同的)代码。

问题在于,使用这种代码会在颜色相遇的中间产生"暗带",这是由于亮度在计算机屏幕和用于表示这些像素的原始数据之间如何转换的怪癖。

所以我的问题是:

  • 我是否需要在主机函数和/或设备函数中更正 gamma,或者两者都不需要?
  • 更正函数以正确处理伽玛的最佳方法是什么?我在下面提供的代码是否以适当的方式转换颜色?

法典:

color produce_gradient(const color & c1, const color & c2, float ratio) {
    color output_color;
    output_color.r = pow(pow(c1.r,2.2) + (pow(c2.r,2.2) - pow(c1.r,2.2)) * ratio, 1/2.2);
    output_color.g = pow(pow(c1.g,2.2) + (pow(c2.g,2.2) - pow(c1.g,2.2)) * ratio, 1/2.2);
    output_color.b = pow(pow(c1.b,2.2) + (pow(c2.b,2.2) - pow(c1.b,2.2)) * ratio, 1/2.2);
    output_color.a = pow(pow(c1.a,2.2) + (pow(c2.a,2.2) - pow(c1.a,2.2)) * ratio, 1/2.2);
    return output_color;
}

编辑:作为参考,这里有一篇与这个问题相关的帖子,目的是解释"错误"在实践中的样子:https://graphicdesign.stackexchange.com/questions/64890/in-gimp-how-do-i-get-the-smudge-blur-tools-to-work-properly

我认为你的代码中有一个缺陷。首先,我会确保0 <= ratio <=1

其次,我会使用公式c1.x * (1-ratio) + c2.x *ratio

您目前设置计算的方式允许负面结果,这将解释黑点。

对于何时必须担心伽玛,没有明确的答案。

在混合、混合、计算照明等时,您通常希望在线性色彩空间中工作。

如果您的输入不在线性空间中(例如,经过伽马校正或在 sRGB 等颜色空间中),那么您通常希望立即将它们转换为线性。 您还没有告诉我们您的输入是否是线性 RGB。

完成后,您希望确保针对输出设备的颜色空间校正线性值,无论是简单的伽玛变换还是其他颜色空间转换。 同样,这里没有简单的答案,因为您必须知道该转换是在堆栈中的较低级别隐式为您完成的,还是您的责任。

也就是说,很多代码都逃脱了作弊。 他们将在 sRGB 中获取输入并应用 alpha 混合或淡入淡出,就好像它们处于线性 RGB 中一样,然后按原样输出结果(可能使用钳位)。 有时这是一个合理的权衡。

你的问题完全在于感知颜色实现领域。要处理感知亮度像差,您可以使用在线找到的众多算法之一一种这样的算法是Luma。

float luma(color c){
return 0.30 * c.r + 0.59 * c.g + 0.11 * c.b;
}

这一点上,我想指出的是,标准方法是在感知色彩空间中应用所有算法,然后转换为RGB色彩空间进行显示。

colorRGB --(转换)--> 颜色感知 --(输入)--> f (颜色感知) --(输出)-->颜色感知' --

(转换) --> 颜色RGB

但是如果你只想调整亮度(感知色差不会固定),你可以通过以下方式有效地做到这一点

//define color of unit lightness. based on Luma algorithm 
color unit_l(1/0.3/3, 1/0.59/3, 1/0.11/3);
color produce_gradient(const color & c1, const color & c2, float ratio) {
    color output_color;
    output_color.r = c1.r + (c2.r - c1.r) * ratio;
    output_color.g = c1.g + (c2.g - c1.g) * ratio;
    output_color.b = c1.b + (c2.b - c1.b) * ratio;
    output_color.a = c1.a + (c2.a - c1.a) * ratio;
    float target_lightness = luma(c1) + (luma(c2) - luma(c1)) * ratio; //linearly interpolate perceptual lightness
    float delta_lightness = target_lightness - luma(output_color); //calculate required lightness change magnitude
    //adjust lightness
    output_color.g += unit_l.r * delta_lightness;
    output_color.b += unit_l.g * delta_lightness;
    output_color.a += unit_l.b * delta_lightness;
    //at this point luma(output_color) approximately equals target_lightness which takes care of the perceptual lightness aberrations
    return output_color;
}

你的第二个代码示例是完全正确的,除了 alpha 通道通常没有经过伽马校正,所以你不应该使用它pow。为了提高效率,最好对每个通道进行一次伽马校正,而不是加倍。

一般规则是,每当添加或减去值时,都必须在两个方向上执行 gamma。如果你只是乘法或除法,它没有区别:pow(pow(x, 2.2) * pow(y, 2.2), 1/2.2)在数学上等价于x * y

有时您可能会发现在未校正的空间中工作可以获得更好的结果。例如,如果要调整图像大小,则应在缩小大小时执行伽玛校正,但在放大图像时不应执行灰度系数校正。我忘记了我在哪里读到的,但我自己验证了它 - 如果您使用伽马校正像素值而不是线性像素值,则放大的伪影就不那么令人反感了。

最新更新