• OpenGL -> Problem with alpha values in framebuffers
    23 replies, posted
Hi. :tinfoil: I am attempting to render transparent textures to a fully transparent-black framebuffer object. However, there is a problem. You can clearly see that the alpha-values of the framebuffer are wrong compared to the original sprite. I've set up a Visual Studio 2010 project to illustrate this. Here's a screenshot of it: [IMG]http://i.snag.gy/05Y8A.jpg[/IMG] The left side is the original sprite rendered directly and the right side is the sprite rendered onto a framebuffer. I have tried cycling through every possible combination of blend-modes, but none of them looked exactly like the original sprite. Currently, the blend-func is set to GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA. I've got all of my source code on github for you to check out: [URL="https://github.com/Landeplage/Amigo"]Amigo at GitHub[/URL] The problem should lie in the RenderTarget.cpp file (I'm used to calling framebuffers rendertargets as I come from C# and XNA). You can see in TestState.cpp how I actually use the RenderTarget class. [URL="https://github.com/Landeplage/Amigo/blob/master/Amigo/TestState.cpp"]TestState.cpp[/URL] [URL="https://github.com/Landeplage/Amigo/blob/master/Amigo/RenderTarget.cpp"]RenderTarget.cpp[/URL] Any help is appreciated. I've been stuck with this problem for two or three weeks now, so I hope someone knows what's going on. Let me know if you have questions about why my code is so shitty. :v:
Okay, I have now experimented with some stuff. If I render to a completely transparent buffer, it works perfectly using this blendfunc: GL_ONE, GL_ZERO [IMG]http://i.snag.gy/0aNwt.jpg[/IMG] However, if I want to render something on top of what's already there using the same blendfunc, the new pixel-information will override the old. This means that what's already there gets washed out completely. Here, I am rendering a gradient texture on top of each of the original textures. Notice how the overlaying texture seems to wipe out the pixels under it on the right side (the left side is drawn directly, the right is drawn via a framebuffer). [IMG]http://i.snag.gy/6Ld3h.jpg[/IMG] So, one would imagine that to fix this, I would need to set the blendfunc to SRC_ALPHA, ONE_MINUS_SRC_ALPHA (which is what I use when I draw sprites directly) but that doesnt work either. The reason that doesn't work is that if a sprite is overlapping both transparent pixels and opaque pixels in the buffer, it should look fine on the opaque pixels, but on transparent pixels it will blend with whatever color I have cleared to! I have tried to illustrate this here. I am rendering a transparent box using the aforementioned blendfunc. You can see that the parts where the pixels are transparent, the box fades out almost completely. It is only visible on top of the opaque pixels! [IMG]http://i.snag.gy/0EkKf.jpg[/IMG] How could I get around this? Is it simply not possible to get a texture to blend correctly onto a transparent and an opaque surface at the same time?
Use glBlendFuncSeparate, the problem is that with only glBlendFunc you are setting both colour and alpha blend modes at the same time. I don't know the correct alpha blends for what you need off the top of my head but it shouldn't be too hard to figure it out.
[QUOTE=Philly c;36786981]Use glBlendFuncSeparate, the problem is that with only glBlendFunc you are setting both colour and alpha blend modes at the same time. I don't know the correct alpha blends for what you need off the top of my head but it shouldn't be too hard to figure it out.[/QUOTE] Okay, I see. I don't know how what the right formula is either. I've read about it [URL="http://www.opengl.org/wiki/Blending"]here[/URL] for the last hour or so, but I can't make sense of it. Edit: I've gotten closer to something that looks right now. It seems that the alpha-blending is still off. However, it looks like color-blending works. I'm not sure, though! Here, I am using [B]glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_DST_ALPHA);[/B] [IMG]http://i.snag.gy/hWQqC.jpg[/IMG] Edit2: Could it have something to do with it blending twice? Once when it gets rendered to a framebuffer, and again when it draws the texture resulted from the framebuffer?
This is a pretty common issue with rendering user interfaces to intermediate surfaces - things will blend with the clear color of the surface. As you've noticed, turning off blending "fixes" this but then you can't blend things on the intermediate surface. Using pre-multiplied alpha might work for you. As you probably already know, the alpha-blend equation looks like this: source_color * source_alpha + existing_color * (1 - source_alpha) AKA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) If you take all of your textures and pre-multiply the colors (RGB) by the alpha (A), you get something like this: source_color * 1 + existing_color * (1 - source_alpha) AKA: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) So: 1) Start with an all-black, fully opaque framebuffer, AKA <0, 0, 0, 1> for every pixel 2) Pre-multiply all of your texture colors by their alpha, but keep the alpha itself intact 2) Render everything to this frame buffer with glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA). This will add up all the pre-multiplied colors and multiply together all of the alphas. For example, if you start with color X, then you blend something 50% transparent on top, you'll end up with only 50% of color X. Then if you blend something 50% transparent on top of that, you'll end up with only 25% of color X. This is why you want to multiple together all of the alphas. 3) Render this framebuffer with glBlendFunc(GL_ONE, GL_SRC_ALPHA) This *should* work, but no guarantees. Let me know how it turns out!
[QUOTE=Mordi;36793867]Here, I am using [B]glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_DST_ALPHA);[/B][/QUOTE] I was just looking back at this thread and noticed you were using blends for premultiplied alpha here which I didn't see before. However icantread seems to have answered this better I would have. While I don't think you really need to use premultipled alpha, it can't hurt to do it that way since it is better.
Now that Philly c mentioned it, you might not need to pre-multiply your colors - I think you can just render with glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_SRC_ALPHA) which will have the same effect as glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA) using pre-multiplied colors.
Ooh. Helpers! [QUOTE]1) Start with an all-black, fully opaque framebuffer, AKA <0, 0, 0, 1> for every pixel 2) Pre-multiply all of your texture colors by their alpha, but keep the alpha itself intact 2) Render everything to this frame buffer with glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA). This will add up all the pre-multiplied colors and multiply together all of the alphas. For example, if you start with color X, then you blend something 50% transparent on top, you'll end up with only 50% of color X. Then if you blend something 50% transparent on top of that, you'll end up with only 25% of color X. This is why you want to multiple together all of the alphas. 3) Render this framebuffer with glBlendFunc(GL_ONE, GL_SRC_ALPHA)[/QUOTE] Let's see here. 1) Easy enough: [cpp]// Clear the buffer glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT);[/cpp] 2) If I understand you correctly, you want me to set this blendfunc before drawing stuff to my framebuffer. Or should I be doing something to the textures themselves before rendering? [cpp]// Set blend mode glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ZERO, GL_SRC_ALPHA);[/cpp] 3) This is how I draw the framebuffer texture itself: [cpp]// Draw the rendertarget glBlendFunc(GL_ONE, GL_SRC_ALPHA); renderTargetTest->Draw(x, y);[/cpp] Here's how it looks! [IMG]http://www.pasteshack.net/images/882564001343185171.png[/IMG] I also tried what you described in the post above (glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_SRC_ALPHA)), which turns out like this: [URL="http://pasteshack.net/images/092572001343185284.png"]http://pasteshack.net/images/092572001343185284.png[/URL]
This is a guess that makes sense to me now but it probably won't work: Clear to 0, 0, 0, 0 and then glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA) for rendering to the target. When you want to render that final texture back to an opaque target (back buffer, or whatever) use glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE).
Made it look like this, Phillly c: [URL="http://pasteshack.net/images/330904001343187120.png"]http://pasteshack.net/images/330904001343187120.png[/URL] Edit: Feel free to tinker with it yourselves: [url]https://github.com/Landeplage/Amigo[/url] Instructions at the bottom of first post!
Question, why not just use scissor testing and draw everything directly? IMO framebuffers seem to be a bit overkill for what you're trying to accomplish...
Oops, I think I made a mistake... Render to the frame buffer with glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA), then render the frame buffer to the destination with glBlendFunc(GL_ONE, GL_SRC_ALPHA) [editline]25th July 2012[/editline] [QUOTE=robmaister12;36923132]Question, why not just use scissor testing and draw everything directly? IMO framebuffers seem to be a bit overkill for what you're trying to accomplish...[/QUOTE] Caching
I wanted to compile your code but you have a lot of dependencies I didn't really want to put together myself. Not sure what I was thinking with my last guess. Sorry i'm doing it blind and not being very helpful but I don't really have any code of my own available right now. glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) or am I having a total maths failure?
robmaister12: I haven't even heard of scissor-testing. :v: icantread49: [IMG]http://pasteshack.net/images/596453001343188763.png[/IMG]
[QUOTE=Philly c;36923179]glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE)[/QUOTE] Using this, I have something very close: [t]https://dl.dropbox.com/u/11093974/Junk/transparent1.png[/t] As you can see everything is much darker on the right. We're clearing to 0,0,0,0 at that point. If I clear to 1,0,0,0 this happens: [t]https://dl.dropbox.com/u/11093974/Junk/transparent2.png[/t] It's bleeding in the color of the background even though it's not alpha'd in. That's because of this GL_ONE_MINUS_SRC_ALPHA thingamajig, I think. Decided to test in a separate project of mine with a similar problem. What I did was draw a semi-transparent texture (0.9 alpha) on a red background (just glClear()). Then I rendered that same texture to a cleared (to 0,0,0,0) render target and then drew that render target on a red background. I took a screenshot of both and made this gif out of the two screenshots: [img]https://dl.dropbox.com/u/11093974/Junk/test.gif[/img] The slightly less transparent (slightly more red) one is the render target. In this case (using GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA for everything) the transparency appears noticeably less for the render target. Whenever using glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) it is almost the same, but gets noticeably different whenever the alpha value gets further from 1. If I render with 1 it looks fine. 0.9, it's slightly different between the two. 0.5 and the framebuffer version is barely visible. Perhaps the issue here has nothing to do with the blend function? It could be a limitation in frame buffers altogether, or maybe an issue with the cards. [del]I know Nvidia cards have a history of being weird when it comes to frame buffers, which is what I'm using.[/del] Also occurs on ATI cards, identically it would appear. Maybe there's something else we're missing whenever creating the framebuffer? [b]EDIT[/b] I think I found the solution. Found [url=http://stackoverflow.com/questions/1295657/render-to-texture-problem-with-alpha]this very helpful answer on stackoverflow[/url]. The reason for the issue I stated is because I am drawing with 80% transparency, then drawing with 80% transparency again. The solution is to render the frame buffer with glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE), as it seems to do the trick nicely, then drawing the actual framebuffer with: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) This may cause other issues down the line (drawing the render target over other transparent objects in the world), but that remains to be seen. This solves this specific problem, it would appear (drawing the sprite twice, one over the other): [t]https://dl.dropbox.com/u/11093974/Junk/transparent5.png[/t]
It seems to me that clearing to red and having it appear is not an issue at all but the expected behaviour of doing that. GL_ONE for both source and destination alpha makes sense because you're just adding the two together. Unless i'm a fucking idiot that is the expected result and I don't know why I didn't see it before. I'm also guessing that, because the blend multiplies the rendered texture with the black target it effectively creates a premultiplied alpha texture and that is why using the GL_ONE, GL_ONE_MINUS_SRC_ALPHA blend is necessary when using it afterwards. Clearing to 1, 1, 1, 0 might have a different effect. It would be useful to dump the contents of the target to verify exactly what you're getting from it, unless you're already happy it's a solved problem.
Yeah, dude. That fixed it! [IMG]http://pasteshack.net/images/506691001343218294.png[/IMG] There is still a slight difference in that the cleared color (0, 0, 0, 0) shows through when something is rendered on top of transparent pixels: [IMG]http://pasteshack.net/images/309034001343218585.png[/IMG] But that's hardly noticable compared to my own results!
That's a damn fine looking UI.
Great UI. I'm trying to get all the dependencies working with VisualStudio2010, but still failed to compile short of a few unresolved externals. Do you have a msvc project with all included dependencies compilable? I'd love to check this out and contribute where I can...but if I can't get the deps right, I'm gona have to start another gui-project :(
[QUOTE=petervy;37113918]Great UI. I'm trying to get all the dependencies working with VisualStudio2010, but still failed to compile short of a few unresolved externals. Do you have a msvc project with all included dependencies compilable? I'd love to check this out and contribute where I can...but if I can't get the deps right, I'm gona have to start another gui-project :([/QUOTE] Here you go, Petervy. [url]https://github.com/Landeplage/Amigo[/url] Although this probably won't help your problem with dependencies, since this has to do with your own setup I think. Not quite sure. Haven't really thought of that, actually.
[QUOTE=Mordi;37114427]Here you go, Petervy. [url]https://github.com/Landeplage/Amigo[/url] Although this probably won't help your problem with dependencies, since this has to do with your own setup I think. Not quite sure. Haven't really thought of that, actually.[/QUOTE] Gah, without your complete copy of the dep libraries(actual version used), I had no luck in getting various in various dep libraries to work properly (a lot of guessing and latest version don't quite work either) ... results in unresolved externals. I give up...I'm gonna have to build one from scratch rather than wasting anymore time :)
Just thought i'd post to correct myself, I was wrong about adding the two alphas, I kind of thought I was. [URL]http://stackoverflow.com/a/3661456[/URL] alphaBelow + (1.0 - alphaBelow) * alphaAbove Rearranged to make it more obvious to opengl terms (1.0 - alphaBelow) * alphaAbove + 1 * alphaBelow aka GL_ONE_MINUS_DST_ALPHA, GL_ONE
Render to software framebuffer, you won't have any issues like this.
[QUOTE=SiPlus;37163165]Render to software framebuffer, you won't have any issues like this.[/QUOTE] Hmm. Care to elaborate on this?
Sorry, you need to Log In to post a reply to this thread.