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 "Infopulsevia/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.
