18

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

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

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

我使用的混合模式是 GL_SRC_ALPHA、GL_ONE_MINUS_SRC_ALPHA。(我相信这通常是标准的混合模式)

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

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

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

4

3 回答 3

27

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

请记住,所有覆盖线在这里混合了两次。一次是当它们混合到 FBO 纹理中时,另一次是当 FBO 纹理混合到场景中时。

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

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

例如,如果您在叠加层中相互绘制两条 50% 的 alpha 线,为了在对 FBO 进行 blit 时获得等效效果,您需要 FBO 的 alpha 为...75%。(即 1 - (1-.5) * (1-0.5),如果您只在场景上绘制两条 50% 的 alpha 线会发生这种情况。但是当您绘制两条 50% 的线时,您将在 FBO 中获得 50% 的 alpha(50% 与...50% 的混合。

这就引出了最后一个问题:通过在将它们混合到世界各地之前将它们预先混合在一起,您正在改变绘制顺序。而你可能有:

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

现在你将拥有

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

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

首先,解决这个问题的简单方法:不要使用 FBO。我意识到这是一种“去重新设计你的应用程序”的答案,但使用 FBO 并不是最便宜的,而且现代 GL 卡非常擅长绘制线条。所以一种选择是:不是将线条混合到 FBO 中,而是将线条几何图形写入顶点缓冲区对象 (VBO)。每次只需将 VBO 扩展一点。如果您一次绘制少于 40,000 条线,这几乎肯定会与您之前所做的一样快。

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

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

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

然后,在绘制 FBO 的时候,使用“预乘”的 alpha,即 (one, one-minus-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 颜色。在这种情况下,您希望它得到正确的结果,但在其他情况下,程序员使用它来提高速度。(通过预乘,它在执行混合操作时每像素删除一个 mult。)

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

于 2010-01-31T23:00:35.927 回答
21

Clear the FBO with transparent black (0, 0, 0, 0), draw into it back-to-front with

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

and draw the FBO with

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

to get the exact result.

As Ben Supnik wrote, the FBO contains colour already multiplied with the alpha channel, so instead of doing that again with GL_SRC_ALPHA, it is drawn with GL_ONE. The destination colour is attenuated normally with GL_ONE_MINUS_SRC_ALPHA.

The reason for blending the alpha channel in the buffer this way is different:

The formula to combine transparency is

resultTr = sTr * dTr

(I use s and d because of the parallel to OpenGL's source and destination, but as you can see the order doesn't matter.)

Written with opacities (alpha values) this becomes

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

which is the same as the blend function (source and destination factors) (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) with the default blend equation GL_FUNC_ADD.


As an aside:

The above answers the specific problem from the question, but if you can easily choose the draw order it may in theory be better to draw premultiplied colour into the buffer front-to-back with

    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);

and otherwise use the same method.

My reasoning behind this is that the graphics card may be able to skip shader execution for regions that are already solid. I haven't tested this though, so it may make no difference in practice.

于 2013-08-28T20:23:27.410 回答
5

正如 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)不支持分离的混合功能。:(

于 2010-11-02T09:15:25.423 回答