Fully Shadowed Transparent Shader for Unity (Shadows receiving support from Spot/Point and Directional Lights)

>>> Summary

  • * Advanced ShaderLab surface shading for transparency management
  • * Acting in “Forward” rendering path and the “ForwardBase” Lightmode
  • * Activating shadows from any kind of light (Methods 1 and 2)
  • * Compositing a scene in both “Forward” and “Deferred Lighting” views




    >>> Downloads


    >>> Prerequisites


    Set of lights and scene graph :


    [FIG 1]



      The scene is composed of 2 lights (a Spot one and a Directional one). Those two lights are supposed to project the shadows of a Sphere on a Plane. The Plane’s Material is set up using the transparent Shader described below.
      Note : The Camera dedicated to the rendering of the two objects MUST also have its “Rendering path” parameter set to “Forward”.


    >>> Advanced ShaderLab surface shading for transparency management


    The “surface” shaders, available in Unity 3 are very simple and powerful (excepted that multi-passes are not allowed – we’ll see it later in this doc.)

    Anyway, they are the way I chose to accomplish this work.


    Here is the base shader which will be used all along this article. For more details see the previous topic [ARTICLE : Advanced Waving Flag Shader for Unity (Double sided, Alpha shadow support) / SECTION : Advanced ShaderLab surface shading for transparency management].



    [CODE] [download file]


    SubShader
    {
    Tags {
    "Queue"="AlphaTest"
    "IgnoreProjector"="True"
    "RenderType"="Transparent"
    }
    LOD 300
    // Main Surface Pass (Handles Spot/Point lights)
    CGPROGRAM
    #pragma surface surf BlinnPhong alpha vertex:vert fullforwardshadows approxview
    half _Shininess;
    sampler2D _MainTex;
    float4 _Color;
    sampler2D _BumpMap;
    //sampler2D _ParallaxMap;
    float _Parallax;
    struct v2f {
    V2F_SHADOW_CASTER;
    float2 uv : TEXCOORD1;
    };
    struct Input {
    float2 uv_MainTex;
    float2 uv_BumpMap;
    //float3 viewDir;
    };
    v2f vert (inout appdata_full v) {
    v2f o;
    return o;
    }
    void surf (Input IN, inout SurfaceOutput o) {
    // Comment the next 4 following lines to get a standard bumped rendering
    // [Without Parallax usage]
    /*half h = tex2D (_ParallaxMap, IN.uv_BumpMap).w;
    float2 offset = ParallaxOffset (h, _Parallax, IN.viewDir);
    IN.uv_MainTex += offset;
    IN.uv_BumpMap += offset;*/
    fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
    o.Albedo = tex.rgb * _Color.rgb;
    o.Gloss = tex.a;
    o.Alpha = tex.a * _Color.a;
    //clip(o.Alpha - _Cutoff);
    o.Specular = _Shininess;
    o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    }
    ENDCG
    }


    [FIG 2]



      With this quite simple Shader, we get a perfect rendering of the Spot/Point lights, but nothing at all concerning DIRECTIONAL lights. The goal is to make the Plane able to also receive shadows from the second ones.


    >>> Acting in “Forward” rendering path and the “ForwardBase” Lightmode


    As said earlier, the Camera dedicated to the rendering of the two objects must have its “Rendering path” parameter set to “Forward”.


    The Pass Tag “Lightmode” can take different values. The one we are interested in is the “ForwardBase” one which is “Used in Forward rendering, ambient, main directional light and vertex/SH lights are applied”, according to the Unity documentation [SL-PassTags].

    But let’s have a test by adding this tag to our current shader :



    [CODE] [download file]


    SubShader
    {
    Tags {
    "Queue"="AlphaTest"
    "IgnoreProjector"="True"
    "RenderType"="Transparent"
    "LightMode" = "ForwardBase"
    }
    LOD 300
    // Main Surface Pass (Handles Spot/Point lights)
    CGPROGRAM
    #pragma surface surf BlinnPhong alpha vertex:vert fullforwardshadows approxview
    .
    .
    .
    ENDCG
    }


    [FIG 3]



    We get – as expected – the shadows related to the Directional light and nothing from the Spot one.


    Fine, but we need all the shadows.




    >>> Activating shadows from any kind of light (Methods 1 and 2)


    Well, the following aims to explain two different methods to mix all the types of shadows together.
    Those methods have benefits and drawbacks, of course…


    >> The “Doubled Material” Method :


    It consists in creating two Materials (one using the first version of the shader and the other using the second/Forward one). Then, set up the MeshRenderer of the Plane with those Materials.



    [FIG 4.1]



    [FIG 4.2]



    Benefits : Easy. Free to redo the whole process on any other base shader.


    Drawbacks : The two shaders are a bit boring to manipulate because of the need for keeping the same values for each parameter between them. They are redundant and we get two Materials where we could have only one. It is also quite hard to have a fine customization of the light and shadows intensity.


    >> The “Blending Shadows” Method :

    With this one, we get only one shader, by blending the Directional Light to our very first Shader (made in an additional Pass).



    [CODE]


    SubShader
    {
    Tags {
    "Queue"="AlphaTest"
    "IgnoreProjector"="True"
    "RenderType"="Transparent"
    }
    LOD 300
    // Main Surface Pass (Handles Spot/Point lights)
    CGPROGRAM
    #pragma surface surf BlinnPhong alpha vertex:vert fullforwardshadows approxview
    .
    .
    .
    ENDCG
    // Shadow Pass : Adding the shadows (from Directional Light)
    // by blending the light attenuation
    Pass {
    Blend SrcAlpha OneMinusSrcAlpha
    Name "ShadowPass"
    Tags {"LightMode" = "ForwardBase"}
    CGPROGRAM
    #pragma exclude_renderers xbox360
    #pragma debug
    #pragma vertex vert
    #pragma fragment frag
    #pragma multi_compile_fwdbase
    #pragma fragmentoption ARB_fog_exp2
    #pragma fragmentoption ARB_precision_hint_fastest
    #include "UnityCG.cginc"
    #include "AutoLight.cginc"
    struct v2f {
    float2 uv_MainTex : TEXCOORD1;
    float4 pos : SV_POSITION;
    LIGHTING_COORDS(3,4)
    float3 lightDir;
    };
    float4 _MainTex_ST;
    sampler2D _MainTex;
    float4 _Color;
    float _ShadowIntensity;
    v2f vert (appdata_full v)
    {
    v2f o;
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.lightDir = ObjSpaceLightDir( v.vertex );
    TRANSFER_VERTEX_TO_FRAGMENT(o);
    return o;
    }
    float4 frag (v2f i) : COLOR
    {
    float atten = LIGHT_ATTENUATION(i);
    half4 c;
    c.rgb = 0;
    c.a = (1-atten) * _ShadowIntensity;
    return c;
    }
    ENDCG
    }

    }


    Here we simply add a specific Pass for Directional Light rendering. This is done by blending the “Light Attenuation” to the first CGPROGRAM part which constitutes a first Pass (using Surface features).


    For “alpha blending”, we use “ Blend SrcAlpha OneMinusSrcAlpha”.
    See [SL-Blend]


    Note : Remember that Surface Shaders allow only one Surface Pass.


    Note : “_ShadowIntensity” parameter has been added to help fine customization of shadows intensity.



    [FIG 5]



    Problem : The Directional light shadow overlaps the transparent areas of our ARGB texture.


    Solution : To correct the shadow intensity by picking up the alpha informations from the alpha channel of the Main Texture…

    Two steps :

    • -  ”TRANSFORM_TEX” calculates the uv coordinates for the tiling and offset set up for the texture (to load the right pixel informations from the Texture)
    • -   “* tex2D(_MainTex, i.uv_MainTex).a” Multiplies the shadow intensity by the alpha channel of the Texture

    [CODE] [download file]


    v2f vert (appdata_full v)
    {
    v2f o;
    o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex);
    o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    o.lightDir = ObjSpaceLightDir( v.vertex );
    TRANSFER_VERTEX_TO_FRAGMENT(o);
    return o;
    }
    float4 frag (v2f i) : COLOR
    {
    float atten = LIGHT_ATTENUATION(i);
    half4 c;
    c.rgb = 0;
    c.a = (1-atten) * _ShadowIntensity *
    (tex2D(_MainTex, i.uv_MainTex).a);
    return c;
    }



    [FIG 6]


      Now it looks good.


    >>> Compositing a scene with both “Forward” and “Deferred Lighting” views


    Finally, we can play with 2 Cameras : the first one with “Rendering path” set to “Forward”, and the second one set to “Deferred Lighting”. And juggle with the layering of the objects to combine usual shadows computing with our “Forward” ones.



    [FIG 7]


      This is obtained with left Sphere+Plane in layer1, and right Sphere+Plane in layer2. Respectively, a Camera in Forward mode (and “Clear Flags” = “Depth Only”) and second Camera in Deferred mode (and “Clear Flags” = “Skybox”).


      Note : An other way to do the trick could have been to keep the whole scene in “Forward rendering path” and to customize the “Non-Forward” shaders to enable them to “fullforwardshadows” in their “#pragma surface” line. Here is an example with the “Normal-Diffuse” one : [download file].


      Anyways, the “Forward rendering path” is a lot more compatible with smaller hardware configurations. So you’re not loosing your time by working in this mode !
      [RenderingPaths]



    Big Conclusion


    Well, nothing more actually…

    Hope this will help,

    and don’t forget to post feedbacks (I need you to make this doc. better)

    Thanks !


    Cette note n’est pas disponible en French, mĂŞme si c’est marquĂ© que si, juste aprĂ©s.


    Cette note est également disponible en: French