opengl - 与帧缓冲区的先前内容混合

发布于 2024-08-19 17:51:45 字数 617 浏览 2 评论 0原文

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

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

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

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

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

这一切都工作正常,除了当我在纹理上绘制 alpha 小于 1 的形状时,它无法与纹理之前的内容正确混合。假设我在纹理上绘制了一些 alpha = .6 的黑线。几个绘制周期后,我在这些线上绘制了一个 alpha = 0.4 的黑色圆圈。圆圈“下方”的线条被完全覆盖。尽管圆圈不是纯黑色(它与白色背景正确融合),但圆圈下方没有您所期望的“深色线条”。

然而,如果我在同一帧中绘制线条和圆圈,它们就会正确混合。我的猜测是,纹理与之前的内容没有融合。就像它只与 glclearcolor 混合一样。 (在本例中为<1.0f,1.0f,1.0f,1.0f>)

I am rendering to a texture through a framebuffer object, and when I draw transparent primitives, the primitives are blended properly with other primitives drawn in that single draw step, but they are not blended properly with the previous contents of the framebuffer.

Is there a way to properly blend the contents of the texture with the new data coming in?

EDIT: More information requsted, I will attempt to explain more clearly;

The blendmode I am using is GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA. (I believe that is typically the standard blendmode)

I am creating an application that tracks mouse movement. It draws lines connecting the previous mouse position to the current mouse position, and as I do not want to draw the lines over again each frame, I figured I would draw to a texture, never clear the texture and then just draw a rectangle with that texture on it to display it.

This all works fine, except that when I draw shapes with alpha less than 1 onto the texture, it does not blend properly with the texture's previous contents. Let's say I have some black lines with alpha = .6 drawn onto the texture. A couple draw cycles later, I then draw a black circle with alpha = .4 over those lines. The lines "underneath" the circle are completely overwritten. Although the circle is not flat black (It blends properly with the white background) there are no "darker lines" underneath the circle as you would expect.

If I draw the lines and the circle in the same frame however, they blend properly. My guess is that the texture just does not blend with it's previous contents. It's like it's only blending with the glclearcolor. (Which, in this case is <1.0f, 1.0f, 1.0f, 1.0f>)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

东京女 2024-08-26 17:51:45

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

请记住,所有叠加线均在此混合两次。一次是当它们混合到 FBO 纹理中时,另一次是当 FBO 纹理混合到场景上时。

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

另一个相关问题:当​​您在 FBO 中以“标准”混合模式(src alpha,1 - src alpha)将一条线与另一条线混合时,“混合”部分的 alpha 通道将包含 of< 的混合/em> 两个覆盖元素的 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 卡非常擅长绘制线条,因此一个选择是:而不是将线条混合到 FBO 中,将线几何图形写入顶点缓冲区对象 (VBO)。每次只需将 VBO 延长一点即可。如果您一次绘制的线数少于 40,000 条,那么几乎肯定会与您之前所做的一样快。

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

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

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

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

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

I think there are two possible problems here.

Remember that all of the overlay lines are blended twice here. Once when they are blended into the FBO texture, and again when the FBO texture is blended over the scene.

So the first possibility is that you don't have blending enabled when drawing one line over another in the FBO overlay. When you draw into an RGBA surface with blending off, the current alpha is simply written directly into the FBO overlay's alpha channel. Then later when you blend the whole FBO texture over the scene, that alpha makes your lines translucent. So if you have blending against "the world" but not between overlay elements, it is possible that no blending is happening.

Another related problem: when you blend one line over another in "standard" blend mode (src alpha, 1 - src alpha) in the FBO, the alpha channel of the "blended" part is going to contain a blend of the alphas of the two overlay elements. This is probably not what you want.

For example, if you draw two 50% alpha lines over each other in the overlay, to get the equivalent effect when you blit the FBO, you need the FBO's alpha to be...75%. (That is, 1 - (1-.5) * (1-0.5), which is what would happen if you just drew two 50% alpha lines over your scene. But when you draw the two 50% lines, you'll get 50% alpha in the FBO (a blend of 50% with...50%.

This brings up the final issue: by pre-mixing the lines with each other before you blend them over the world, you are changing the draw order. Whereas you might have had:

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

now you will have

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

In other words, pre-mixing the overlay lines into an FBO changes the order of blending and thus changes the final look in a way you may not want.

First, the simple way to get around this: don't use an FBO. I realize this is a "go redesign your app" kind of answer, but using an FBO is not the cheapest thing, and modern GL cards are very good at drawing lines. So one option would be: instead of blending lines into an FBO, write the line geometry into a vertex buffer object (VBO). Simply extend the VBO a little bit each time. If you are drawing less than, say, 40,000 lines at a time, this will almost certainly be as fast as what you were doing before.

(One tip if you go this route: use glBufferSubData to write the lines in, not glMapBuffer - mapping can be expensive and doesn't work on sub-ranges on many drivers...better to just let the driver copy the few new vertices.)

If that isn't an option (for example, if you draw a mix of shape types or use a mix of GL state, such that "remembering" what you did is a lot more complex than just accumulating vertices) then you may want to change how you draw into the VBO.

Basically what you'll need to do is enable separate blending; initialize the overlay to black + 0% alpha (0,0,0,0) and blend by "standard blending" the RGB but additive blending the alpha channels. This still isn't quite correct for the alpha channel but it's generally a lot closer - without this, over-drawn areas will be too transparent.

Then, when drawing the FBO, use "pre-multiplied" alpha, that is, (one, one-minus-src-alph).

Here's why that last step is needed: when you draw into the FBO, you have already multiplied every draw call by its alpha channel (if blending is on). Since you are drawing over black, a green (0,1,0,0.5) line is now dark green (0,0.5,0,0.5). If alpha is on and you blend normally again, the alpha is reapplied and you'l have 0,0.25,0,0.5.). By simply using the FBO color as is, you avoid the second alpha multiplication.

This is sometimes called "pre-multiplied" alpha because the alpha has already been multiplied into the RGB color. In this case you want it to get correct results, but in other cases, programmers use it for speed. (By pre-multiplying, it removes a mult per pixel when the blend op is performed.)

Hope that helps! Getting blending right when the layers are not mixed in order gets really tricky, and separate blend isn't available on old hardware, so simply drawing the lines every time may be the path of least misery.

怂人 2024-08-26 17:51:45

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

与默认的混合函数(源和目标因子)(GL_ONE、GL_ONE_MINUS_SRC_ALPHA)相同 混合方程 GL_FUNC_ADD


顺便说一句:

上面回答了问题中的具体问题,但是如果您可以轻松选择绘制顺序,那么理论上绘制可能会更好 颜色从前到后预乘到缓冲区中

    glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);

,否则使用同样的方法。

我背后的原因是显卡可能能够跳过已经实体的区域的着色器执行。不过我还没有测试过,所以在实践中可能没有什么区别。

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.

最好是你 2024-08-26 17:51:45

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

As Ben Supnik said, the best way to do this is rendering the entire scene with separate blend functions for color and alpha. If you are using the classic non premultiplied blend function try glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) to render your scene to FBO. and glBlendFuncSeparateOES(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) to render the FBO to screen.

It is not 100% accurate, but in most of the cases that will create no unexpected transparency.

Keep in mind that old Hardware and some mobile devices (mostly OpenGL ES 1.x devices, like the original iPhone and 3G) does not support separated blend functions. :(

药祭#氼 2024-08-26 17:51:45

虽然有点晚了,但我想回答这个问题,因为它仍然与我相关,并且其他人在使用多个 fbo 时可能会遇到类似的混合错误问题。

当从后到前渲染并使用多个 fbo 时,我发现这个解决方案更适合我的用例:

glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);

// set the alpha value to 1.0 for each fbo used
glClearColor(0.0, 0.0, 0.0, 1.0);

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);

glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
glClear(GL_COLOR_BUFFER_BIT);

gl[Draw|Bind|Draw|Bind|...]

这有效地确保了目标 fbo 颜色缓冲区中的 alpha 值保持在 1.0,这正是我想要进行后处理的值。

但是,这也会使存储的图像完全不透明!

说明:

这是 GLSL 中解释的情况。考虑来自您想要相互混合的两个纹理的两种颜色 srcdest。请注意,我们希望将src绘制在dest之上。这里是片段着色器中的代码:

in vec2 tex_coords;

out vec4 target;

uniform sampler2D s;
uniform sampler2D d;

void main() {
    vec4 src = texture(s, tex_coords);
    vec4 dest = texture(d, tex_coords);
    [...]
    target = dest;
}

实际上,我们会在绘制调用中提供 src 颜色,而 dest 是不可访问的,因为它是当前存储在 fbo 中的颜色。即,类似于“当前存储的”目标。如果在未启用混合的情况下将最后两帧渲染为纹理,则可以使用相同的纹理坐标访问这些像素及其原始 Alpha 值,并自行执行混合。

使用默认设置的结果(用此替换上面的 [...]):

// glBlendEquation(GL_FUNC_ADD)
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
dest.rgba = src.rgba * (src.a) + dest.rgba * (1.0 - src.a);

注意 + 如何来自 GL_FUNC_ADD, (src .a) 来自 GL_SRC_ALPHA(1.0 - src.a) 来自 GL_ONE_MINUS_SRC_ALPHA

重要的是要知道只有 dest.rgb 实际上打印到屏幕,而不考虑存储的 dest.a 值!因此,当涉及到屏幕上打印哪种颜色时,dest 中计算出的 alpha 完全无关,只有 dest.rgb 重要。但是,存储的 alpha 已更改。因此,当您使用之前存储的 dest 值作为新的 src 值再次运行片段着色器时,它会再次应用 Alpha 并且颜色会发生变化。这是首先渲染到纹理,然后从纹理渲染到默认 fbo/屏幕之后发生的情况。

建议设置将 dest 中的 alpha 保持为 1.0:

// glBlendEquation(GL_FUNC_ADD)
// glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)
dest.rgb = src.rgb * (src.a) + dest.rgb * (1.0 - src.a);
dest.a = src.a * (0.0) + dest.a * (1.0);

这次 glBlendFuncSeparate 拆分 RGB 和 dest 颜色的 alpha 部分的计算。调用 glClearColor(0.0, 0.0, 0.0, 1.0) 会将 dest.a 初始设置为 1.0。这意味着该项的右侧始终为 1.0,因为 GL_ONE 导致该项为 1.0 * 1.0 = 1.0。左侧始终为 0.0,因为 GL_ZERO 强制将 src alpha 乘以该值。因此,dest 的 alpha 值保持为 0.0 + 1.0 = 1.0。如果 dest 值再次用作 src 值,它将完全不透明,并将完全覆盖 dest.rgb(请参阅 dest.rgb 方程)。

或者,正如前面的答案已经指出的那样,也可以在写入默认 fbo 时简单地禁用混合,这基本上不会再次应用 alpha 值。然而,当有多个后处理阶段或尝试一次渲染到多个目标然后组合结果时,这会变得很棘手。因此,提出了上述解决方案。

Although a bit late to the game, I want to answer this question, because it was still relevant for me and other people might have a similar issue with blending being wrong when using multiple fbos.

When rendering back-to-front to and using multiple fbos, I found this solution more suitable for my use case:

glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);

// set the alpha value to 1.0 for each fbo used
glClearColor(0.0, 0.0, 0.0, 1.0);

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);

glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
glClear(GL_COLOR_BUFFER_BIT);

gl[Draw|Bind|Draw|Bind|...]

This effectively ensures that the alpha value in the destination fbo color buffer stays at 1.0, which is what I wanted for post-processing.

However, this also makes the stored image fully opaque!

Explanation:

Here is what happens explained in GLSL. Think about two colors src and dest that come from two textures that you want to blend with each other. Note that we want to draw src over dest. Here the code in the fragment shader:

in vec2 tex_coords;

out vec4 target;

uniform sampler2D s;
uniform sampler2D d;

void main() {
    vec4 src = texture(s, tex_coords);
    vec4 dest = texture(d, tex_coords);
    [...]
    target = dest;
}

In reality, one would provide the src color in the draw call and dest is unaccessible, because it is the color currently stored in the fbo. I.e., something like "currently stored" target. If you render the last two frames to textures without blending enabled, you can access these pixels and their original alpha values with the same texture coordinates and perform blending yourself.

Result with default settings (replace above [...] with this):

// glBlendEquation(GL_FUNC_ADD)
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
dest.rgba = src.rgba * (src.a) + dest.rgba * (1.0 - src.a);

Note how the + comes from GL_FUNC_ADD, (src.a) from GL_SRC_ALPHA and (1.0 - src.a) from GL_ONE_MINUS_SRC_ALPHA.

It is important to know that only dest.rgb is actually printed to screen without any consideration of the stored dest.a value! Hence, the calculated alpha in dest is completely irrelevant when it comes to which color on the screen is printed, only dest.rgb matters. However, the alpha that is stored has changed. So when you run the fragment shader again with the previously stored dest value as the new src value, it applies the alpha again and the color changes. This is what happens after rendering to the texture first and then from texture to default fbo / screen.

The proposed settings to keep the alpha at 1.0 in dest:

// glBlendEquation(GL_FUNC_ADD)
// glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)
dest.rgb = src.rgb * (src.a) + dest.rgb * (1.0 - src.a);
dest.a = src.a * (0.0) + dest.a * (1.0);

This time glBlendFuncSeparate split the calculation for the RGB and the alpha part of the dest color. Calling glClearColor(0.0, 0.0, 0.0, 1.0) sets dest.a initially to 1.0. This means that the righthand side of the term is always 1.0, because GL_ONE causes the term to be 1.0 * 1.0 = 1.0. The left side is always 0.0, because GL_ZERO forces the src alpha to be mulitplied by that. Hence, the alpha value of dest stays 0.0 + 1.0 = 1.0. If the dest value is used again as the src value, it will be fully opaque and will overwrite dest.rgb entirely (see dest.rgb equation).

Alternatively, as already pointed out by previous answers, one could also simply disable blending when writing to the default fbo, which would basically not apply the alpha value again. However, this gets tricky when having multiple post-processing stages or when trying to render to multiple targets at once and then combine results. Hence, the above proposed solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文