Introduction to Shaders in Unity
Ever wondered about shaders in Unity? In this tutorial, you’ll learn what shaders are, how to display vertex colors and how to animate within shaders. By Joseph Hocking.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Shaders in Unity
25 mins
- What Are Shaders?
- Understanding Types of Shaders
- Writing a Custom Shader
- Looking at the Default Template for a Custom Shader
- Properties
- The SubShader block
- The CGPROGRAM block
- MainTex property variable
- Inputs and property variables
- The main shader function
- FallBack shader
- Adding Vertex Color to a Surface Shader
- Creating a Custom Shader for Vertex Color
- Animating the Water Texture
- Creating an Unlit Shader
- Where to Go From Here?
Creating a Custom Shader for Vertex Color
Create a new Surface Shader and name it LitVertexColor. Set the cartoon-sand material to use this Shader, and then open the Shader asset to edit the code.
You only need to make two small additions for vertex color. First, add the following code to the Input
struct:
struct Input {
float2 uv_MainTex;
float4 vcolor : COLOR; // vertex color
};
Next, incorporate vcolor
into the color calculation that happens in surf
:
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color * IN.vcolor;
And that’s it! Save the Shader and return to the Unity scene. Once the Shader compiles, you’ll see the edges of the island blend under the water.
Animating the Water Texture
Now the sand looks a lot more appealing! Meanwhile, the other issue you identified was that the water is completely static.
You can change this with another custom Shader, although you don’t need lighting this time. Remember that Surface Shaders handle the lighting for you, so you won’t create a Surface Shader this time.
Creating an Unlit Shader
Instead, create an Unlit Shader through the right-click menu: Create ▸ Shader ▸ Unlit Shader.
Name the new Shader asset CartoonWater and open it in your code editor.
First thing’s first, update the name of the shader right at the very top.
Shader "Custom/CartoonWater"
The default name for this Shader is "Unlit/CartoonWater"
. Changing that name to "Custom/CartoonWater"
makes it easier to find your custom Shaders in the menu.
Next, add some additional properties for your water shader. The new Properties
block should look like this:
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Opacity ("Opacity", Range(0,1)) = 0.5
_AnimSpeedX ("Anim Speed (X)", Range(0,4)) = 1.3
_AnimSpeedY ("Anim Speed (Y)", Range(0,4)) = 2.7
_AnimScale ("Anim Scale", Range(0,1)) = 0.03
_AnimTiling ("Anim Tiling", Range(0,20)) = 8
}
The default Unlit Shader only had a single texture property _MainTex
, so you added more properties to control both the opacity of the water and how the surface texture animates.
Update the Tags section next and just after the LOD 100
line, add some new options:
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
LOD 100
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
The default tags expected an opaque material, so you added and adjusted some flags to support transparency. In particular, note that turning ZWrite
off means the water won’t obscure other objects in the depth buffer, while you set Blend
to use alpha values.
Now observe the CGPROGRAM
section with its set of pragma
directives.
You worked with a Surface Shader previously, but now you’re dealing with straight Vertex and Fragment Shaders. Thus, whereas the #pragma
directive declared the Surface Shading function before, this time the #pragma
directives declare Vertex and Fragment Shading functions.
Also note the #include
statement. Unity provides a library of useful functions here (which this line imports for you to use in the shader). They do useful things like sample the texture and keep track of time.
Surface Shaders get these libraries automatically when you generate their code, but you need to include them explicitly for Vertex and Fragment Shaders.
Now take a look at the included structures:
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
There are two input structures this time, instead of just one input structure for a Surface Shader. You input the first struct to the Vertex Shader, which outputs the second struct. That output from the Vertex Shader then goes to the input of the Fragment Shader.
Just below the sampler2D _MainTex;
line in the main Cg code, add the following new properties:
float4 _MainTex_ST;
half _Opacity;
float _AnimSpeedX;
float _AnimSpeedY;
float _AnimScale;
float _AnimTiling;
Just as in the previous Surface Shader, you must declare all the properties as variables in the Cg code. You’ll use these properties to modify the texture and animate it in the main shader function.
Now, in the main frag function of the Shader, right at the top (above the // sample the texture
code comment, add:
// distort the UVs
i.uv.x += sin((i.uv.x + i.uv.y) * _AnimTiling + _Time.y * _AnimSpeedX) * _AnimScale;
i.uv.y += cos((i.uv.x - i.uv.y) * _AnimTiling + _Time.y * _AnimSpeedY) * _AnimScale;
These two lines of code are the meat of the animated water effect. This code offsets the UV coordinates it receives, with the amount of offset changing over time.
The result is an undulating animation of the texture. Meanwhile, both lines of code do essentially the same thing but in different directions. You’ll understand both after breaking down one line in detail.
First, the code calls a trigonometry function, either sine or cosine, so that the value will range on a repeating wave. _AnimTiling
scales the number passed in. As the name implies, _AnimTiling
controls the tiling of the effect – in this case, by affecting the frequency of the sine wave.
Next, you add the time to the number passed in, causing the returned value to move along the sine wave over time. The library code from earlier includes a variable that tracks the time.
_Time
is a vector of four numbers, with the four values being the time scaled by various amounts for convenience. That’s why the code uses _Time.y
and not simply _Time
; y
is the second number in the vector.
Finally, you multiply the entire thing by _AnimScale
, a property that controls how strong the effect is. Greater scale means more undulation.
Lastly, just above the return col;
line of code in the frag
function, add:
col.a = _Opacity;
Here, the code simply sets the alpha value to the _Opacity
property. Make sure to do this after sampling the texture; otherwise, the texture would overwrite the alpha value.
Now you’ve written your Shader, you can assign it to the cartoon-water material.
By default, the water’s surface won’t animate in the Scene view, but it will animate in the Game view when you press Play.