OpenGL - 与帧缓冲先前内容混合

21

我正在使用帧缓冲对象渲染到纹理,当我绘制透明的形状时,它们与在同一绘制步骤中绘制的其他形状正确地混合,但是它们与帧缓冲区先前的内容混合不当。

有没有办法将纹理的内容与新数据正确混合?

编辑:请求更多信息,我将尝试更清楚地解释;

我正在使用的混合模式是GL_SRC_ALPHA和GL_ONE_MINUS_SRC_ALPHA。(我认为这通常是标准混合模式)

我正在创建一个跟踪鼠标移动的应用程序。它绘制连接之前鼠标位置和当前鼠标位置的线条,并且因为我不想每一帧都重新绘制线条,所以我想着将其绘制到一个纹理上,从不清除该纹理,然后只需在一个矩形上绘制该纹理即可显示它。

所有这些都很好,除了当我绘制alpha小于1的形状到纹理上时,它们无法与纹理的先前内容正确混合。比方说,我在纹理上绘制了一些alpha = .6的黑色线条。再过几个绘制周期,我就在这些线条上面画了一个alpha = .4的黑色圆。圆形下面的线条完全被覆盖了。尽管该圆不是纯黑色(它可以与白色背景正确混合),但是你没有看到预期的圆形下面的更暗的线条。

然而,如果我在同一帧中绘制线条和圆形,它们就可以正确混合了。我的猜测是该纹理只能与其先前的内容混合。就像它只与glclearcolor混合一样。(在此情况下为 <1.0f,1.0f,1.0f,1.0f>)


2
混合应该与帧缓冲区中的任何内容配合使用(这就是混合的作用)。提供更多数据(您正在使用什么混合模式,您所谓的“不正确”的混合是什么意思?) - Bahbar
我已经更新了更多的信息。谢谢你的快速回复。 - staticfloat
3个回答

29

我认为这里可能有两个问题。

请记住,所有叠加线都会混合两次。一次是在它们混合到 FBO 纹理时,另一次是在 FBO 纹理覆盖场景时混合。

因此,第一个可能的问题是在 FBO 叠加层中绘制一条线覆盖另一条线时,您没有启用混合。当您使用关闭混合的 RGBA 表面进行绘制时,当前 alpha 直接写入 FBO 叠加层的 alpha 通道中。然后稍后当整个 FBO 纹理覆盖在场景上时,该 alpha 使您的线条半透明。因此,如果您针对“世界”进行混合,但不进行叠加元素之间的混合,则可能不会发生混合。

另一个相关的问题:在 FBO 中以“标准”混合模式(src alpha,1 - src alpha)混合一条线覆盖另一条线时,“混合”部分的 alpha 通道将包含两个叠加元素的 alpha 的混合。这可能不是您想要的。

例如,如果您在叠加层中绘制两条 50% 的 alpha 线,那么在复制 FBO 时,您需要 FBO 的 alpha 是 ...75%。也就是说,1 -(1-.5)*(1-0.5),这是当您在场景中绘制两条 50% 的 alpha 线时会发生的情况。但是当您绘制两条 50% 的线时,您将在 FBO 中获得 50% 的 alpha(50% 与...50% 的混合。

这带来了最后一个问题:通过在将它们混合到世界之前彼此预混合叠加层,您正在更改绘制顺序。尽管您可能会有:

blend(blend(blend(background color, model), first line), second line);

现在您将会有:

blend(blend(first line, second line), blend(background color, model))。

换句话说,在 FBO 中预混合叠加线会更改混合顺序,从而以您可能不希望的方式更改最终外观。

首先,解决这个问题的简单方法是:不要使用帧缓冲对象(FBO)。我知道这是一个“重新设计应用程序”的答案,但是使用FBO并不是最便宜的选择,而现代GL卡在绘制线条方面非常出色。所以一个选择是:将线条几何图形写入顶点缓冲对象(VBO)而不是混合到FBO中。每次简单地扩展一下VBO即可。如果您一次绘制的线条少于,比如说,40,000条,那么这几乎肯定和之前做的一样快。

(如果您采取这种方法,请注意一个提示:使用glBufferSubData将线条写入VBO,而不是使用glMapBuffer - 在许多驱动程序上,映射可能很昂贵,并且不能在子范围上工作... 最好只是让驱动程序复制一些新的顶点。)

如果这不是一个选项(例如,如果您绘制了混合各种类型的形状或使用了混合各种GL状态,以至于“记住”您所做的事情比累积顶点更加复杂),那么您可能需要改变将图形绘制到VBO的方式。

基本上,您需要启用分离混合;将覆盖层初始化为黑色+0%的alpha(0,0,0,0),并按标准混合RGB和叠加混合alpha通道进行混合。这对于alpha通道仍然不是完全正确的,但通常更接近 - 如果没有这样做,则过度绘制区域将过于透明。

然后,在绘制FBO时,使用“预乘”alpha,即(一,一减源alpha)。

这是为什么需要最后一步:当您绘制到FBO中时,您已经通过其alpha通道乘以了每个绘制调用(如果启用混合)。 因为您正在黑色上绘图,所以绿色(0,1,0,0.5)的线现在变成了深绿色(0,0.5,0,0.5)。如果再次启用alpha并正常混合,则重新应用alpha,并且您将获得0,0.25,0,0.5.)。通过简单地使用FBO颜色,您避免了第二次alpha乘法。

有时这被称为“预乘”alpha,因为alpha已经乘到RGB颜色中。在这种情况下,您希望获得正确的结果,但在其他情况下,程序员使用它来提高速度。(通过预乘法,当执行混合操作时,每个像素上就可以少一次乘法运算。)

希望这能帮助到您!当图层未按顺序混合并且旧硬件上不可用单独混合时,正确地进行混合变得非常棘手,因此每次简单地绘制线条可能是最不痛苦的方法。


非常感谢您提供如此详尽的回复!很抱歉这么晚才回复您,但如果我有更多的积分可以给您,我一定会的!谢谢! - staticfloat

23

使用透明黑色(0, 0, 0, 0)清除FBO,在后向前的顺序中绘制到其中

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

并使用 FBO 进行绘制

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

为了得到精确的结果。
正如Ben Supnik所写,FBO已经包含了颜色和alpha通道相乘的值,因此不需要再次使用GL_SRC_ALPHA进行操作,而是使用GL_ONE进行绘制。目标颜色通过GL_ONE_MINUS_SRC_ALPHA进行正常衰减。
在缓冲区中以这种方式混合alpha通道的原因是不同的:
组合透明度的公式为:
resultTr = sTr * dTr

(I使用s和d是因为与OpenGL的源和目标平行,但正如您所看到的,顺序并不重要。)

用不透明度(alpha值)编写,变成了

    1 - rA = (1 - sA) * (1 - dA)
<=> rA = 1 - (1 - sA) * (1 - dA)
       = 1 - 1 + sA + dA - sA * dA
       =         sA + (1 - sA) * dA

这与默认的 混合方程 GL_FUNC_ADD 使用相同的混合函数(源和目标因子)(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)相同。
作为附注:
上述回答了问题中的具体问题,但如果您可以轻松选择绘制顺序,理论上最好使用预乘颜色从前到后绘制到缓冲区。
    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);

并且使用相同的方法。

我之所以这样做的原因是,显卡可能能够跳过已经是实心的区域的着色器执行。不过我还没有进行测试,所以在实践中可能没有任何区别。


5
我想说的是,大约5年后,我再次搜索这个问题,在阅读了你的帖子后,心里想着:“哇,这解决了我的问题,它如此简单,而且完美地契合了我的需求!”然后我又想,“我不知道最初提出这个问题的人是谁。” - staticfloat
1
@staticfloat 嘿,很高兴这个消息传到了你这里。我是在为其他人查找嵌套UI控件的渲染问题(我仍然不擅长实际图形编程)而进行搜索,之前的答案提示了我关于分离混合模式的信息,但如果没有确切的解决方案,那么这将会非常奇怪。再次思考后,这个解决方案只有在你需要在之前的缓冲区上绘制时才是最优的。对于可以选择顺序的情况,例如将GUI控件渲染到缓冲区中时,我会添加一个注释,因为这种情况下人们很可能会来到这里。 - Tamschi
1
运行得很好!这帮助我将 FBO 中的透明文本渲染到屏幕上。至于为什么/如何工作,我认为我还需要更多的阅读 :) - Arahman

5
如Ben Supnik所说,最好的方法是使用单独的混合函数对颜色和透明度进行渲染。如果您正在使用经典的非预乘混合函数,请尝试使用glBlendFuncSeparateOES(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ONE)将场景渲染到FBO中,并使用glBlendFuncSeparateOES(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)将FBO渲染到屏幕上。
虽然不是100%准确,但在大多数情况下,这样做不会产生意外的透明度。
请记住,旧硬件和一些移动设备(主要是OpenGL ES 1.x设备,如原始iPhone和3G)不支持分离的混合函数。:(

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