OpenGL Photoshop 叠加混合模式

5

我正在尝试实现一个粒子系统(使用OpenGL 2.0 ES),其中每个粒子由一个带有简单纹理的四边形组成。

the red pixels are transparent

红色像素是透明的。每个粒子都会有一个随机的alpha值,从50%到100%不等。
现在棘手的部分是我想让每个粒子都具有类似于Photoshop中的“叠加”混合模式,我尝试了许多不同的glBlendFunc()组合但没有成功。
我不明白如何在片段着色器中实现这一点,因为我需要关于片段当前颜色的信息。所以我可以基于当前和纹理颜色计算出新的颜色。
我还考虑过使用帧缓冲对象,但我想每个粒子每一帧都需要重新渲染我的帧缓冲对象成纹理,因为我需要当粒子重叠时计算出的片段颜色。
我找到了关于Overlay计算的数学和其他信息,但我很难弄清楚我可以采取哪个方向来实现这个。
链接: - http://www.pegtop.net/delphi/articles/blendmodes/ - Photoshop blending mode to OpenGL ES without shaders

我希望有这样的效果:

overlapping particles and how they blend together

3个回答

11
你可以在iOS设备上获取关于帧缓冲区中当前片段颜色的信息。自iOS 6.0以来,可编程混合已经通过EXT_shader_framebuffer_fetch扩展在所有支持该版本的设备上提供。只需在片段着色器中声明该扩展(通过在顶部放置指令#extension GL_EXT_shader_framebuffer_fetch : require),然后你就可以在gl_LastFragData[0]中获取当前片段数据。
然后,是的,你可以在片段着色器中使用它来实现任何你喜欢的混合模式,包括所有Photoshop样式的混合模式。以下是一个差异混合的示例:
// compute srcColor earlier in shader or get from varying
gl_FragColor = abs(srcColor - gl_LastFragData[0]);

你可以使用这个扩展来实现不混合两种颜色的特效。例如,你可以将整个场景转换为灰度图像——正常渲染场景,然后使用一个着色器绘制四边形,该着色器读取最后一个片段数据并进行处理:
mediump float luminance = dot(gl_LastFragData[0], vec4(0.30,0.59,0.11,0.0));
gl_FragColor = vec4(luminance, luminance, luminance, 1.0);

您可以在GLSL中使用各种混合模式而无需进行帧缓冲提取,但这需要渲染到多个纹理,然后使用混合纹理的着色器绘制四边形。与帧缓冲提取相比,这是一个额外的绘制调用,并且需要在共享内存和瓷砖内存之间来回传输像素 - 这种方法更快。
此外,还可以说帧缓冲数据不一定是颜色...如果您在OpenGL ES 3.0中使用多个渲染目标,则可以从其中一个读取数据并将其用于计算写入另一个的数据。(请注意,扩展在GLSL 3.0中的工作方式有所不同。上述示例是GLSL 1.0,您仍然可以在ES3上下文中使用它。请参见spec以了解如何在#version 300 es着色器中使用帧缓冲提取。)

谢谢!我刚刚花了好几个小时的时间处理带FBO切换的头痛代码,现在使用gl_LastFragData集成它变得如此简单。非常感谢您提供这些信息!它运行起来非常流畅! - Mads Lee Jensen
1
这对我来说是新的。正如下面我的错误答案所证明的那样。因此,一个通用的GLSL提示可能会有所帮助:尽量避免分支。它们不仅会阻塞管道,而且通常会减少并行性(因为它们减少了相同程序计数器可以用于的同时事物数量)。您可能想要计算出亮化和变暗路径,并通过夹紧或混合最终选择其中之一。 - Tommy
1
只是为了扩展答案,在iOS上,您应该在着色器中添加一行“#extension GL_EXT_shader_framebuffer_fetch:require”以启用gl_LastFragData。 - Bikush

1
我猜测你想要这个配置: 源:GL_SRC_ALPHA 目标:GL_ONE。 方程式:GL_ADD
如果不是这样的话,如果你能解释一下你希望得到的过滤器的数学原理,那可能会有帮助。

将这些配置设置在一个完全不透明的四边形上,并在其上贴上纹理,结果是显示一个完全不透明的白色圆圈,期望的结果应该像这样(http://ge.tt/26KUDev/v/0)。我已经添加了类似问题/数学计算的链接 :) - Mads Lee Jensen
看起来文件共享服务目前不可用,我已经在这里重新上传了预期的结果图像:http://cl.ly/image/0o1p1M0R3n0b - Mads Lee Jensen
@madsleejensen 这不同于叠加层(我正在撰写一篇详细的回答),但如果你将源圆以不同颜色着色,加性混合是否能够给你接近你想要的效果? - Tommy
@Tommy,问题是我希望“背景颜色”也能动画显示,因此使用“固定”颜色不起作用。我的第一次尝试实际上是根据当前背景颜色和它们的位置(因为它是渐变背景颜色)硬编码粒子颜色。 - Mads Lee Jensen
@madsleejensen 加性混合将粒子的颜色添加到背景的颜色中。无论您是否有固定的背景颜色都是不重要的。如果粒子的颜色为(0.1,0.1,0.1,1.0),则粒子将使现有背景图像的三个通道变亮0.1。 - Tommy

1

[编辑:下面的答案在几乎所有OpenGL和OpenGL ES中都是正确的,除了iOS自6.0版本以来。有关EXT_shader_framebuffer_fetch的信息,请参见rickster的答案,它在ES 3.0术语中允许将目标缓冲区标记为inout,并在ES 2.0下引入相应的内置变量。在撰写本文时,iOS 6.0已经超过一年了,所以我没有什么特别的借口不知道这个问题;我决定不删除这个答案,因为对于那些基于其opengl-es、opengl-es-2.0和shader标签找到这个问题的人来说,它可能是有效的。]

简单确认一下:

  • OpenGL混合模式是在片段着色器结束后由硬件实现的;
  • 您不能以编程方式指定混合模式;
  • 您是正确的,唯一的解决方法是进行ping pong,交换每个几何体的目标缓冲区和源纹理(因此您从第一个绘制到第二个,然后从第二个返回到第一个,依此类推)。

根据维基百科和您提供的链接,Photoshop的叠加模式定义为:如果背景值为a,前景颜色为b,则输出像素为f(a, b),其中如果a < 0.5,则为2ab,否则为1 - 2(1 - a)(1 - b)

因此,混合模式会根据颜色缓冲区中已有的颜色每像素进行更改。而每个后续绘制的决策都取决于上一个留下的颜色缓冲区状态。

因此,你没有办法避免写作ping pong算法。

最接近的方法可能是像Sorin建议的那样,尝试使用纯加法混合来产生类似的效果。你可以通过添加最后一个ping-pong阶段,将所有值从线性比例尺转换为S曲线,这样它们就能够重叠并产生更大的变化。


感谢澄清,我只是觉得在片段着色器中没有访问渲染缓冲区的当前颜色有些奇怪,我的意思是说,应该有一种方法在幕后实现glBlendFunc所做的事情吧 :)?我对OpenGL还不熟悉,对性能没有什么感觉,您认为在渐变背景上运行约40个(随机移动的)粒子,并将渲染步骤分成3-4个步骤(背景,第一层粒子,第二层粒子...),使用FBO在像iPhone这样的设备上每秒60帧执行是否太昂贵了? - Mads Lee Jensen
我成功地使用“查找”纹理(背景)在片段着色器中渲染粒子时实现了这一点,然后使用叠加颜色计算,需要进行很多微调,但似乎可以工作 :) - Mads Lee Jensen
实际上,在iOS 6.0(及更高版本)设备上是可以进行可编程混合的。(而且这个问题标记为iOS,所以可以讨论。) - rickster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接