Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Default Blending Incorrect?

Discussion in 'Shaders' started by Jodon, Oct 26, 2013.

  1. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Hello,

    I'm fairly new to shaders and am working on software that does a lot of image compositing. What I've noticed is that almost all default shaders use this blend mode:

    Blend SrcAlpha OneMinusSrcAlpha

    This is fine for the vast majority of cases since DstAlpha (the alpha value that resides in the framebuffer) never appears in any equations. However, I'm working on a product that does need a proper DstAlpha value and I'm finding this equation has little utility, so I'm wondering why is it used everywhere?

    The default equation equals SrcA*SrcA + DstA(1 - SrcA) which causes overlapping alpha to combine to a color that is MORE transparent. Is there a reason for this?

    Here's my example, blending colors with half-opacity:

    Backbuffer contains black, zero alpha. Apply Red at half opacity then Green at half opacity using RGBA:

    Apply (1,0,0,0.5), (0,0,0,0) => (0.5,0,0,0.25)
    Apply (0,1,0,0.5), (0.5,0,0,0.25) => (0.25, 0.5, 0, 0.375) ... notice the 0.375 is more transparent than any of the inputs?

    Notice how alpha is now at 0.375? It's because SrcAlpha is getting multiplied by itself during the blend equation. Using the linear blend a + d(1-a) seems to make more sense.

    Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha

    Apply (1,0,0,0.5), (0,0,0,0) => (0.5,0,0,0.5)
    Apply (0,1,0,0.5), (0.5,0,0,0.5) => (0.25, 0.5, 0, 0.75)

    This appears to be correct. So my question is ... why isn't this equation used?

    Cheers.
     
  2. MikeEnoch

    MikeEnoch

    Joined:
    May 31, 2014
    Posts:
    6
    Sorry to bring up this old thread, found it while searching for a related shader problem and thought it deserved some kind of reply in case other people drop by...

    I believe the reason is essentially just convention, based on how computer graphics have been done for decades, and based in part on the limitations of the technology and usage of transparency when the convention was created. It's what people are used to, although as you found out it's not always the best way to do transparency.

    Alternatives first came to my attention a number of years ago, when there was an effort in the XNA scene to get everyone to switch over to a different (arguably better) alpha model that works much better for doing composition, compression, and a bunch of other things I can't remember. It's called pre-multiplied alpha, if you search around there's actually been a lot written about it.

    There are two XNA articles here, first one quickly explains premultiplied alpha, the second article is when they made the big switchover in the toolset (maybe less interesting now):
    http://blogs.msdn.com/b/shawnhar/archive/2009/11/06/premultiplied-alpha.aspx
    http://blogs.msdn.com/b/shawnhar/archive/2010/04/08/premultiplied-alpha-in-xna-game-studio-4-0.aspx

    If you are doing a lot of composition then it's definitely worth looking into this stuff.

    So yeah, depending on what you're doing, the default alpha setup might not be best, but it's the default because it's what most people understand as "normal" and requires relatively little explanation (except when it goes wrong!).
     
    Jodon likes this.
  3. Dolkar

    Dolkar

    Joined:
    Jun 8, 2013
    Posts:
    576
    Another reason is that the ability to specify different blending mode for the alpha channel is / was not supported on some platforms. Because in most cases, the target alpha values are never used, it does not make a difference in the end and this way avoids problems with platform support.
     
    Jodon likes this.
  4. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Mike,

    Thank you for taking the time to respond in such detail. A while after I had posted, I found out more about pre-multiplied alpha, which as you suggest, appears to be what I was looking for. Upon further reflection though, I realized it's not exactly the same thing. I now believe this to be a bug/oversight on Unity's part.

    For reference, the default Unity blend mode is:

    Blend SrcAlpha OneMinusSrcAlpha, SrcAlpha OneMinusSrcAlpha

    The blending mode for pre-multiplied alpha is:

    Blend One OneMinusSrcAlpha, One OneMinusSrcAlpha

    Note I'm being explicit about separating Alpha from Color here. The equation I suggest is correct is as follows:

    Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha

    Note the color value is not determined like in pre-multiplied mode, it is only the target's alpha value that changes. Let's go through an easy scenario and forget about numbers (which is how I confused myself to begin with):

    Scenario: you have pure red with 50% transparency.

    Pre-multiplied Result: pure red as the on-screen color. 50% transparency stored in the target.
    Mine: 50% red as the-onscreen color. 50% transparency stored in the target.
    Unity: 50% red as the-onscreen color. 25% transparency stored in the target (50% of 50%).

    I can't think of a single situation where you want Unity's default blending mode.

    Dolkar's post does sound plausible, thanks as well for replying! However, I don't think this would the reasoning for the issue. If the target alpha values are never used, why shouldn't they at least default to correct? I don't have a great deal of platform-specific knowledge on mobiles. Are you aware of a platform that doesn't support writing these values to the target, but still supported reading those values back from the target? I believe you need a 32-bit backbuffer in order to correctly store/read the alpha values, but I can't think of a platform that would support 32-bit backbuffers and not allow you to determine the alpha value to write.

    It would seem to me regardless of what default blend mode you chose, since no shaders read-back the alpha value to begin with, you would have to write a custom shader to see the error. Indeed I had to write a custom shader to see the error, but in this case it's not that the hardware has stopped me from seeing the correct value, but rather than Unity's default value is incorrect. It doesn't seem like an odd choice (like I had initially assumed); it seems like an oversight.
     
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,589
    I have another one which I think is rather easy to follow, but still providing very useful information: Alpha Blending: To Pre or Not To Pre
     
  6. Dolkar

    Dolkar

    Joined:
    Jun 8, 2013
    Posts:
    576
    Of course, all platforms support writing alpha values as well as reading them. What I was referring to is that some platforms might not support different blending factors for them, making a setup like this invalid: Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha
    However, I've searched around and it seems this was supported as far back as in shader model 2.0, so I think it's safe to say all platforms support this.

    Here's another point though... There is not really the one correct way to calculate the output alpha values. There is no standard that describes what you can generally expect to be stored in the alpha channel. As I understand it, the "premultiplied" blending factors for the alpha basically produce the combined transparency of all the written colors, so for individual transparency values of 0.5, 0.2 and 0.75 you would get a value of 1 - (1 - 0.5) * (1 - 0.2) * (1 - 0.75) = 0.9. This could prove useful for some effects, but, for example, other effects might require the last written alpha to be available instead, with the blend mode of One Zero for the alpha. Different setups could reserve the alpha channel for special occasions, like the glow factor, where non-glowing objects would not touch it at all.

    Anyway, I agree with you that the default blend mode does not produce any meaningful alpha values and yours would be preferable. It would be extremely difficult to change that now, though, when there are already thousands of shaders doing it the "wrong" way.
     
    Jodon likes this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    It's not an oversight, it's the fact once you've gotten to the point of doing the compositing you usually don't care about the alpha. In fact in many cases the frame buffer being rendered to doesn't even have an alpha, especially in the early days of real time graphics where the calculating alpha was just wasted memory and computation. Also many of Unity's shaders that do use alpha blending use ColorMask RGB so they don't render anything into the alpha even if the frame buffer has one or not.

    Because of the way graphics cards work, and the assumptions that have been made for several generations of hardware, doing the same math operation to four channels is as fast as doing it to just one so doing Blend SrcAlpha OneMinusSrcAlpha (generic alpha blend) is cheaper than doing Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha because the later means doing the math on RGB, then doing the math on A and this actually takes twice as long for a lot of hardware!

    Now if you think about that too much you might question why use SrcAlpha OneMinusSrcAlpha (alpha blend) when One OneMinusSrcAlpha (premultiplied alpha blend) is less math, shouldn't that be faster? And the answer is yes, it was faster, but almost everyone used SrcAlpha OneMinusSrcAlpha (alpha blend) so again hardware has optimized this path so both are the same speed. On most modern hardware SrcAlpha One (additive) is the same speed as SrcAlpha OneMinusSrcAlpha (alpha blend).

    As for why we ended up with alpha blend instead of premultiplied alpha blend as the standard in real time graphics instead of premultiplied which is the standard for non real time image compositing, that's a harder question but it comes down to flexibility and image compression.

    With "standard" alpha blending you can use a color and alpha from separate assets and they'll still blend properly where as with premultiplied the color and alpha are always tied together. This might seem like an odd thing to do, but it also means you can use different compression techniques on the color and the alpha and not get strange halos, which is what almost all modern real time compression schemes do. This isn't an issue for non real time image compositing since usually the images are stored in an uncompressed format, a losslessly compressed format. With video compositing the color and alpha are often stored in two files, but with identical compression settings which usually results in similar artifacts.

    There's also the issue of gamma. You have to be careful about the gamma space the color is stored in and how it's going to be used else you'll get dark or light fringing with premultiplied. Again this isn't an issue for non real time since you're pretty much working in sRGB or at least a gamma of 2.2 100% of the time and the programs that deal with split alpha are smart about handling it. Real time graphics, especially now, are doing stuff in linear color space sometimes, gamma space other times.
     
    Last edited: Sep 11, 2015
    Jodon likes this.
  8. Dolkar

    Dolkar

    Joined:
    Jun 8, 2013
    Posts:
    576
    I'm not so sure if different blending factors have different performance characteristics... The blending math (on desktop GPUs) is not done in the general shading cores, but rather in fixed function render output units, so completely different rules apply there. Similarly, I believe that the only ColorMask setting that speeds up the rendering is 0, which disables the color output altogether. Due to how GPU memory is accessed, any other setting needs to read and write the full texel data, regardless of the masked components. Because of that, I suspect that ColorMask RGB could actually be slower than the default RGBA when blending is disabled. The former would have to read the old value, replace the rgb components and write it back in one piece.
    It's something that would need some hard and honest profiling though, if your life depends on it.
     
    Jodon likes this.
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    For blend modes I said "it was faster", and still is on some mobile GPUs. Most certainly hasn't been on desktop for a long time. ColorMask RGB likely doesn't have any impact on performance one way or another, it's just the fact it doesn't taint the framebuffer alpha. I didn't mean to intimate a performance impact.

    The math for alpha blend is one operation less than premultiplied.

    SrcColor * SrcAlpha + DstColor * (1 - SrcAlpha)
    vs
    SrcColor + DstColor * (1 - SrcAlpha)

    Which is why most compositing programs, like video editors, photo editors, and even web browsers, use premultiplied. When these were all calculated on the CPU it mattered. Today it doesn't as most of these are being accelerated by GPUs too but the workflow stuck.

    There's even been some stuff that stores the alpha inverted to get rid of that one minus, though generally multiplication has been slower than adds, and the latter half of that is a MAD (multiply and add) anyways which most CPUs and GPUs can do in the same speed as a single multiply.
     
    Last edited: Sep 11, 2015
  10. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Between the two of you, I've decided to check the history of the OpenGL ES API. It looks like glBlendFuncSeparate was only introduced in OpenGL ES 2.0. That means the original iPhone and Android would not be able to use the separate alpha equations and you would be stuck with SrcAlpha, OneMinusSrcAlpha. That makes the most sense, though I still strongly believe it should be fixed by now since those platforms haven't been supported in ages.

    Thanks for taking the time to write. Cheers.