Adding Shadows to a Unity Vertex/Fragment Shader in 7 Easy Steps

This was a question asked on the Unity Forums recently, so I thought I’d just write up the answer here.

Unity provides its own unique brand of “surface shaders”, which make dealing with lighting and shadows relatively simple. But there are still plenty of occasions in which you find yourself writing more traditional vert/frag CG shaders, and needing to deal with shadows in those too.

Suppose you had written a custom vertex/fragment CG shader, such as the following simple example:

Shader "Custom/SolidColor" {
    SubShader {
        Pass {
        
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 pos : SV_POSITION;
            };


            v2f vert(appdata_base v) {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : COLOR {
                return fixed4(1.0,0.0,0.0,1.0);
            }

            ENDCG
        }
    }
    Fallback "VertexLit"
}

 

This shader simply outputs the colour red for all fragments, as shown on the plane in the following image:

image

 

Now what if you wanted to add shadows to that surface? Unity already creates a shadowmap for you from all objects set to cast shadows, and defines several macros that make it easier to sample that shadowmap at the appropriate point. So here’s the changes you need to make to a shader to make use of those built-in shadows:

Shader "Custom/SolidColor" {
    SubShader {
        Pass {
        
            // 1.) This will be the base forward rendering pass in which ambient, vertex, and
            // main directional light will be applied. Additional lights will need additional passes
            // using the "ForwardAdd" lightmode.
            // see: http://docs.unity3d.com/Manual/SL-PassTags.html
            Tags { "LightMode" = "ForwardBase" }
        
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            // 2.) This matches the "forward base" of the LightMode tag to ensure the shader compiles
            // properly for the forward bass pass. As with the LightMode tag, for any additional lights
            // this would be changed from _fwdbase to _fwdadd.
            #pragma multi_compile_fwdbase

            // 3.) Reference the Unity library that includes all the lighting shadow macros
            #include "AutoLight.cginc"


            struct v2f
            {
                float4 pos : SV_POSITION;
				
                // 4.) The LIGHTING_COORDS macro (defined in AutoLight.cginc) defines the parameters needed to sample 
                // the shadow map. The (0,1) specifies which unused TEXCOORD semantics to hold the sampled values - 
                // As I'm not using any texcoords in this shader, I can use TEXCOORD0 and TEXCOORD1 for the shadow 
                // sampling. If I was already using TEXCOORD for UV coordinates, say, I could specify
                // LIGHTING_COORDS(1,2) instead to use TEXCOORD1 and TEXCOORD2.
                LIGHTING_COORDS(0,1)
            };


            v2f vert(appdata_base v) {
                v2f o;
                o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                
                // 5.) The TRANSFER_VERTEX_TO_FRAGMENT macro populates the chosen LIGHTING_COORDS in the v2f structure
                // with appropriate values to sample from the shadow/lighting map
                TRANSFER_VERTEX_TO_FRAGMENT(o);
                
                return o;
            }

            fixed4 frag(v2f i) : COLOR {
            
                // 6.) The LIGHT_ATTENUATION samples the shadowmap (using the coordinates calculated by TRANSFER_VERTEX_TO_FRAGMENT
                // and stored in the structure defined by LIGHTING_COORDS), and returns the value as a float.
                float attenuation = LIGHT_ATTENUATION(i);
                return fixed4(1.0,0.0,0.0,1.0) * attenuation;
            }

            ENDCG
        }
    }
    
    // 7.) To receive or cast a shadow, shaders must implement the appropriate "Shadow Collector" or "Shadow Caster" pass.
    // Although we haven't explicitly done so in this shader, if these passes are missing they will be read from a fallback
    // shader instead, so specify one here to import the collector/caster passes used in that fallback.
    Fallback "VertexLit"
}

 

And that’s it!

image

This entry was posted in Game Dev and tagged , , , . Bookmark the permalink.

3 Responses to Adding Shadows to a Unity Vertex/Fragment Shader in 7 Easy Steps

  1. Sean Hart says:

    Thank you for the in-depth explanation of Unity’s shadows in shaders.

    I’m currently using a custom projection matrix in Unity to skew my scene, which as expected breaks Unity’s default shadow system. I was wondering is there a way to use my own shadow shader that uses my custom projection matrix to display shadows correctly with my skewed camera?

  2. magicpotato says:

    Thanks, But I have same issue on surface shader.
    Could you advise for surface shader to receive shadows?

Leave a comment