Learn to write surface shaders in Unity in this complete tutorial series.
In this Unity tutorial series we will be giving you an introduction shader scripting, showing you how to write your our own surface shaders for your Unity game.
This text tutorial is accompanied by a complete video. I recommend watching the video and referring to the text as reference.
Be sure to read the tutorial carefully, and post any questions you may have if you get stuck, as there is a lot to cover. I will be using some examples from the Unity shader reference found here:
http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderExamples.html
My goal is to help you understand how shaders work and how you can write your own shaders from scratch in Unity.
NOTE: This tutorial requires very basic knowledge of scripting such as variables and functions. If you have no scripting experience I recommend you first watch our Introduction to Scripting tutorial in the getting started section.
Part 3 – Adding properties to our surface shaders in Unity
Our shader is quite boring at the moment, so we need to create a properties block so we can give it some inputs.
- A properties block allows us to have adjustable parameters within Unity such as images, slider values and color pickers.
- This saves us having to go into the shader every time we want to adjust something.
- As discussed earlier we can place properties in the properties block, and they come in a range of different types.
- They are written in the format:
- name(“display name”, Type) = Starting value - The name usually starts with an underscore, but is not required
Here is some examples of properties
|
1 2 3 4 5 6 7 8 9 |
Properties { _MainTex ("Texture", 2D) = "white" {} //This is a standard 2D image input, default white _BumpTex ("Normal Map", 2D) = "bump" {} // This is a normal map, the "bump" start value tells Unity to make sure it is a normal map _ColorTint ("Color Tint", Color) = (1,1,1,1) //This creates a color picker, default white _TintValue ("Tint Intensity", Range(0.0,1.0)) = 0.5 //creates a slider _Darkness ("Darkness", Float) = 0.25 //Creates a number input _Coord ("Vector coord", Vector) = (1,1,1,1) //Creates a vector input _Cube ("Cube Map",Cube) = "" {} //This is a cubemap reflection } |
Our properties go inside the subshader when we define them.
To define a property so we can use it we need to convert it into something Unity understands.
sampler2D [property];
- This tells Unity the property is a 2D image float4 [property];
- This tells Unity the property uses 4 floating point digits, most common for RGBA values such as a color picker, this is the most computationally expensive.
half3 [property];
- This is a half vector, a half is smaller than a float so takes less data, and is less accurate. The 3 is telling Unity the property has 3 half values, such as a vector.
fixed4 [property];
- A fixed value is smaller than a half, and the most optimized. For mobile shaders you will see a lot of fixed variables, we also tend to use fixed if we are not doing any calculations with it.
So after that your probably like “Woah! so why are we still using floats?!” Well floats are easier to work with, and it’s not a major performance hit to use them. We will tend to use fixed as the last variable as it is faster, such as if we want to take the alpha channel of an image and use it as a gloss map. We simply aren’t using the high accuracy of the float. We will talk about float vs half vs fixed at a later stage, but I just want to teach you that at this stage they are interchangeable terms. Ok so now we know how to use properties we need to have them actually control something.
Let’s start simple and add an image to our shader
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Shader "unityCookie/Basic Diffuse" { Properties { _MainTex ("Diffuse Texture", 2D) = "white" {} //We create a box to drop a texture into in Unity. } Subshader { Tags { "RenderType" = "Opaque"} CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; //Create a new variable and assign it the UV coords }; sampler2D _MainTex; // sample the image so we can use it void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; // assign the image to the output color. } ENDCG } Fallback "Diffuse" } |
Ok so we have some new ones in here. What’s this “float2 uv_MainTex;” or “tex2D (_MainTex, IN.uv_MainTex).rgb; ” ??
So let’s break this down.
float2 uv_MainTex;
- float2 – This is a float with two values, also known as a 2D vector (XY or UV)
- uv – We put this in front of our variable name to tell it to use the first UV map. To better explain this if you have a second UV map you would use “float2 uv2_MainTex”.
- We also use a sampler2D to sample the image so we can use it, we have this on every image.
Ok so that’s making more sense, what about the other one?
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
- So firstly, this is shorthand. Here is the long version:
float4 diffuseTex = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = diffuseTex.rgb;
- So we create a new float called diffuseTex, and using the tex2D function we take the color from _MainTex and the UV’s from uv_MainTex.
- We then pass the RGB color channels from them and pass them into the Albedo (output color)
Once again in shorthand:
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
So with our properties set up and image applied it’s time to get into normal mapping!


Now that osl support is included in blender is there any chance that you can do a tutorial about osl shader coding in blender?
Hi Shane,
I certainly hope to. my knowledge of OSL is pretty basic at this point.
But I’d love to cover some OSL programming for cycles.
We have a few series after this one covering more advanced shader programming (we’ll even delve into real-time sub surface scattering at some point!)
So OSL for cycles might be sometime early-mid next year after CG/HLSL and openGL
-Alex
Thanks