Search Unity

BlendOp Min — How does it work?

Discussion in 'Shaders' started by Essential, Mar 15, 2014.

  1. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    I can't seem to get BlendOp Min to work. No matter what I try it just turns my materials fully black. Does it take alpha value into account?
     
  2. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,610
  3. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Thanks for the link.

    It seems to mention alpha as a separate parameter if using glBlendEquationSeparate. Is that possible to use this in a shader in Unity to modify the alpha blending separately?
     
  4. smd863

    smd863

    Joined:
    Jan 26, 2014
    Posts:
    292
    Sure is.

    https://docs.unity3d.com/Documentation/Components/SL-Blend.html

     
  5. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Okay, now I'm confused… So I can use, for example, BlendOp Add, Min and it will perform an operation on the color and alpha separately?

    It does compile but changing the second parameter doesn't seem to affect the result in any way.
     
  6. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    I also tried just doing it directly in GLSL code via this:

    Code (csharp):
    1.             #ifdef FRAGMENT
    2.             uniform lowp sampler2D _MainTex;
    3.             uniform lowp vec4 _Color;
    4.             void main() {
    5.                 glBlendEquationSeparate(GL_FUNC_ADD, GL_MIN);
    6.                 gl_FragColor = texture2D(_MainTex, uv) * _Color;
    7.             }
    8.             #endif      
    9.             ENDGLSL
    But I get an error that GL_FUNC_ADD and GL_MIN are "undeclared identifiers". However the OpenGL docs say those are the correct parameters :|
     
  7. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    In blending there is basically this equation ..

    (SourceRGBA*Something) + (DestRGBA*Something)

    The blendop changes the + in-between the two parts into something else like Sub(tract), Min(inimum of the two results), Max(imum) of the two result, etc

    ie Min((SourceRGBA*Something),(DestRGBA*Something))
     
    andeliseev, NeatWolf and AlexPGilbert like this.
  8. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Maybe I've misunderstood what the BlendOp is for, but my understanding of the render process (and I'm still learning so please forgive any mistakes here) would be the following...

    1) Opaque queue shaders are calculated and put into the frame buffer.
    2) Transparent queue shaders are then dealt with...
    3) If a transparent queue shader uses a blend operation, the Source is usually the current pixel fragment of the current shader...
    4) ...and the Destination is usually what's already in the frame buffer (probably from the opaque shaders pixels).
    5) BlendOp Min should decide what is smaller between what's already in the frame buffer (Destination) and the current pixel fragment (Source) and replace the frame buffer with the new result.

    Is this understanding correct or am I barking up the wrong tree? :)
     
  9. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    I recently experimented with BlendOps. Here are my notes:
    "BlendOp affects the way ‘src’ and ‘dst’ are added together. It only works with the Blend keyword present. Even when using Min and Max, which don’t take the settings of Blend into consideration."

    Code (csharp):
    1. Shader "Custom/MaxBlending" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.     }
    5.     SubShader {
    6.        
    7.         Tags { "Queue" = "Transparent" }
    8.         Pass {
    9.             BlendOp Max
    10.             Blend One One
    11.             CGPROGRAM
    12.            
    13.                         // pragma target 5.0 because I was experimenting with DX11.1 BlendOps, you might need to use 'target 3.0' instead
    14.             #pragma target 5.0
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.            
    18.             float4 _Color;
    19.  
    20.             float4 vert(float4 v:POSITION) : SV_POSITION {
    21.                 return mul (UNITY_MATRIX_MVP, v);
    22.             }
    23.  
    24.             fixed4 frag() : COLOR {
    25.                 return _Color;
    26.             }
    27.  
    28.             ENDCG
    29.         }
    30.     }
    31. }
    A picture I posted on Twitter used the 'min' variant:
    $Bh6zX8tCcAAMfjq.jpg


    P.S.: The Forum's edit functionality appears to be glitching out, hopefully this version of the post won't glitch out and duplicate itself.
     
  10. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    I now notice that I didn't mention that these operations are done per-channel. If I recall correctly, this is also mentioned in the OpenGL documentation.
    (I don't dare editing the original post right now, the last time I did that, it duplicated the post)
     
  11. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    I finally got BlendOp working (must have been a weird compilation bug) but I'm still stuck in trying to accomplish my objective, which is illustrated below.

    Basically I want to take these intersecting transparent textures:


    ...and retrieve the smallest (Min) alpha value of each pixel, to get a result like this:


    Some have said it can't be done with a shader, but I feel as though it should be possible based on what you guys have told me, in combination with my [admittedly limited] understanding of BlendOp Min. Can anyone give me a conclusive answer on if this is possible?

    This is the shader I'm trying to work with:

    Code (csharp):
    1. // Sets the most transparent pixel when textures overlap
    2.  
    3. Shader "Mobile/Unlit/Transparent Blend" {
    4.  
    5.     Properties {
    6.         _Color ("Main Color (A=Opacity)", Color) = (1,1,1,1)
    7.         _MainTex ("Base (A=Opacity)", 2D) = ""
    8.     }
    9.  
    10.     Category {
    11.         Tags {"Queue"="Transparent" "IgnoreProjector"="True"}
    12.         BlendOp Min, Min
    13.         Blend SrcAlpha OneMinusSrcAlpha
    14.  
    15.         SubShader {Pass {
    16.             GLSLPROGRAM
    17.             varying mediump vec2 uv;
    18.  
    19.             #ifdef VERTEX
    20.             uniform mediump vec4 _MainTex_ST;
    21.             void main() {
    22.                 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    23.                 uv = gl_MultiTexCoord0.xy * _MainTex_ST.xy + _MainTex_ST.zw;   
    24.             }
    25.             #endif
    26.  
    27.             #ifdef FRAGMENT
    28.             uniform lowp sampler2D _MainTex;
    29.             uniform lowp vec4 _Color;
    30.             void main() {
    31.                 gl_FragColor = texture2D(_MainTex, uv) * _Color;
    32.             }
    33.             #endif      
    34.             ENDGLSL
    35.         }}
    36.  
    37.         SubShader {Pass {
    38.             SetTexture[_MainTex] {Combine texture * constant ConstantColor[_Color]}
    39.         }}
    40.     }
    41. }
     
  12. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Your example pictures look more like BlendOp Max.

    The answer to your question is yes, but only for grey textures.
    $BlendOp Max demo for Forum User Essential 650w.JPG

    But perhaps "Is (...) possible?" wasn't the question you actually wanted to ask.
     
    Last edited: Apr 4, 2014
  13. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Thanks for the reply RC-1290, I think what you've posted it promising and I'll try and clarify what I'm trying to do…

    I'm trying to create a little 2D fog of war technique in my game's level screen. On each revealed space, I use a depth mask shader to cut out a hole through a dark overlay…

    Then I use a second quad with a semi-transparent 'fog' texture on top, that blends the hole:


    So, the inner white space is transparent and the outer part is black, and that's the part I'm trying to make invisible when it overlaps with a transparent section.
     
  14. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Alright, but now you're switching the topic a bit. To make sure your original question is answered: BlendOp Min and Max do thake alpha into account, but only for the alpha value that's created. Just like the color channels, they're all handled separately.

    If you want someone to create a fog of war shader for you, try the commercial forum.
     
  15. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Thanks for the suggestion. I could pay for someone to do it but I want to try and do it myself for a sense of satisfaction and learning. I'll try a different approach if this one doesn't work though.

    Currently I'm still a bit confused on how BlendOp is meant to work. I'll demonstrate with a much more simple example. I'll show what happens compared with what I expect. If someone can explain why it happens this way that would be appreciated. :)

    I set up a blue background, with two black ring textures…

    $ScreenShot2014-04-06at171350_zpsc4cd781b.png

    With BlendOp Off, it looks like so, which is as expected:

    $ScreenShot2014-04-06at173730_zps70451d98.png

    If I turn on BlendOp Min, the circles disappear…

    $ScreenShot2014-04-06at171447_zpsfe1c713e.png

    …But what I'd expect to happen is more like below, because I'm expecting the transparent parts of the textures to cancel out the black parts of the ring it overlaps with:

    $ScreenShot2014-04-06at175900_zpsf1cc09dd.png

    So, I'm expecting BlendOp Min to favor the completely transparent and the translucent parts of the texture where the two ring textures overlap, but why isn't this happening?


    And for comparison's sake, when I try BlendOp Max, this happens:

    $ScreenShot2014-04-06at171438_zps5af281a8.png
     
  16. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    It's important to note that the resulting alpha value isn't used for blending (unfortunately). It just determines the value that's written to the alpha channel of the buffer / texture you're writing to.
     
  17. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    My apologies, I'm still learning and don't quite understand. Can you explain with a bit more detail what you mean?
     
  18. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    I hope that I understand it well enough myself to be able to explain it well.

    It would be nice if BlendOp would allow you to choose which alpha value to use for blending; the alpha value of the current fragment, or the alpha value of the fragment in the background. But when you use BlendOp Min or Max, the values for Blend are ignored. It simply does a comparison per channel between the newly created fragment, and the fragment in the background, and uses the highest (Max) or Lowest (Min) value.

    If you imagine the color of current fragment and the background fragment as two vectors:
    Code (csharp):
    1. foreground = float4( 0, 0.5, 0.2, 0.5)
    2. background = float4( 1, 0.2, 0.8, 0.6)
    The two BlendOps would give you the following results:
    Code (csharp):
    1. Max = float4( 1, 0.5, 0.8, 0.6)
    2. Min = float4( 0, 0.2, 0.2, 0.5)
    Any further blend settings are ignored, so Blend SrcAlpha OneMinusSrcAlpha would give the same result as Blend Zero Zero.

    Hopefully that gives you a better idea of what's going on.

    So I think you'll need to use a different strategy to create the effect you're looking for.
     
    Last edited: Apr 6, 2014
  19. Essential

    Essential

    Joined:
    Sep 8, 2011
    Posts:
    265
    Ah, that makes sense. Thank you for helping me understand.

    I'll go for a different approach.
     
  20. bigcatsobig

    bigcatsobig

    Joined:
    Dec 9, 2014
    Posts:
    9
    But..

    BlendOp max only

    스크린샷 2014-12-09 오후 5.23.40.png

    blend overlaps .. dark

    BlendOp max Blend One One have different output

    스크린샷 2014-12-09 오후 5.23.19.png

    this output feel so good :)
    but Appear Only Scene View..

    unity blendOp min and max . is Right??
     
  21. DepreCats

    DepreCats

    Joined:
    Jun 3, 2016
    Posts:
    39

    Sorry for the necro, but because I too noticed that
    "BlendOp max Blend One One"
    looks differently to
    "BlendOp max"
    alone, I got false hope for what I am trying to do, and a lot of confusion.

    It appears that there can be duplicate "Blend ..."s or "BlendOp ..."s but only the last one is selected, and so
    "BlendOp max
    Blend One One"
    first chooses "BlendOp max", then continues to "Blend One One", but because "BlendOp max" is incompatible, the default "BlendOp Add" is used with the "Blend One One".
    Reversing the order results in only using "BlendOp max", without any "Blend", unfortunately.
     
  22. Romeno

    Romeno

    Joined:
    Jun 24, 2013
    Posts:
    35
    Thanks for this discussion. I'm not sure if necroposting is a huge deal.

    I think I solved similar issue. I needed to make a UI element to be applied on top of another element using Darken photoshop blending mode. As I read here somewhere this is equvalent to Blend Min. But I also wanted to change the opacity of that element. So I ended up using
    Code (CSharp):
    1. GrabPass
    then do BlendOP Min emulation math with grabbed colors and on top of it apply
    Code (CSharp):
    1. BlendOP Add
    2. Blend SrcAlpha OneMinusSrcAlpha
    Docs say GrabPass is inefficient but I dont know any other way to apply both effects.

    So the resulting code for this UI element is the following:
    Code (CSharp):
    1. Shader "Romeno/UIDarken"
    2. {
    3.     Properties
    4.     {
    5.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    6.         _Color ("Tint", Color) = (1,1,1,1)
    7.  
    8.         _StencilComp ("Stencil Comparison", Float) = 8
    9.         _Stencil ("Stencil ID", Float) = 0
    10.         _StencilOp ("Stencil Operation", Float) = 0
    11.         _StencilWriteMask ("Stencil Write Mask", Float) = 255
    12.         _StencilReadMask ("Stencil Read Mask", Float) = 255
    13.  
    14.         _ColorMask ("Color Mask", Float) = 15
    15.  
    16.         [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    17.     }
    18.  
    19.     SubShader
    20.     {
    21.         Tags
    22.         {
    23.             "Queue"="Transparent"
    24.             "IgnoreProjector"="True"
    25.             "RenderType"="Transparent"
    26.             "PreviewType"="Plane"
    27.             "CanUseSpriteAtlas"="True"
    28.         }
    29.  
    30.         Stencil
    31.         {
    32.             Ref [_Stencil]
    33.             Comp [_StencilComp]
    34.             Pass [_StencilOp]
    35.             ReadMask [_StencilReadMask]
    36.             WriteMask [_StencilWriteMask]
    37.         }
    38.  
    39.         Cull Off
    40.         Lighting Off
    41.         ZWrite Off
    42.         ZTest [unity_GUIZTestMode]
    43.         ColorMask [_ColorMask]
    44.  
    45.         GrabPass
    46.         {
    47.             "_BackgroundTexture"
    48.         }
    49.  
    50.         Pass
    51.         {
    52.             Name "Default"
    53.  
    54.             BlendOp Add
    55.             Blend SrcAlpha OneMinusSrcAlpha
    56.  
    57.         CGPROGRAM
    58.             #pragma vertex vert
    59.             #pragma fragment frag
    60.             #pragma target 2.0
    61.  
    62.             #include "UnityCG.cginc"
    63.             #include "UnityUI.cginc"
    64.  
    65.             #pragma multi_compile __ UNITY_UI_CLIP_RECT
    66.             #pragma multi_compile __ UNITY_UI_ALPHACLIP
    67.  
    68.             struct appdata_t
    69.             {
    70.                 float4 vertex   : POSITION;
    71.                 float4 color    : COLOR;
    72.                 float2 texcoord : TEXCOORD0;
    73.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    74.             };
    75.  
    76.             struct v2f
    77.             {
    78.                 float4 vertex   : SV_POSITION;
    79.                 fixed4 color    : COLOR;
    80.                 float2 texcoord  : TEXCOORD0;
    81.                 float4 worldPosition : TEXCOORD1;
    82.                 float4 grabPos : TEXCOORD2;
    83.                 UNITY_VERTEX_OUTPUT_STEREO
    84.             };
    85.  
    86.             sampler2D _MainTex;
    87.             fixed4 _Color;
    88.             fixed4 _TextureSampleAdd;
    89.             float4 _ClipRect;
    90.             float4 _MainTex_ST;
    91.  
    92.             v2f vert(appdata_t v)
    93.             {
    94.                 v2f OUT;
    95.                 UNITY_SETUP_INSTANCE_ID(v);
    96.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
    97.                 OUT.worldPosition = v.vertex;
    98.                 OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
    99.  
    100.                 OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    101.  
    102.                 OUT.grabPos = ComputeGrabScreenPos(OUT.vertex);
    103.  
    104.                 OUT.color = v.color * _Color;
    105.                 return OUT;
    106.             }
    107.  
    108.             sampler2D _BackgroundTexture;
    109.  
    110.             fixed4 frag(v2f IN) : SV_Target
    111.             {
    112.                 half4 dstColor = tex2Dproj(_BackgroundTexture, IN.grabPos);
    113.                 half4 srcColor = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
    114.  
    115.                 half4 color = half4(min(dstColor.r, srcColor.r), min(dstColor.g, srcColor.g), min(dstColor.b, srcColor.b), srcColor.a);
    116.  
    117.                 #ifdef UNITY_UI_CLIP_RECT
    118.                 color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
    119.                 #endif
    120.  
    121.                 #ifdef UNITY_UI_ALPHACLIP
    122.                 clip (color.a - 0.001);
    123.                 #endif
    124.  
    125.                 return color;
    126.             }
    127.         ENDCG
    128.         }
    129.     }
    130. }
     
  23. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    BlendOp Add
    is the default blend op.
    Blend
    and
    BlendOp
    are a pair that control how the blend equation works.

    Something like
    Blend SrcAlpha OneMinusSrcAlpha
    and
    BlendOp Add
    works like this:
    finalColor = SrcColor * SrcAlpha + DstColor * OneMinusSrcAlpha;


    The add in the middle is controlled by the
    BlendOp
    , using
    Subtract
    replaces the + with a -, and
    ReverseSubtact
    swaps the src and dst.

    BlendOp Min
    and
    Max
    don’t use the
    Blend
    at all, or at least shouldn’t.

    However to recreate a blend like the one you’re describing you shouldn’t need to do a grab pass. Just do BlendOp Min and change the output of your shader to:
    Code (csharp):
    1. return half4(lerp(half3(1,1,1), col.rgb, col.a), 0);
     
  24. Romeno

    Romeno

    Joined:
    Jun 24, 2013
    Posts:
    35
    Yeah, it works, just resulting alpha should be 1. But for some reason opacity changes a tiny little bit differently - faster then in my approach. Thx for the variation without GrabPass.
     
  25. Angurvadal

    Angurvadal

    Joined:
    Jul 19, 2013
    Posts:
    11
    Hello,

    Sorry for over-necro-ing this thread. I've been looking for a solution for days now and found lot of informations into your replies @bgolus so thank you =)

    Thing is : I'm trying to do something not that exotic but I keep on getting mitigated results.
    I have a background plane with colors (think sky colors).I have trees silhouette (overlapping) sprites in front of this plane and I'd like them to :
    - be transparent so that they take the plane's colors
    - don't stack alpha from other silhouette (so I went for BlendOp Max)
    - modulate the transparency with the vertex color's alpha set in sprite
    - multiply the vertex color to the texture color as well

    My problem is that if I take the texture color and just "return half4(0,0,0,1)" instead of black, the result is transparent.
    I have another problem is that the border transparency of the silhouette returns something like the complementary color of the background color.

    Any clues ? Is it doable that way ? Did I miss something ? =/
    Thanks for your help anyway
     
  26. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    BlendOp Max
    means take the largest value from the src and dst. So if your shader outputs zeros, what's already been rendered is essentially guaranteed to be used since that's going to be some value larger than that.

    What you want is to simultaneously darken the background, but prevent multiple sprites from stacking that darkening effect.

    I.E.: You want something like this:
    upload_2020-4-30_15-47-36.png

    Instead of what you're probably currently getting, which is something like this:
    upload_2020-4-30_15-47-57.png

    The easiest solution is to have the trees be 100% opaque transparency (alpha of 1.0) and also be colored by a similar gradient. Not exactly what you want, but would work. The hard part is of course lining up the gradient between multiple trees which might not all be the same size or have the same position in the gradient.

    Another option would be to use something like a named grab pass (if you're using the built in rendering path) which would grab a copy of the screen just before rendering the first tree outline, darken that, and output that is a fully opaque transparency.

    After that you could look into stencils, or possibly even sprite masks.

    The really complex setup that would match how something like Photoshop / Gimp would work would be to render your sprites into a separate render texture that you then render back into the scene.


    If it was me I'd actually go with the first option I listed, and have some global settings to define the gradient in world space so I could recalculate it / sample the same texture for all of the sprites.
     
  27. Angurvadal

    Angurvadal

    Joined:
    Jul 19, 2013
    Posts:
    11
    Thanks for your quick reply =)

    Yep that's what I was afraid you'd answer. I'm using the URP with the 2D renderer pipeline for mobile platform.

    The gradient solution would work but the environment is procedurally generated so there are no "quick" way to know what gradient colors to use nor where to apply (the gradient isn't linear + there is a parallaxe effect to make it complete haha).
    I checked the stencil part (which is enabled by default) but thought it was perf-expansive for mobiles. Am I wrong?
    The sprite masks works if we have each sprite on different rendering layer except its own.
    For now I have a rendertexture (that does the job well, except postprocesses on rendercams kill the alpha no matter what). And I'm afraid it will drastically affect performances too since having a texture that large won't be good for the fillrate.

    Can I please ask you one last question : what is the overall less expansive solution in here ?
     
    Last edited: May 1, 2020
  28. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Sprite masks are implemented using stencils. The trick is to have each sprite both write to and read from the stencil in the same draw, where as I believe sprite masks break that up into separate passes.

    The one that renders faster is the less expensive solution.

    Which one will that be? Who knows. Depends on what hardware you’re targeting and what else is going on. You just have to try them and see if the easier solutions are fast enough.
     
  29. Angurvadal

    Angurvadal

    Joined:
    Jul 19, 2013
    Posts:
    11
    Allright, I guess I'll have to check then =)

    Thanks a lot for your help and, if in the meantime I find another (better) way to achieve this, I'll post it here for posterity!
     
  30. Hookkshot

    Hookkshot

    Joined:
    Jan 11, 2013
    Posts:
    27
    Did you find a better way to do this. trying to do the same thing for drop shadows. Basically one whole sorting layer needs to be blended.