Search Unity

Transparent shadows - Changing queue from AlphaTest to Transparent

Discussion in 'Shaders' started by jrhee, Sep 20, 2017.

  1. jrhee

    jrhee

    Joined:
    Dec 5, 2013
    Posts:
    74
    Hi,

    I'm working on a game with 3D characters and 2D environments, with depth sorting handled using sorting layer/ID.

    I'm trying to have my characters cast shadows from a directional light onto invisible geometry, then draw those shadows on top of my environment sprites.

    shadows.jpg

    As shown above, I'm able to get shadows to display on an invisible plane, but my sprites are being drawn over them. I suspect I need to change the queue from "AlphaTest" to "Transparent" in the shader below, but when I do, shadows aren't displayed at all.

    Would anyone be able to point me to how I can get the below working with queue set to transparent?

    Code (csharp):
    1.  
    2. Shader "Transparent/TransparentReceiveShadow"
    3. {
    4.     Properties
    5.     {
    6.         _Color("Shadow Color", Color) = (1,1,1,1)
    7.         _Cutoff("Alpha cutoff", Range(0,1)) = 0.5
    8.     }
    9.  
    10.  
    11.     SubShader
    12.     {
    13.         Tags
    14.         {
    15.             "Queue" = "AlphaTest"
    16.             "IgnoreProjector" = "True"
    17.             "RenderType" = "TransparentCutout"
    18.         }
    19.         LOD 200
    20.         ZWrite off
    21.         Blend zero SrcColor
    22.  
    23.  
    24.         CGPROGRAM
    25. #pragma surface surf ShadowOnly alphatest:_Cutoff fullforwardshadows
    26.  
    27.         fixed4 _Color;
    28.         float _ShadowInt;
    29.  
    30.         struct Input
    31.         {
    32.             float2 uv_MainTex;
    33.         };
    34.  
    35.         inline fixed4 LightingShadowOnly(SurfaceOutput s, fixed3 lightDir, fixed atten)
    36.         {
    37.             fixed4 c;
    38.             c.rgb = lerp(s.Albedo, float3(1.0,1.0,1.0), atten);
    39.             c.a = 1.0-atten;
    40.             return c;
    41.         }
    42.  
    43.  
    44.         void surf(Input IN, inout SurfaceOutput o)
    45.         {
    46.             o.Albedo = _Color.rgb;
    47.             o.Alpha = 1.0;
    48.         }
    49.         ENDCG
    50.     }
    51.     Fallback "Transparent/Cutout/VertexLit"
    52. }
    53.  
     
  2. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    The transparent pass can neither receive nor cast shadows, so that is not the solution for your problem. What can solve your problem is changing the render queue:
    In every material you find find at the very bottom when in the renderqueue this material will be drawn
    upload_2017-9-20_9-29-27.png

    Normally you would first draw opaque geometry, then cutout materials and then transparent materials.
    In your case it would make sense to first draw your 2d environment (which probably has quite a lot of transparent materials), and then your 3D materials (character and invisible objects). If you manage that the invisible objects write into the depth buffer for their full geometry you will also have no need to do the sorting yourself. But actually I#m not sure if that is possible with a single pass, I guess you would need two passes in order to draw your shadow with a cutout shader.
     
  3. jrhee

    jrhee

    Joined:
    Dec 5, 2013
    Posts:
    74
    Thanks Johannski. It's tricky since sometimes 2D environment sprites need to be drawn over my 3D characters. I'm thinking I may need to go full 3D to meet all my sorting requirements, but will give the two pass shader a try first.
     
  4. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    Well that should be possible if the invisible 3d geometry writes into the depth buffer. For every pixel the shader for the character will do a check, if the depthvalue is greater than the one already written to the depth buffer and will only render the pixel if the pixel is in front of the one already written there.

    This sadly implies that you have to be quite precise with your invisble 3d models which might lead to the conclusion that it is less work to go full 3d.

    But maybe somebody else has another approach which could lead you on a more efficient way?!
     
  5. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    Hey again @jrhee
    I put a bit more thought into it, and I think the solution I was offering is quite good after all. I did a small test:
    upload_2017-9-21_14-23-55.png

    As you can see, the invisible geometry can receive shadows and also culls away the red block behind it. The sprite on the other hand is not culled because it is drawn earlier in the render queue (those are the numbers in the notes).

    Here is the shader for the invisible block:
    Code (CSharp):
    1. Shader "Custom/ShadowDrawer"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Shadow Color", Color) = (0, 0, 0, 0.6)
    6.     }
    7.  
    8.     CGINCLUDE
    9.  
    10.     #include "UnityCG.cginc"
    11.     #include "AutoLight.cginc"
    12.  
    13.     struct v2f_shadow {
    14.         float4 pos : SV_POSITION;
    15.         LIGHTING_COORDS(0, 1)
    16.     };
    17.  
    18.     half4 _Color;
    19.  
    20.     v2f_shadow vert_shadow(appdata_full v)
    21.     {
    22.         v2f_shadow o;
    23.         o.pos = UnityObjectToClipPos(v.vertex);
    24.         TRANSFER_VERTEX_TO_FRAGMENT(o);
    25.         return o;
    26.     }
    27.  
    28.     half4 frag_shadow(v2f_shadow IN) : SV_Target
    29.     {
    30.         half atten = LIGHT_ATTENUATION(IN);
    31.         return half4(_Color.rgb, lerp(_Color.a, 0, atten));
    32.     }
    33.  
    34.     ENDCG
    35.  
    36.     SubShader
    37.     {
    38.         Tags { "Queue"="Geometry -1" }
    39.  
    40.         // Depth fill pass
    41.         Pass
    42.         {
    43.             ColorMask 0
    44.  
    45.             CGPROGRAM
    46.  
    47.             #pragma vertex vert
    48.             #pragma fragment frag
    49.  
    50.             struct v2f {
    51.                 float4 pos : SV_POSITION;
    52.             };
    53.  
    54.             v2f vert(appdata_full v)
    55.             {
    56.                 v2f o;
    57.                 o.pos = UnityObjectToClipPos (v.vertex);
    58.                 return o;
    59.             }
    60.  
    61.             half4 frag(v2f IN) : SV_Target
    62.             {
    63.                 return (half4)0;
    64.             }
    65.  
    66.             ENDCG
    67.         }
    68.  
    69.         // Forward base pass
    70.         Pass
    71.         {
    72.             Tags { "LightMode" = "ForwardBase" }
    73.             Blend SrcAlpha OneMinusSrcAlpha
    74.             CGPROGRAM
    75.             #pragma vertex vert_shadow
    76.             #pragma fragment frag_shadow
    77.             #pragma multi_compile_fwdbase
    78.             ENDCG
    79.         }
    80.  
    81.         // Forward add pass
    82.         Pass
    83.         {
    84.             Tags { "LightMode" = "ForwardAdd" }
    85.             Blend SrcAlpha OneMinusSrcAlpha
    86.             CGPROGRAM
    87.             #pragma vertex vert_shadow
    88.             #pragma fragment frag_shadow
    89.             #pragma multi_compile_fwdbase
    90.             ENDCG
    91.         }
    92.     }
    93.     FallBack "Mobile/VertexLit"
    94. }
    It is from a project of keijiro and has, as I would have done as well, multiple passes.

    You can change the render queue for every sprite that has a invisible object as I showed you, or you use this custom shader to make sure you don't miss any sprites:
    Code (CSharp):
    1. Shader "Custom/Sprites"
    2. {
    3.     Properties
    4.     {
    5.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    6.         _Color ("Tint", Color) = (1,1,1,1)
    7.         [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    8.         [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
    9.         [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
    10.         [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
    11.         [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
    12.     }
    13.  
    14.     SubShader
    15.     {
    16.         Tags
    17.         {
    18.             "Queue"="Geometry -50"
    19.             "IgnoreProjector"="True"
    20.             "RenderType"="Transparent"
    21.             "PreviewType"="Plane"
    22.             "CanUseSpriteAtlas"="True"
    23.         }
    24.  
    25.         Cull Off
    26.         Lighting Off
    27.         ZWrite Off
    28.         Blend One OneMinusSrcAlpha
    29.  
    30.         Pass
    31.         {
    32.         CGPROGRAM
    33.             #pragma vertex SpriteVert
    34.             #pragma fragment SpriteFrag
    35.             #pragma target 2.0
    36.             #pragma multi_compile_instancing
    37.             #pragma multi_compile _ PIXELSNAP_ON
    38.             #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    39.             #include "UnitySprites.cginc"
    40.         ENDCG
    41.         }
    42.     }
    43. }
    44.  
    For all other sprites you can use the normal shader and queue.

    I hope this gets you going. This way you won't need any special sorting, the invisible objects will do all the work for you :)
     
    jrhee likes this.
  6. jrhee

    jrhee

    Joined:
    Dec 5, 2013
    Posts:
    74
    Awesome, thanks Johannski, that works great! I'm still thinking through sorting characters moving around larger assets, but this plenty to get me started. Cheers!