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

Transparency while limiting albedo max?

Discussion in 'Shaders' started by Raseru, Feb 8, 2019.

  1. Raseru

    Raseru

    Joined:
    Oct 4, 2013
    Posts:
    87
    Sorry, new to Shaders and really confused.
    I don't like how lights will make certain sprites so bright that they turn white when more than one light is nearby. My ideal solution would be to make it so that the lights either barely get more bright when near others, or don't add at all.

    I've been working with Sprites Diffuse (I'll post its code below for reference)
    It seems like BlendOp Max is what I want, but it also seems like Blend One OneMinusSrcAlpha may not play nicely with it? (Darker colors seem to become transparent, lights stop working, transparent things behind mix)

    Maybe I should just limit the albedo? It still seems to get brighter though.

    Is there perhaps another way to approach this or am I on the right track? Any help would be appreciated. Thanks.

    Code (CSharp):
    1. // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
    2.  
    3. Shader "Sprites/Diffuse"
    4. {
    5.     Properties
    6.     {
    7.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    8.         _Color ("Tint", Color) = (1,1,1,1)
    9.         [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    10.         [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
    11.         [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
    12.         [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
    13.         [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
    14.     }
    15.  
    16.     SubShader
    17.     {
    18.         Tags
    19.         {
    20.             "Queue"="Transparent"
    21.             "IgnoreProjector"="True"
    22.             "RenderType"="Transparent"
    23.             "PreviewType"="Plane"
    24.             "CanUseSpriteAtlas"="True"
    25.         }
    26.  
    27.         Cull Off
    28.         Lighting Off
    29.         ZWrite Off
    30.         Blend One OneMinusSrcAlpha
    31.  
    32.         CGPROGRAM
    33.         #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
    34.         #pragma multi_compile _ PIXELSNAP_ON
    35.         #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    36.         #include "UnitySprites.cginc"
    37.  
    38.         struct Input
    39.         {
    40.             float2 uv_MainTex;
    41.             fixed4 color;
    42.         };
    43.  
    44.         void vert (inout appdata_full v, out Input o)
    45.         {
    46.             v.vertex = UnityFlipSprite(v.vertex, _Flip);
    47.  
    48.             #if defined(PIXELSNAP_ON)
    49.             v.vertex = UnityPixelSnap (v.vertex);
    50.             #endif
    51.  
    52.             UNITY_INITIALIZE_OUTPUT(Input, o);
    53.             o.color = v.color * _Color * _RendererColor;
    54.         }
    55.  
    56.         void surf (Input IN, inout SurfaceOutput o)
    57.         {
    58.             fixed4 c = SampleSpriteTexture (IN.uv_MainTex) * IN.color;
    59.             o.Albedo = c.rgb * c.a;
    60.             o.Alpha = c.a;
    61.         }
    62.         ENDCG
    63.     }
    64.  
    65. Fallback "Transparent/VertexLit"
    66. }
    67.  
     
    Last edited: Feb 8, 2019
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    What you want to do won't be possible with Surface Shaders, not the least of which is because Surface shaders override the blend mode for the ForwardAdd pass, but also because by that point it's too late for BlendOp Max to work as you would want.

    Unity's forward lighting system works by rendering the object once applying the main directional light, along with ambient light and any non-important lights. This is using the ForwardBase pass. Any additional important (aka per-pixel) lights beyond the first directional light are rendered by drawing the object again for each light. Those are using the ForwardAdd pass. The blend and blend op mode you set in the shader will get used by the ForwardBase pass, but the ForwardAdd passes will always use an additive blend. The only way to override that is to not use a Surface Shader, or to take the shader code generated by a surface shader and hand modify it.

    But like I said, even if you do this, it's too late.

    For the opaque case, you'd render the ForwardBase normally, maybe using some code to clamp the total lighting brightness (direct lighting + ambient) to not exceed "1.0". This would black out the render buffer's contents, then add the output of this shader on top. This is the default blend mode of Blend One Zero, ie: multiply the shader output (src) by 1 leaving it unmodified, multiply the render buffer (dst) by zero making it black, and add them together. Subsequent passes you could then use BlendOp Max, along with also limiting the light's color to "1.0", and you'll never see the lighting go over the original texture's brightness.

    For the transparent case, this all breaks down because that first pass is a blend between the background and the sprite you're rendering. The BlendOp Max doesn't know if the color it's testing against is the background or the lit sprite ... because it's both. The only solution is to do all of the lighting in one pass and clamp the values prior to output. But Unity's lighting system for the built in forward renderer doesn't work that way.


    Except it can, as long as you're okay with limiting your sprites to 1 directional light, and 4 point lights, and you don't need shadow casting from the point lights. Remember how I mentioned non-important lights? The ForwardBase pass can handle 4 non-important point lights. These are often referred to as "per-vertex" lights as Unity's code calculates these per-vertex. You would need to write a custom vertex fragment shader with only a ForwardBase pass, and then take the total lighting coming from the main directional light, ambient light, and vertex lights and clamp it to a max of "1.0" before multiplying it against your sprite texture's color.
     
    Raseru likes this.
  3. Raseru

    Raseru

    Joined:
    Oct 4, 2013
    Posts:
    87
    Thanks a lot for this, I got a deeper understanding of how this works and how it behaves and learned a lot from that post. To get over these limitations I think I will go with just faking the lights, https://github.com/prime31/SpriteLightKit seems promising so far and has a lot of advantages for my case.