用以前的帧缓冲区的内容进行Opengl混合[英] opengl - blending with previous contents of framebuffer

本文是小编为大家收集整理的关于用以前的帧缓冲区的内容进行Opengl混合的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我正在通过框架缓冲对象呈现纹理,当我绘制透明的原始词时,原始词与该单个绘制步骤中绘制的其他原始词正确混合在一起,但是它们与框架缓冲器的先前内容没有正确混合.

是否有一种方法可以将纹理的内容与新数据融合在一起?

编辑:更多的信息重新定义,我将尝试更清楚地解释;

我正在使用的BlendMode是GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA. (我相信这通常是标准Blendmode)

我正在创建一个跟踪鼠标运动的应用程序.它绘制了将先前的鼠标位置连接到当前鼠标位置的线条,并且由于我不想再次绘制每个帧的线条,我认为我会绘制到纹理上,切勿清除纹理,然后与此绘制矩形上面的纹理以显示它.

这一切都很好,除了我用小于1的alpha绘制形状在纹理上时,它与纹理的先前内容没有正确的融合.假设我有一些黑色线,alpha = .6绘制在纹理上.一对夫妇绘制周期,然后我在这些线上绘制一个黑色圆圈= .4.圆圈"下面"的线完全被覆盖.尽管圆圈不是平坦的黑色(它与白色背景正确融合在一起),但圆下没有"暗线".

如果我在同一框架中绘制线条和圆圈,它们会正确融合.我的猜测是,纹理与以前的内容没有融合.就像它仅与Glclearcolor融为一体一样. (在这种情况下,这是<1.0f,1.0f,1.0f,1.0f>)

推荐答案

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

请记住,所有覆盖线都在此处混合了两次..一次将它们混合到FBO纹理中,然后在场景上混合FBO纹理时.

因此,第一个可能性是,在FBO覆盖层中的另一行绘制一行时,您没有启用混合.当您将RGBA表面融合在一起时,当前的Alpha直接写入FBO Overlay的Alpha通道中.然后,当您将整个FBO纹理混合在场景上时,Alpha使您的线条变得半透明.因此,如果您反对"世界",但没有在覆盖元素之间进行混合,则可能不会发生混合.

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

例如,如果您在覆盖层中互相绘制两条50%的alpha线,以在爆炸FBO时获得等效效果,则需要FBO的alpha为... 75%. (也就是说,1-(1-.5) *(1-0.5),如果您只是在场景上绘制了两条5​​0%的alpha线,那就是会发生的事情.但是,当您绘制两条50%的线条时,您将在FBO中获得50%的α(将50%与... 50%的50%混合.

这提出了最后一个问题:通过将彼此混合在一起,然后再将它们融合到世界上,您就是在更改抽奖顺序.而您可能有:

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

现在您将拥有

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

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

首先,解决此问题的简单方法:不要使用FBO.我意识到这是一种"重新设计您的应用程序"的答案,但是使用FBO并不是最便宜的东西,现代GL卡非常擅长绘图线.因此,一个选项是:而不是将线路混合到FBO中,而是将线几何形状写入顶点缓冲区对象(VBO).只需每次稍微扩展VBO即可.如果您一次的绘制比40,000行要少,那么这几乎肯定会像以前一样快.

(如果您走此路线,请使用一个提示:使用Glbuffersubdata写线条,而不是glmapbuffer-映射可能很昂贵,并且在许多驱动程序上不适用于子范围...最好让驱动程序复制很少有新顶点.)

如果不是一个选项(例如,如果您绘制形状类型的混合或使用GL状态的混合,则"记住"您所做的工作比仅仅累积顶点更为复杂),然后您可能想更改您如何吸引VBO.

基本上您需要做的是启用单独的混合;将覆盖层初始化为Black + 0%alpha(0,0,0,0),然后通过"标准混合" RGB而融合,但将添加剂混合在Alpha通道中.对于Alpha频道而言,这仍然不太正确,但通常更近 - 没有这个,过度的区域将太透明.

然后,在绘制FBO时,请使用" pre-multipliped" alpha,即(一个,一分钟,src-alph).

这就是需要最后一步的原因:当您绘制FBO时,您已经通过其Alpha频道乘以每次绘制呼叫(如果打开的话).由于您正在绘画黑色,因此绿色(0,1,0.5)线现在是深绿色(0,0.5,0,0.5).如果Alpha打开并且您正常混合,则重新申请Alpha,并且您有0,0.25,0,0.5.).通过简单地使用FBO颜色,您可以避免第二个alpha乘法.

这有时被称为" pre-multiple"α,因为alpha已经乘以RGB颜色.在这种情况下,您希望它获得正确的结果,但是在其他情况下,程序员将其用于速度. (通过进行预倾斜,它在执行混合OP时会删除每个像素的多个像素.)

希望有帮助!当不混合以使订单变得非常棘手时,将层混合得正确,并且在旧硬件上没有单独的混合物,因此每次绘制线条可能是最少痛苦的路径.

其他推荐答案

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

在缓冲区中混合α通道的原因是不同的:

结合透明度的公式为

resultTr = sTr * dTr

(我使用s和d,因为与OpenGL的源和目的地平行,但是您可以看到订单没关系.)

用不透明的(alpha values)编写的这一变成

    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 .


旁边:

上面从问题中回答了特定问题,但是如果您可以轻松地选择抽奖顺序,那么从理论上讲,绘制 glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);

,否则使用相同的方法.

我的理由是,图形卡可能能够跳过已经固体的区域的着色器执行.我没有测试过,所以在实践中可能没有任何区别.

正如本·苏普尼克(Ben Supnik)所说,

其他推荐答案

做到这一点的最佳方法是用颜色和alpha的单独混合功能来渲染整个场景.如果您使用的是经典的非预皮混合功能,请尝试GlblendFuncseParateOes(gl_src_alpha,gl_one_one_minus_src_alpha,gl_one,gl_one,gl_one)将场景渲染到FBO.和glblendfuncseparateoes(gl_one,gl_one_minus_src_alpha)将FBO渲染为屏幕.

它不是100%准确的,但是在大多数情况下,不会产生意外透明度.

请记住,旧硬件和某些移动设备(主要是OpenGL ES 1.X设备,例如原始iPhone和3G)不支持分离的混合功能. :(

本文地址:https://www.itbaoku.cn/post/2091197.html

问题描述

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>)

推荐答案

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.

其他推荐答案

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.

其他推荐答案

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. :(