I have searched through google and found results that explain how blending works in direct3d 11. So I'm making this post just to validate on whether or not I completely understand these concepts.
For the most part, I somewhat understand the concept of blending. We blend colors by combining two colors and producing a final color. This is mostly done with two equations that direct3d has in the output merger stage.
FinalColor = (Source Color * Source Blend Factor) + (Destination Color * Destination Blend Factor)
and
FinalAlpha = (Source Alpha * Source Alpha Blend Factor) + (Destination Alpha * Destination Alpha Blend Factor)
The color sources Source Color and Source Alpha are defined by whatever the Pixel Shader outputs. And the destinations Destination Color and Destination Alpha are defined by whatever color is in the Render Target (backbuffer).
Now, i have a little bit of difficulty understanding the blend factors Source Blend Factor, Destination Blend Factor, Source Alpha Blend Factor and Destination Alpha Blend factor
Since I know that these blend factors are defined by D3D11_RENDER_TARGET_BLEND_DESC, i can use the member SrcBlend and assign it the flag D3D11_BLEND_SRC_COLOR... this would mean that the blend factor Source Blend Factor will have the color that the pixel shader outputs.
So do you think that i understand the concept of blending? or is there something that i am missing? (Feel free to correct me)
The equations for blending are even more flexible than the ones you wrote above. The generic blending equation is:
DestColor = Color1 <ColorOp> Color2
DestAlpha = Alpha1 <AlphaOp> Alpha2
While Color1 is often the output from the pixel shader, it (along with Color2, Alpha1, and Alpha2) can be any of the values in the BLEND enum. Likewise, while the blend operator is usually +, it can actually be any of the values in the BLEND_OP enum. Together these provide the complete set of all possible normalized linear blending operations. For an example of what values to use for "traditional alpha blending" see this answer.
Related
Demo https://codepen.io/Andreslav/pen/wvmjzwe
Scheme: Top left - the original.
Top right - the result.
Bottom right - rounding coordinates when extracting color.
The problem can be solved this way, but then the result is less smoothed:
coord = floor(coord) + .5;
How to make it better? Make it so that when calculating the average color, the program ignores the color of transparent pixels?
Maybe there are some settings that I haven't figured out..
Updated the demo
The result is even better after such an adjustment:
vec4 color = texture2D(texture, coord / texSize);
vec4 color_ = texture2D(texture, coordNoRound / texSize);
if(color_.a != color.a) {
color.a *= color_.a;
}
On the preview: bottom left. But this is not an ideal option, the correction is partial. The problem is relevant.
This appears to be a premultiplied alpha problem. And it's not as much of a glsl problem as it is a glfx problem.
Here's what happens:
Consider the RGBA values of two adjacent pixels at the edge of your source image. It would be something like this:
[R G B A ] [R, G, B, A]
[1.0, 1.0, 1.0, 1.0] [?, ?, ?, 0]
Meaning that there is a fully opaque, fully-white pixel to the left, and then comes a fully-transparent (A=0) pixel to the right.
But what are the RGB values of a completely transparent pixel?
They are technically ill-defined (this fact is the core problem which needs to be solved). In practice, pretty much every image processing software will put [0, 0, 0] there.
So the pixels are actually like this:
[R G B A ] [R, G, B, A]
[1.0, 1.0, 1.0, 1.0] [0, 0, 0, 0]
What happens if your swirl shader samples the texture halfway between those 2 pixels? You get [0.5, 0.5, 0.5, 0.5]. That's color [0.5 0.5 0.5], with 0.5 Alpha. Which is gray, not white.
The generally chosen solution to this problem is premultiplied alpha. Which means that, for any given RGBA color, the RGB components are defined so that they don't range from 0 .. 1.0, but instead from 0 .. A. With that definition, color [0.5 0.5 0.5 0.5] is now "0.5 A, with maximum RGB, which is white". One side effect of this definition is that the RGB values of a fully transparent pixel are no longer ill-defined; they must now be exactly [0, 0, 0].
As you can see, we didn't really change any values, instead, we just defined that our result is now correct. Of course, we still need to tell the other parts of the graphics pipeline of our definition.
Premultiplied alpha is not the only solution to the problem. Now that you know what's happening, you might be able to come up with your own solution. But pretty much all modern graphics pipelines expect that you are working with premultiplied alpha all the time. So the correct solution would be to make that true. That means:
(1) You need to make sure that your input texture also has premultiplied alpha, i.e. all its RGB values must be multiplied with their alpha value. This is generally what game engines do, all their textures have premultiplied alpha. Either every pixel must already be edited in the source file, or you do the multiplication once for each pixel in the texture after loading the image.
AND
(2) You need to convince every alpha blending component in your rendering pipeline to use premultiplied alpha blending, instead of "normal" alpha blending. It seems you use the "glfx" framework, I don't know glfx, so I don't know how you can make it blend correctly. Maybe check the docs. In case you are using raw OpenGL/WebGL, then this is the way to tell the pipeline that it should assume premultiplied alpha values when blending:
gl.blendEquation(gl.FUNC_ADD); // Normally not needed because it's the default
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
(This can be derived from the analyzing the formula for source-over alpha blending, but without the last division step.)
The above code tells OpenGL/WebGL that every time it's blending two pixels on top of another, it should calculate the final RGBA values in a way that's correct assuming that both the "top" and the "bottom" pixel has premultiplied alpha applied to it.
For higher level APIs (for example, GDI+), you can typically specify the pixel format of images, where there is a separation between RGBA and RGBPA, in which case the API will automatically choose correct blending. That may not be true for glfx though. In essence, you always need to be aware whether the pixel format of your textures and drawing surfaces have premultiplied alpha or not, there is no magic code that always works correctly.
(Also note that using premultiplied alpha has other advantages too.)
For a quick fix, it appears that the framework you're using performs alpha blending so that it expects non-premultiplied alpha values. So you could just undo the premultiplication by adding this at the end:
color.rgb /= color.a;
gl_FragColor = color;
But for correctness, you still need to premultiply the alpha values of your input texture.
Because at the rounded corners, your input texture contains pixels which are fully white, but semi-transparent; their RGBA values would look like this:
[1.0, 1.0, 1.0, 0.8]
For the blending code to work correctly, the values should be
[0.8, 0.8, 0.8, 0.8]
,
because otherwise the line color.rgb /= color.a; would give you RGB values greater than 1.
I draw from texture a lot of white traingles. But when it are drawing on yellow circle, the points which contains a small alpha value(but not equal with 0) are blended wrong, and I get some darker pixels on screen(see on screenshot, it was zoomed in). Which can be the problem?
On blue background all are ok.
As #tklausi pointed out in the comments, this problem was related to the texture interpolation in combination with traditional alpha blending. At the transition from values with high alpha to "background" with alpha=0, you will get some interpolation results where alpha is > 0, and RGB is mixed with your "background" color.
#tlkausi's solution was to change the RGB values of the background to white. But this will result in the same issue as before: If your actual image has dark colors, you will see bright artifacts around it then.
The correct solution would be to repeat the RGB color of the actual border pixels, so that the interpolation will always result in the same color, just with a lower alpha value.
However, there is a much better solution: premultiplied alpha.
Instead of storing (R,G,B,a) in the texture per pixel, you store (aR,aG,aB,a). When blending, you don't use a*source + (1-a) * background, but just source + (1-a)*background. The difference is that you now have a "neutral element" (0,0,0,0) and interpolation towards that will not pose any issue. It works nicely with filtering, and is also good for mipmapping and other techniques.
In general, I would recommend to always use premultiplied alpha in favor of the "traditional" one. The premultiplication can be directly applied into the image file, or you can do it at texture upload, but it does incur no runtime costs at all.
More information about premultiplied alpha can be found in this MSDN blog article or over here at NVIDIA.
Q : Is there a way to use the default pipeline to blend the Alpha component properly?
Problem : I'm drawing semi-transparent surfaces into a texture, then I want to blit that texture into the main frame back buffer. Normally when you use straightforward alpha blending for transparency or antialiasing, it is to blend color components into the back buffer ( which has no alpha channel by deafult ).
However, when blending into a texture that has an alpha component, the usual GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALHPA does not blend the "alpha" component properly :
Notice the problematic zone over the death star at the edge of the red and blue circles; the alpha component is supposed to be completely opaque. Note that the RGB components are blendend as expected.
So I played around with different settings for the BlendFunc and the closest to what I want is by using GL_DST_ALPHA as my dst alpha scaling factor.
There is still a problem with this approach because blending two surfaces with 50% opacity should give a combined surface of 75% opacity. But using OpenGL default formulas, I get (0.50.5) + (0.50.5) = 0.5. More disturbing, if I have 2 surfaces with 40% opacity, I get a LESS opaque surface : (.4*.4)+(.4*.4) = 0.32.
The real formula ( 1 - ( 1 - srcAlpha ) * ( 1 - dstAlpha ) ) would imply that I could use a GL_FUNC_MULT instead of GL_FUNC_ADD in the blend equation for the alpha component. Unfortunately this does not exist.
So I gave up and tried to do a solution using a shader. This becomes complicated really fast if the texture you are rendering to is also the texture you read from. Especially if you render multiple semi-transparent surfaces.
I'm looking for alternative solutions I have not already tried. Any idea is welcome.
EDIT : I also tried GL_ONE as both src and dst scaling factor. this makes the alpha component way too opaque as 1*.5 + 1*.5 will give 1 as the resulting alpha. Makes me wonder if GL_ONE and GL_DST_ALPHA would give better results.
p.s. : I used Anders Riggelsen's blending tool for the images.
The standard technique to do perfect blending is to use
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Note that it requires both the source and destination to be premultiplied, and produces premultiplied result.
Premultiplication basically means performing color.rgb *= color.a on the color. Usually you apply it to the textures when you load them, or to the final color in your shader.
Corollary: if alpha == 1, premultiplication makes no difference.
If the source is not premultiplied, but the destination is, use this instead:
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Yes, kind of.
Blending factors ONE_MINUS_DST_ALPHA, ONE may be used for blending alpha correctly. If you have glBlendFuncSeperate you can set different blend factors for color and alpha. Operation is add.
I say kind of because as I recall seperate blend func appeared late in the deprecated API.
Edit: after doing the math those blend factors are mathematically equivalent to HolyBlackCat answer.
I am just learning the intricacies of OpenGL. What I would like to do is render a sprite onto a pre-existing texture. The texture will consist of terrain with some points alpha=1 and some points alpha=0. I would like the sprite to appear on a pixel of the texture if and only if the corresponding texture's pixel's alpha = 0. That is, for each pixel of the sprite, the output colour is:
Color of the sprite, if terrain alpha = 0.
Color of the terrain, if terrain alpha = 1.
Is this possible to do with blending function, if not how should I do it?
This is the exact opposite of the traditional blending function. The usual blend function is a linear interpolation between the source and destination colors, based on the source alpha.
What you want is a linear interpolation between the source and destination colors, based on the destination alpha. But you also want to invert the usual meaning; a destination alpha of 1 means that the destination color should be taken, not the source color.
That's pretty easy.
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA);
However, the above assumes that your sprites do not themselves have some from of inherent transparency. And most sprites do. That is, if the sprite alpha is 0 at some pixel, you don't want to overwrite the terrain color, no matter what the terrain's alpha is.
That makes this whole process excessively difficult. Pre-multiplying the alpha will not save you either, since black will just as easily overwrite the color in the terrain if there is no terrain color there.
In effect, you would need to do a linear interpolation based on neither the source nor the destination, but on a combination of them. I think multiplication of the two (src-alpha * (1 - dst-alpha)) would do a good job.
This is not possible with OpenGL's standard blending system. You would need to employ some form of programmatic blending technique. This typically involves read/modify/write operations using NV/ARB_texture_barrier or otherwise ping-ponging between bound textures.
According to my understanding:
Multisampling:
Consider we have primitive with 4X multisampling and only 1 sample is covered by primitive. So it calculates the percentage (pixel coverage based on how many samples passed the test) i.e. 25% and blends with the background color and we get blended color for that pixel which is copied to all the 4 samples when fragment shader is executed.
Antialiasing:
OpenGL calculates the amount of a pixel that is covered by a primitive (point, line, or triangle) and uses it to generate an alpha value for each fragment. This alpha value is multiplied by the alpha value of the fragment produced by your shader and so has an effect on blending when either the source or destination blend factor includes the source alpha term.
Please correct me it is wrong.
In case of multisampling we get 50-50 blend but with anti-aliasing we get 25-25-50 blend, hows that?
In case of multisampling when we render one triangle it will update the FBO with 25-75 pixel color and when second triangle being rendered it will blend with the background color, so we should get background color instead of 50-50, but this is not the case.
Here by multisampling, I meant to say enabling it with glEnable(GL_MULTISAMPLE) and by antialiasing, i meant enabling it with GL_POLYGON_SMOOTH