I'm having a hard time understanding how premultiplied blend mode is suppose to work in Unity

In Unity’s shader graph with HDRP, this means that the transparency is controlled by both color values and the alpha values.
In URP, using the same shader graph setup, the premultiplied blend mode behaves differently than it does in HDRP, and the transparency is controlled only by the alpha values and premultiplied seems to act the same as alpha blend in URP.


This shader I had for HDRP.

I’m using version 10.4 of shader graph/HDRP/URP for this.

I’ve read this: https://developer.nvidia.com/content/alpha-blending-pre-or-not-pre
but I’m not too sure what “SourceColor” or “DestinationColor” is referring to.

Here’s my current understanding: in engine, to create transparency, the image on top is blended with the image behind it. For additive, the image is add to the background. For multiply, the imagine is multiplied with the background. For alpha blend, the image is masked using the alpha channel?
And the premultiplied blend mode is meant to use the alpha channel to determine where the texture is additive (black) and where is alpha blend (white). Where it is Black in the alpha channel, RGB black would be come transparent.
For premultiplied blend mode to work, it would have to use premultiplied textures, which are textures that have the RGB multiplied with it’s own alpha channel.

So I came up with this formula to use in Unity Shader Graph:

Output RGB = Texture RGB * ( Vertex RGB * Tint RGB) * Emission Intensity
Output Alpha = {[Texture Alpha * (Vertex Alpha * Tint Alpha)] + [(Texture RGB adds together) / 3]} * Opacity


I avg out the rgb so the alpha isn’t just controlled by 1 of the RGB channel when I mess with tint/vertex color.

With that formula I made a URP premultiplied shader graph, and it seems to behave pretty similarly to the HDRP premultiplied shader graph, where the transparency is controlled by both alpha and color values.
But here I run into a problem, the image that shows up has distinct black edges.

So I ended up making s URP shader graph using this formula for the alpha instead:

Output Alpha = {(Vertex Alpha * Tint Alpha) + [(Texture RGB adds together) / 3]} * Texture Alpha * Opacity

Once I do this, the black edges are reduced (but still there).
This shader can also use textures that are not premultiplied, this removes the black edges completely and makes the image look brighter (and better imo).


The testing textures I used.


The results.
URP (Left) is the first URP shader, HDRP (center) is the HDRP shader), and URP 2 is using the second, revised URP shader. URP 2 is the only one here using the non-premultiplied texture.

Is my understanding of what on blend mode and premultiplied correct here?
Is Unity’s premultiplied working the way it is suppose to in either HDRP or URP?
Does my revised shader still counts as premultiplied blend mode? Does that even matter if it looks good and seems to have the right effect? Does it have the right effect??
Please let me know if there’s something I can do to improve these basic shaders.

Thank you for your help.

P.S. Are there any books on this kind of topic that I should read up on? I’d like to better understand how shaders work.

syntax analogous to layers would be like
source = Layer 1
destination = Layer 2 (Below 1)

all blending is performed by summation in the final step:

Source * (operation) + Destination * (operation)

so before you add them you may ‘operate’ on them with a number of options

One = multiply by 1 = no change
DstColor = multiply by the Destination color values (incase you want to tint your source with the destination below it)
Zero = multiply by 0 = removes this

Pre multiplied alpha blending = Blend One, OneMinusSrcAlpha

  • Source behaves identical as an additive layer (linear dodge layer in photoshop)
  • The destination is ‘masked’ identically to a multiply layer by 1-alpha (e.g. inverted alpha)

But It’s an ongoing bug with LWRP -> URP
:face_with_monocle:

snippet

 #ifdef _ALPHAPREMULTIPLY_ON   
            Color *= Alpha;
    #endif
            return half4(Color, Alpha);

https://fogbugz.unity3d.com/default.asp?1260085_urumn09cr2dqcnam
That breaks Pre-mult because it multiplies Color by alpha.
Identical & redundant to Blend srcAlpha, OneMinusSrcAlpha

It darkens source color creating black halos on VFX as well as causes it to go ‘invisible’
i don’t think you can salvage the blend mode in the shader preset pulldown, you have to compile and make it by hand CG/HLSL or w/e language afaik the pulldown in Shader graph is broken and has been for a while

it is unacceptable - Unity 2020.3.14f1


not technically. Alpha operates as a mask over the background e.g. ‘destination’ before source is added. Technically Premult is additive as source is left alone (*‘one’). it is the reduction of the destination by the alpha that occludes what is underneath that determines how much can shine through adding brightness.

you want to author the color of your textures as if it will be used in an additive blend mode < So you illustrate them on a black background

doing this in photoshop layers : this is how it will and does behave as intended
premultExample

in a shader it is ostensibly

texture.rgb * vertex.rgb -> output shader rgb
texture.a * vertex.a -> output shader alpha

any overlap of alpha processing color will alter the basic intended use

also another post about some of this that might contain helpful things - but I tried to include everything here
another for alpha dissolve and the bug
another with scrolling and dissolve

2 Likes

Just to be clear for me

Premultiplied alpha blending = Blend One, OneMinusSrcAlpha
means Source * (One) + Destination * (OneMinusSrcAlpha), right?
Is alpha blending then Source * (SrcAlpha) + Destination * (OneMinusSrcAlpha)?
And additive is Source * (One) + Destination * (One)?

So what Destination * (OneMinusSrcAlpha) does is to mask out/“remove” the destination base on the inverted source alpha? Is this usually what happens when one object overlaps another in engines? The object behind get masked out? Is this what the normal blend mode in Photoshop does?

So is the HDRP premultiplied blend mode is the one working properly?
It seems like I’ve been creating my premultiplied textures wrong. I should just be producing them the way I do for additive textures? Draw on a black background (with alpha) but without multiplying it with anything pre-engine?

Thanks for the thorough reply and clearing up my confusion. I think I understand now. Much appreciated.

Man, this is a long term bug though.

you got it :+1:

yes. Under the hood the blending math is nearly identical. A normal Layer blends like this
Source * OneMinusSourceTransperancy + Destination * SourceTransparency

note

  • Photoshop deals with Transparency: 100% = invisible
  • Alpha is the invert of that: 100% = opaque

Alpha vs transperancy?

Film vfx (where 3D CG and RTVFX get’s its precedent) was projecting onto positive film using Alpha while photoshop is based on enlarging negatives in a photo lab so they used the invert of Alpha which is a more relatable concept for that purpose.

Also means photoshop has never really supported RGBA PNGs the way you will need; you need a plugin called SuperPNG to author PNG without causing a change to RGB. Photoshop natively Ceilings RGB white when transparency = 1, so they compress RGBA pixel as 1,1,1,1


  • yes illustrating for premult blending is identical to Additive blending in terms of RGB. painting what you want on black is the idea of it being pre-multiplicative

  • Premult blend expects an alpha value to operate on destination. If you just supply it a value of 1 then this is nearly* identical to Alpha blending.

*the catch being all alpha values < 1 operate non-proportionally since you are adding values to a multiplied destination - You will always get brightness on the feathering of VFX when Premultiplied (see blue laser in my post above)
X + [multiplied Y] means X’s addition are proportionally larger when blended on non-black content.

  • Premult also means color and alpha share no overlapping operation; to make VFX fade out you must tint RGB & Alpha both to black

  • It also means you can paint black into the Alpha where you specifically want to remain a ‘hot spot’… or white in the alpha to keep darker/opaque spots.

1 Like

Good to know about the SuperPNGs, I’ve just been saving my files as targa. Thanks.
What does it mean by ceilings RGB? Like setting it to the max value of 1? So when a pixel is fully transparent, it is saved as a white pixel in regular PNG in PS?

Thank you for your time and clear explanations! :smiley:

yes exactly
Transparency 1, compresses RGB 1,1,1 white. This is why the images have white bounding lines or boxes when you look at Color channel in Maya or Unity etc

1 Like

I made a bug report that can be vote on in the issue tracker. I’ll leave the link here so if people want, they can vote for it.

I meant to comment on this earlier.

Unity’s internal shader devs have consistently misunderstood premultiplied alpha for the last 5 years, and it’s incredibly frustrating.

The original default particle shader was a proper premultiplied alpha shader in Unity 5.0 and prior, but they “fixed” it in Unity 5.3 … by modifying the default premultiplied shader to no longer be premultiplied. There were a lot of complaints that the default particle shader didn’t fade when the alpha was reduced, which is expected since it’s was a premultiplied alpha shader, but obviously confusing for those unfamiliar with it. So they modified that shader so the alpha also modifies the opacity, but they did it wrong. The shader multiplied the output RGBA by the alpha to incorrectly mimic traditional alpha blending, but can take a premultiplied alpha texture. The “correct” fix would have been only multiplying the RGB, but because it’s also multiplying the alpha by itself it produces a broken output. I say “correct” there, but it depends on if you consider a shader to be “a premultiplied shader” depending on how it handles the color multiplier’s alpha value & output blend mode, or just if it works with a premultiplied input texture. Several of us noticed this broken behavior immediately and pointed it out, but they ignored the complaints for multiple years. Basically they made the change and then they removed any resources for working on particle or shader related stuff, and by the time they put more resources on it a few years later the argument was “well, someone might be relying on that broken behavior now, so we can’t change it”.

Fast forward a few years and they brought out the “Particle/Standard” shaders. For the most part these were a huge upgrade from the older shaders in terms of functionality and useability. But they only exposed the same set of blend modes for both the lit and unlit versions of the shader. This meant there were both “Fade” and “Transparent” modes. For lit shaders Unity uses these two modes to differentiate between having the alpha make a surface invisible, and making a surface transparent (ala glass). The main difference is whether or not specular reflections appear in the transparent area or not. But under the hood it uses premultiplied alpha for “Transparent” mode, as specular highlights are always additive. This works as advertised (mostly) and is great.

For the Particle/Standard Unlit shader also has Fade and Transparent, and the Transparent mode does also use a premultiplied alpha blend for the fragment shader’s blend mode. The first release of the shaders used the roughly same broken “fix” as the old default particle shader, except it doesn’t even work with a premultiplied input texture. A handful of us pointed this out and suggested they add a proper premultiplied option. They instead just changed it so both fade and transparent produce identical results and neither take a premultiplied texture as an input, which wasn’t what we had hoped for.

And now we’re to Shader Graph, where they’re basically making the same mistake again, even with even more people noticing the issue and calling it out. And we’re now a few years into Shader Graph and we’re starting to hear similar “well, some people might be relying on that broken behavior” response again.

Aw man, that’s really frustrating. I really hope they fix it this time and keep it that way.

To be fair to Unity, it does work properly in the HDRP. That team is made up of generally more senior AAA devs from the game industry as best I can tell, and so they do seem to understand premultiplied alpha’s usefulness for custom shaders.

But yeah, still broken for the URP with generally way fewer features than the HDRP. The last major change to Shader Graph seems to have given up on the attempt at providing the “one shader graph to rule them all work on all SRPs” idea and given more power to the individual SRP teams to add features they want to Shader Graph. The side effect of that being that the URP Shader Graph basically stalled out and HDRP Shader Graph has been getting a ton of new features.

1 Like

afaik a ‘cheap’ sufficient compromise suggested was add to the Premultiply blend an ENUM variant pulldown FadeWithAlpha = True / False

If URP master nodes had that switch we’d be able to set it ‘false’ while the default:True wouldn’t break other projects expecting Color *= Alpha

1 Like

It’s a bug in URP, which haven’t been fixed. In my projects, I’ve fixed this by creating a local copy of the URP package (go to the package cache folder and simply copy the Library\PackageCache\com.unity.render-pipelines.universal to somewhere in your hard drive or under the project itself), then fixing the UnlitPass.hlsl by commenting out that unnecessary alpha multiplication:

half4 frag(PackedVaryings packedInput) : SV_TARGET 
{    
    Varyings unpacked = UnpackVaryings(packedInput);
    UNITY_SETUP_INSTANCE_ID(unpacked);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(unpacked);

    SurfaceDescriptionInputs surfaceDescriptionInputs = BuildSurfaceDescriptionInputs(unpacked);
    SurfaceDescription surfaceDescription = SurfaceDescriptionFunction(surfaceDescriptionInputs);

#if _AlphaClip
    clip(surfaceDescription.Alpha - surfaceDescription.AlphaClipThreshold);
#endif

/*
// comment this
#ifdef _ALPHAPREMULTIPLY_ON
    surfaceDescription.Color *= surfaceDescription.Alpha;
#endif
*/

    return half4(surfaceDescription.Color, surfaceDescription.Alpha);
}

You can then add the local version of the URP package following these instructions: https://docs.unity3d.com/Manual/upm-ui-local.html

You can also put the fixed package into git/GitHub by putting something like this in the packages.json:

"com.unity.render-pipelines.universal": "https://github.com/YourAccount/FixedURP.git#master",

The only issue with git is that you need to make the repo public in order to save some headaches with logins… Including the package in your project might be better idea.

1 Like

Thanks for the fix! I’ll give it a shot in the future.