Shaders are interesting. Wielded properly, they can create awe-inspiring effects that’ll blow your mind, but be careful. The path to that knowledge is fraught with challenges that may cause you to lose your mind. Shaders are difficult to debug, examples are hard to find, and they typically don’t work at all until they work perfectly.
I set out upon the path to shader enlightenment recently. I lost a night of sleep and 10 sanity points on my first effort. So what went wrong?
Build Fails: Check for Typos
Typos are easier to make and harder to correct in shaders. My favorite trick is to search for each my variables and make sure they all appear as highlighted. A quick scan of the document should reveal any non-highlighted variation on the variable you’re checking as a typo.
Assignments Aren’t Working Properly
This is the one that truly caused my to pull my hair out. I blame a lack of sleep as I started my first Unity shader at 2 AM and refused to give up until I found the source of my confusion.
You declare properties like this: _TilingOffset ("Tiling and Offset", Vector) = (1, 1, 0, 0)
. However, the same initialization style won’t work in your vert/frag functions. If you try float2 uv = (i.uv.x, i.uv.y)
you’ll notice some rather frustrating behavior. (I believe the x value was used and the y value was a constant.) You’ll need to add the type to both sides like so: float2 uv = float2(i.uv.x, i.uv.y)
. This probably seems pretty obvious now, but I couldn’t find any examples of variable assignments and there weren’t any errors thrown from it.
Nothing Appears (Culling)
By default, shaders use culling. That means only one side of a given quad is shown. This is for efficiency reasons. In most cases, the inside of a shape will never be shown, so it saves rendering time to skip processing them. This is also why you’ll see odd patterns you see when cameras clip. You’re seeing the invisible other side of objects that should only ever be viewed from one side.
The solution to this issue is either to make sure that the proper side is always facing the camera. Or in cases where both sides should be shown, you can turn culling off. Just add Cull Off
at the start of the SubShader section. Image Effect Shaders have culling off by default if you’d like to see an example.
SubShader
{
Cull Off
Nothing Appears (ZWrite Off)
Turning ZWrite Off means you’ll have to be fairly careful with your render queue to get the write display order. New Image Effect Shaders have ZWrite Off by default, but oddly, they’re not put in the Transparent Queue. This causes what might appear to be some bizarre behavior.
I tested a brand new default Image Effect Shader with my logo. Initially, I was confused as to why the logo wouldn’t appear at all. With ZWrite Off, the Render Queue essentially determined the visibility. With solid objects or even semi-transparent ones, this was fine. However, I had a UI element in the background of my scene that completely blocked the logo until the Render Queue on the logo was set to Transparency.
If you’d like to learn more, check out the documentation on the Render Queue, the documentation on Culling and Depth Testing. The legacy documentation also has a good tree example for possible depth issues with semi-transparent objects.
Undeclared identifier Errors
Unlike in other code files, the order of variables within the shader matters. You need to declare variables before you use them. If you don’t declare a variable until after the function that uses it, then you’ll get this error. Just move the declaration to the proper spot.
Example:
v2f vert (appdata v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = float2(v.uv.x * _TilingOffset.x + _TilingOffset.z, v.uv.y * _TilingOffset.y + _TilingOffset.w);
return o;
}
float4 _TilingOffset;
sampler2D _MainTex;
fixed4 frag (v2f i) : SV_Target {
...
}
_TilingOffset is used in vert despite the fact that the declaration appears later in the file. This will fail.
Unexpected Token and Undeclared identifier Errors
This usually means you forgot a semicolon.
Tiling/Offset Not Working on Custom Shader
The tiling/offset values go unused on custom shaders unless you set up your shader to use them. To use them, just add a reference to the supplied values: float4 _MainTex_ST;
(before the vert function) and replace o.uv = v.uv;
with o.uv = TRANSFORM_TEX (v.uv, _MainTex);
in the vert function.
Tiling Not Working
Check that the Wrap Mode on your texture asset is set to Repeat. Otherwise, when setting your tiling values greater than zero, you’ll notice the texture squishes, but not repeat.
Shader Not Appearing in Shader List
Change Shader "Hidden/YourShader"
to Shader "Custom/YourShader"
. You could also replace the word “Custom” with something else depending on how you want to keep organized.
Summary
Few, that went on longer than I thought and I’m sure there are many more common mistakes to find. Let me know if you have a few more to add.
Bonus Trivia
You can’t create a Vector property with less than 4 elements. This is a hardware constraint. Single variables will have 3 empty values (3, 0, 0, 0). The Tiling and Offset pairs shown in the Editor are actually put into a single float4 called {TextureName}_ST.
Thank you!