Creating a simple Unity diffuse shader with shadow support

I recently came across a scenario where I needed a simple Unity diffuse shader (similar to Legacy/Diffuse), but with support for shadows in forward rendering. Admittedly I’m fairly new to the world of shader programming so I figured I would try to save myself some time by dropping in an existing shader. I actually ended up spending an inordinate amount of time trying to Google one, and even went so far as trying to create one in Shader Forge only to find out that Shader Forge shaders don’t support vertex lights. 🤦‍♂️

In the end, I decided to bite the bullet and learn a bit more about Unity shader development. I always found shader dev a bit daunting–and still kind of do–but it’s actually surprising how much can be done in just a few lines of code (or in this case, a simple #pragma directive).

Where to start?

If you’re ever feeling stuck with Unity, start with the docs–they’re surprisingly good! In my case I started by reading through the surface shader and surface shader lighting examples. There are plenty of shaders here that you can use as a starting point. In my case, I grabbed Example/Diffuse Texture:

Shader "Example/Diffuse Texture" {
	Properties {
		_MainTex ("Texture", 2D) = "white" {}
	}

	SubShader {
		Tags { "RenderType" = "Opaque" }
		CGPROGRAM
		#pragma surface surf Lambert

		struct Input {
			float2 uv_MainTex;
		};

		sampler2D _MainTex;

		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
		}
		ENDCG
	} 
	Fallback "Diffuse"
}

This is more or less equivalent to Legacy/Diffuse: it applies a texture to a surface and lights it Unity’s built-in diffuse lighting model (Lambert).

Adding shadows

This was a great start, but sadly, no shadows. Reading through the docs some more, I found the shadow and tessellation directives here; these are key! For those new to shader programming–or programming in general–#pragma directives basically tell the compiler how to compile something. In the case of shaders, they actualy play a major role in their appearance.

At first I thought I would just use the addshadow directive:

addshadow – Generate a shadow caster pass. Commonly used with custom vertex modification, so that shadow casting also gets any procedural vertex animation. Often shaders don’t need any special shadows handling, as they can just use shadow caster pass from their fallback.

But where my fallback was Diffuse, this didn’t really make sense (or work). Okay… round 2!

fullforwardshadows – Support all light shadow types in Forward rendering path. By default shaders only support shadows from one directional light in forward rendering (to save on internal shader variant count). If you need point or spot light shadows in forward rendering, use this directive.

Yes! That did the trick. One simple word placed in exactly the right spot… if only everything else were that easy. 😀

Here’s the full shader in all its glory:

Shader "Cosmocat/Diffuse" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader {
		Tags { "RenderType" = "Opaque" }
		CGPROGRAM
		#pragma surface surf Lambert fullforwardshadows 
  
		struct Input {
			float2 uv_MainTex;
		};
        
		sampler2D _MainTex;
        
		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
		}
		ENDCG
    }
    Fallback "Diffuse"
}

And here’s what looks like in action (left = pixel light with shadows / right = vertex light):

Well this may seem like an obvious and simple shader to somebody with any experience, I’m putting this here for those who finds themselves in a similar situation: new to shader development and not sure where to start. Hopefully this helps lead you down the right path.

More importantly, I hope you learn from my mistakes! This really was a classic case of “you should RTFM”, and a classic example of how not taking a few minutes to learn something new actually resulted in an inordinate amount of time being wasted time.

If you found this helpful please let me know. Likewise, if you have any tips for new shader devs, feel free to share.