【问题标题】:Recreate HSV color using blend modes使用混合模式重新创建 HSV 颜色
【发布时间】:2012-09-23 22:09:06
【问题描述】:

我正在开发一个应用程序,该应用程序创建的图像的色调、饱和度和值会根据不同的参数而变化。出于性能原因,分别渲染色调、饱和度和数值分量,然后使用 Photoshop 风格的混合模式(正片叠底、叠加、屏幕、色调等)将它们合成在一起是有意义的。

我已经知道如何对 RGB 图像执行此操作:将每个通道拆分为自己的红色、绿色或蓝色图像,其值范围从透明到该通道的颜色。将它们叠加在黑色之上,并将它们的混合模式设置为屏幕,嘿,你有你的彩色图像:

如何使用由 HSV 值定义的图像执行此操作?我的应用程序经常更改其中一个通道而不更改其他两个通道,如果我可以在 GPU 上合成现有图像而不是每次发生变化时都渲染一个全新的图像,它将加快我的渲染速度。

这是一个例子:

在这个例子中,色调在圆周上从 0º 到 360º 变化,饱和度从中心到边缘从 0% 到 100% 变化,亮度 (V) 在圆周上从 0% 到 100% 变化.这是我的应用程序创建的典型图像类型。有没有可以用来分别创建这些通道并以数学上完美的方式合成它们的常见混合模式的组合?

【问题讨论】:

  • 我认为这是一个非常有趣的问题。我也在努力寻找答案。你找到什么了吗?
  • 我最近才真正在玩这个。我没有找到确切的解决方案,但 Screen 很接近。我在 Photoshop 中创建的图像实际上最终看起来比代码生成的版本更好(更亮、更饱和的颜色),但当然,如果你要追求准确性,那不会削减它。也许您可以将色调和亮度混合模式与重复图层结合使用?

标签: graphics colors alphablending compositing color-blending


【解决方案1】:

我的应用程序经常更改其中一个通道而不更改其他两个通道,如果我可以在 GPU 上合成现有图像而不是每次发生变化时都渲染一个全新的图像,它将加快我的渲染速度。 [OP,@ZevEisenberg]

关于速度和 GPU,我只需将 a conversion function 放入片段着色器 (e.g.)。这将读取存储在纹理或三个不同纹理中的 HSV,按像素进行转换并输出 RGB。好,易于。我看不出不更改其他图层有什么好处,因为 H、S 或 V 都会影响所有 RGB 通道。也许存储中间 RGB 结果,例如 hue=hsv2rgb(H,1,1),并使用 final=(hue*S+1-S)*V 进行更新,缓存 hue-to-rgb,但我认为这不值得。

无论如何,每种混合模式都有一个简单的公式,您可以将它们串在一起以用于 HSV 涉及一组过于复杂的中间纹理,但它会慢得多主要是因为不必要的临时存储和内存带宽。更不用说,尝试将公式重写为混合函数听起来非常具有挑战性,分支、除法、fract、钳位、绝对值等...

我对将图像拆分为其 HSV 组件并使用 Photoshop 中的混合模式重新创建原始图像的解决方案非常感兴趣。 [赏金,@phisch]

关于 Photoshop...我不是靠钱赚钱的。 所以在 gimp 中,有 Colours -> Components -> Compose/Decompose 可以为您执行此操作。如果这在 Photoshop 中不存在,我会有点惊讶,但也有点奇怪。如果没有的话,也许有 Photoshop 脚本/插件可以做到这一点?但是你确实特别说混合。您的问题可能会在https://graphicdesign.stackexchange.com/ 得到更多关注。下面,我对所涉及的复杂性给出了一个想法,我怀疑 Photoshop 是否真的可以做到。在 0 到 1 之外的像素值可能有一些方法,但是您可能会遇到精度问题,这是不应该的。


不管怎样,挑战就是挑战,尽管不切实际。以下内容纯属娱乐。

我将从以下函数(来自here)和三个 HSV 纹理开始...

vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

我只知道 OpenGL,我不确定如果没有浮点纹理或某些扩展混合功能我将如何做到这一点,所以我正在使用它们。但我只被允许使用混合(没有任何着色器)。对于常量,我将使用 (1,1,1), (1,2/3,1/3), (3,3,3), (6,6,6) (1/255,1) 制作纹理/255,1/255), (255,255,255), (1/2,1/2,1/2) 和 (0,0,0) 因为我无法让 GL_ZERO 与 GL_DIFFERENCE_NV 一起扩展。

  1. 从色调纹理开始
  2. 使用添加剂混合来添加 (1,2/3,1/3)
  3. 找到小数部分

    1. 使用减法混合,减去 0.5(这是针对 floor(),因为我假设 GL rounds 颜色在转换为 8 位时。如果不是,请跳过此步骤)
    2. 按比例缩小 1/255。这可以通过常规的 Alpha 混合来完成,但我已经使用颜色纹理进行了缩放。
    3. 通过非浮点纹理四舍五入到最接近的 1/255
    4. 按比例放大 255(回到浮点纹理)

    5. 现在我们有了整数部分。从我们开始的内容中减去这个

  4. 按 6 倍缩放

  5. 使用减法混合,取 3
  6. 取值的绝对值

    我将为此简单地使用GL_DIFFERENCE_NV,但如果没有它,下一步可能会使用两个单独的夹子。因为无论如何都会限制底片,类似于clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1)

  7. 减1

    好了,色调完成了

  8. 可以通过非浮点纹理进行钳位,但只使用GL_MIN

  9. 现在我可以对mix() 使用 Alpha 混合,但饱和度作为没有 Alpha 通道的黑白图像加载。因为它是混合白色的,所以手工操作实际上更容易......

    按饱和度缩放

  10. 加1
  11. 减去饱和度

    已应用饱和度

  12. 按值缩放

    还有图片

  13. 茶歇

全部使用

  • glBlendEquationGL_FUNC_REVERSE_SUBTRACTGL_MINGL_DIFFERENCE_NV
  • glBlendFunc

这是我的代码...

//const tex init
constTex[0] = makeTex() with 1, 1, 1...
constTex[1] = makeTex() with 1, 2/3, 1/3...
constTex[2] = makeTex() with 3, 3, 3...
constTex[3] = makeTex() with 6, 6, 6...
constTex[4] = makeTex() with 1/255, 1/255, 1/255...
constTex[5] = makeTex() with 255, 255, 255...
constTex[6] = makeTex() with 1/2, 1/2, 1/2...
constTex[7] = makeTex() with 0, 0, 0...

...

fbo[0] = makeFBO() with GL_RGB
fbo[1] = makeFBO() with GL_RGB32F
fbo[2] = makeFBO() with GL_RGB32F

...


hsv[0] = loadTex() hue
hsv[1] = loadTex() value
hsv[2] = loadTex() saturation

...

fbo[1].bind();
glDisable(GL_BLEND);
draw(hsv[0]); //start with hue
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[1]); //(1, 2/3, 1/3)
glBlendFunc(GL_ONE, GL_ONE);
fbo[1].unbind();

//compute integer part
fbo[2].bind();
glDisable(GL_BLEND);
draw(*fbo[1].colour[0]); //copy the last bit
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[6]); //0.5
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale down
draw(constTex[4]); //1/255
fbo[2].unbind();

fbo[0].bind(); //floor to integer
glDisable(GL_BLEND);
draw(*fbo[2].colour[0]);
fbo[0].unbind();

fbo[2].bind(); //scale back up
glDisable(GL_BLEND);
draw(*fbo[0].colour[0]);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale up
draw(constTex[5]); //255
fbo[2].unbind();

//take integer part for fractional
fbo[1].bind();
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(*fbo[2].colour[0]); //integer part
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(constTex[3]); //6
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[2]); //3
glBlendEquation(GL_DIFFERENCE_NV);
glBlendFunc(GL_ZERO, GL_ONE); //take the absolute
draw(constTex[7]); //0
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[0]); //1
glBlendEquation(GL_MIN);
glBlendFunc(GL_ONE, GL_ONE); //clamp (<0 doesn't matter, >1 use min)
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[2]); //saturation
fbo[1].unbind();

fbo[1].blit(); //check result

【讨论】:

    猜你喜欢
    • 2011-11-14
    • 2017-04-09
    • 2014-04-09
    • 1970-01-01
    • 2020-12-25
    • 2012-06-02
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    相关资源
    最近更新 更多