opengl - 与之前的framebuffer内容混合

时间:2010-01-31 08:50:29

标签: opengl alphablending framebuffer render-to-texture

我通过帧缓冲对象渲染到纹理,当我绘制透明图元时,图元与在单个绘制步骤中绘制的其他图元正确混合,但它们没有与帧缓冲的先前内容正确混合。

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

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

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

我正在创建一个跟踪鼠标移动的应用程序。它绘制了将前一个鼠标位置连接到当前鼠标位置的线条,因为我不想在每个帧上再次绘制线条,我想我会绘制一个纹理,从不清除纹理然后只绘制一个矩形纹理就可以显示出来。

这一切都很好,除了当我在纹理上绘制alpha小于1的形状时,它不能与纹理的先前内容正确混合。假设我有一些黑色线条,alpha = .6被绘制到纹理上。一对夫妇画了一个周期之后,我在那些线上画了一个alpha = .4的黑色圆圈。圆圈“下方”的线条被完全覆盖。虽然圆圈不是黑色(它与白色背景正确混合),但圆圈下方没有“暗线”,正如您所期望的那样。

如果我在同一帧中绘制线条和圆圈,它们会正确混合。我的猜测是纹理不会与之前的内容混合。它就像它只与glclearcolor混合。 (在这种情况下,<1.0f,1.0f,1.0f,1.0f>)

3 个答案:

答案 0 :(得分:27)

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

请记住,所有叠加线都在此处混合两次。一旦它们被混合到FBO纹理中,并且再次将FBO纹理混合到场景上时。

所以第一种可能性是在FBO叠加层中绘制一条线而不是另一条线时没有启用混合。当您在混合关闭时绘制RGBA曲面时,当前的alpha将直接写入FBO叠加层的Alpha通道。然后,当您将整个FBO纹理混合到场景上时,该alpha会使您的线条变得半透明。因此,如果您对“世界”进行混合而不是叠加元素之间的混合,则可能不会发生混合。

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

例如,如果您在叠加层中相互绘制两条50%的alpha线,为了在blit FBO时获得相同的效果,则需要FBO的alpha值为... 75%。 (也就是说,1 - (1 - .5)*(1-0.5),如果你只是在你的场景中绘制了两条5​​0%的alpha线,那会发生什么。但是当你绘制两条50%的线时,你会在FBO中获得50%的阿尔法(混合50%和... 50%。

这提出了最后一个问题:通过在将它们混合到世界之前预先混合线条,您将更改绘制顺序。虽然你可能有:

混合(混合(混合(背景颜色,模型),第一行),第二行);

现在你将拥有

混合(混合(第一行,第二行),混合(背景颜色,模型))。

换句话说,将叠加线预混合到FBO会改变混合顺序,从而以您可能不需要的方式改变最终外观。

首先,解决这个问题的简单方法是:不要使用FBO。我意识到这是一个“重新设计你的应用程序”的答案,但使用FBO并不是最便宜的,现代GL卡非常擅长绘制线条。因此,一个选项是:将线条几何体写入顶点缓冲区对象(VBO),而不是将线条混合到FBO中。每次只需稍微扩展一下VBO。如果你一次抽取的线数少于40,000行,那么这几乎肯定会和以前一样快。

(如果你走这条路线就有一个提示:使用glBufferSubData写入行,而不是glMapBuffer - 映射可能很昂贵而且在许多驱动程序的子范围上不起作用...更好的是让驱动程序复制几个新的顶点。)

如果这不是一个选项(例如,如果你绘制混合的形状类型或使用GL状态的混合,这样“记住”你所做的比复制顶点要复杂得多)那么你可能想要改变你如何吸引VBO。

基本上你需要做的是启用单独混合;将叠加层初始化为黑色+ 0%alpha(0,0,0,0)并通过“标准混合”RGB混合,但添加混合Alpha通道。这对于alpha通道来说仍然不太正确,但它通常更接近 - 如果没有这个,过度绘制的区域将会过于透明。

然后,在绘制FBO时,使用“pre-multiplied”alpha,即(one,one-dec-src-alph)。

这就是为什么需要最后一步的原因:当您绘制到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颜色。在这种情况下,您希望它获得正确的结果,但在其他情况下,程序员使用它来获得速度。 (通过预乘,它会在执行混合操作时删除每个像素的多个。)

希望有所帮助!当图层没有按顺序混合时正确混合变得非常棘手,并且旧硬件上没有单独的混合,因此每次简单地绘制线条可能是最不痛苦的路径。

答案 1 :(得分:17)

用透明黑色(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

(我使用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

与默认blend equation GL_FUNC_ADD的混合函数(源和目标因子)(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)相同。


暂且不说:

以上回答了问题中的具体问题,但是如果您可以轻松选择绘制顺序,理论上最好将premultiplied颜色从前到后绘制到缓冲区中

    glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_ONE);

以其他方式使用相同的方法。

我的理由是,显卡可能能够跳过已经稳固的区域的着色器执行。我没有对此进行过测试,因此在实践中可能没有任何区别。

答案 2 :(得分:4)

正如Ben Supnik所说,最好的方法是使用单独的颜色和alpha混合函数渲染整个场景。如果您使用经典的非预乘混合函数,请尝试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)不支持分离的混合功能。 :(