Search

Vertex Shaders in Unity

Shader are just like other programs, well actually there is quite a bit differences, but it does look like just normal programming. So if you are a programmer you will feel right at home from very beginning. This article doesn’t expect to you to have any shader programming experience, but basic knowledge of how to use shaders (in Unity) and basic programming is warmly recommended. In this article I’m using Unity and CG shader language which isn’t exactly Unity specific. However shader itself will not work outside of Unity since it’s wrapped inside of ShaderLab language (more about that later on). This article is going to cover only one directional light for sake of simplicity. Handling multiple lights and different light types is rather straightforward task after you understand all shader stuff. Now, let’s put this disclaimer a side and write some magic.

Actually magic can wait a bit. First some boring stuff and then magic. Shader in Unity comes in three forms.

  • Fixed Function Shaders
  • Vertex/Fragment Shaders
  • Surface Shaders

Each of these are bit different and you need choose the one which fits for your needs. Fixed Function Shaders are for legacy devices which doesn’t support real shaders. We aren’t interested about those at all so we just skip the whole thing and say that you must have device with shader support. Vertex and Surface shaders are pretty much a same thing. Both uses CG or HLSL language. Yet shader code is always wrapped into ShaderLab code (still coming to that ShaderLab thing). Vertex shaders are more lower level shaders than surface shaders. Surface shaders handle some stuff under the hood to make your life easier, but it also means that you can’t fully see what is happening. Vertex shaders are low level shaders where you handle everything by yourself. This is bit more work, but you will have full understanding what is going on in your shader. Surface shaders are also same vertex shaders. After you write your surface shader Unity will convert it to vertex shader. This is potential place for bugs and performance hits, but I have understood that surface shaders works well and mostly those are very useful and simplifies your shader code. In this article I gonna focus on vertex/fragment shaders.

This article is based on amazing Wikibooks article series CG Programming. Which you should definitely check out as well Wikibooks – CG Progarmming. I’m also using information for Unity’s ShaderLab manual which has lot of good information about ShaderLab language ShaderLab syntax. Last but not least I want to thank to James O’Hare of his excellent comments and help in Unity community. Here’s his webpage http://www.farfarer.com/.

And now that ShaderLab thing. What the he… that is? Well, it’s language as well. It describes the shader and handle shader passes. Here as example one really simple shader from Unity’s manual.

Shader "Simple colored lighting" {
    Properties {
        _Color ("Main Color", Color) = (1,.5,.5,1)
    }
    SubShader {
        Pass {
            Material {
                Diffuse [_Color]
            }
            Lighting On
        }
    }
}

Shader above define a shader “Simple Colored lighting”, this is done with keyword Shader. Then it set some properties in Properties list. These properties will show up in Unity’s UI. Then code define SubShader(s). Unity take first subshader what works with your graphics card so you could have multiple of these subshaders here. Actual magic happens inside SubShader section where you can see Pass section. You’ll write your code inside of this Pass section. There could also be multiple Passes which are executed one after other. After this we are out of ShaderLab part and move on to the actual shader part.

And now that magic. We start by writing really simple shader kinda like hello world of shaders. This shader just assign red color to the mesh.

Shader "Tutorial/FixedColor"
{
   SubShader
   {
	  Pass 
	  {
		 CGPROGRAM
 
		 #pragma vertex vert             
		 #pragma fragment frag
 
		 struct vertInput
		 {
			float4 pos : POSITION;
		 };  
 
		 struct vertOutput
		 {
			float4 pos : SV_POSITION;
		 };
 
		 vertOutput vert(vertInput input)
		 {
			vertOutput o;
			o.pos = mul(UNITY_MATRIX_MVP, input.pos);
			return o;
		 }
 
		 half4 frag(vertOutput output) : COLOR
		 {
			return half4(1.0, 0.0, 0.0, 1.0); 
		 }
		 ENDCG
	  }
   }
}

shader01

This is very basic shader and may not even require any kind explanation, but it’s very fundamental stuff so let’s make it super clear for you.

So we focus on the part inside of Pass section. Stuff around the Pass section is just ShaderLab wrapping. So CGPROGRAM tells that this is where CG begins and ENDCG at the end of the shader code tells that CG code ends.

Then next two #pragma lines define vertex and fragment shader functions. Vertex is vert and fragment is frag, but you can name these as you like.

Then there is vertOutput struct, these are variables for vertex shader which Unity will automatically fill up in each draw call. If you look inside of vertInput struct you will notice that it contain one variable definition with odd : POSITION prefix. That : POSITION is something what CG calls semantics. It is used to map this variable to certain input type such as vertex position in this case. It could also be number of other types and some of those will be introduced later on. I haven’t yet found complete list of semantics for CG in Unity, but here’s HLSL list which is at least some of what compatible. Semantics for HLSL.

Then yet another struct vertOutput. This struct is filled up in vertex shader, but since this data is going to fragment shader this data will be interpolated.

Then next thing is vertex function called vert. It simply project vertex to model*view*projection space (position in your camera view). We assign that position to vertOutput struct so it will be transferred to fragment function.

Now last part is fragment function. This just assign new color to the mesh and does nothing else. So that’s it, not to hard yet.

Now let’s modify it so that we can change that color.

Shader "Tutorial/FixedColor"
{
	Properties
	{
		_Color ("Main Color", Color) = (1,1,1,1)
	}
 
	SubShader
	{
		Pass 
		{
			CGPROGRAM
 
			#pragma vertex vert             
			#pragma fragment frag
 
			half4 _Color;
 
			struct vertInput
			{
				float4 pos : POSITION;
			};  
 
			struct vertOutput
			{
				float4 pos : SV_POSITION;
			};
 
			vertOutput vert(vertInput input)
			{
				vertOutput o;
				o.pos = mul(UNITY_MATRIX_MVP, input.pos);
				return o;
			}
 
			half4 frag(vertOutput output) : COLOR
			{
				return _Color; 
			}
 
			ENDCG
		}
	}
}

shader02

Still super simple. New thing is that Properties list which now has one property defined _Color. This will be shown in Unity UI as color chooser element. You access properties by define CG variable with same name as the property and it will be automatically linked to it. Then you can use that variable like any other variable in your code. Variable comes with several different data types and you always need to use compatible data type for your property. I will show you few of these while we dig in deeper to shader magic.

So now we have done very basic stuff and it doesn’t really look much of anything yet. So let’s start to make it bit more complex and add some lighting.

Lights are very complicated thing, but let’s again start with simple things and work our way further.

Shader "Tutorial/DiffuseVertex"
{
	Properties
	{
		_Color ("Main Color", Color) = (1,1,1,1)
	}
 
	SubShader
	{
		Pass 
		{
			Tags { "LightMode" = "ForwardBase" } 
 
			CGPROGRAM
 
			#pragma vertex vert             
			#pragma fragment frag
 
			#include "UnityCG.cginc"
 
			uniform half4 _Color;
			uniform float4 _LightColor0;
 
			struct vertInput
			{
				float4 pos : POSITION;
				float3 nor : NORMAL;
			};  
 
			struct vertOutput
			{
				float4 pos : SV_POSITION;				
				half4 col : COLOR;				
			};
 
			vertOutput vert(vertInput input)
			{
				vertOutput o;
 
				float4 normal = float4(input.nor, 0.0);
				float3 n = normalize(mul(normal, _World2Object));
				float3 l = normalize(_WorldSpaceLightPos0);
 
				float3 NdotL = max(0.0, dot(n, l));
 
				float3 d = NdotL * _LightColor0 * _Color;
				o.col = float4(d, 1.0);
				o.pos = mul(UNITY_MATRIX_MVP, input.pos);
 
				return o;
			}
 
			half4 frag(vertOutput input) : COLOR
			{	
				return saturate(input.col); 
			}
 
			ENDCG
		}
	}
}

shader04

Starting to feel it already? It’s getting hard, but this is still easy. Let’s see what’s going on there.

This is something called diffuse lighting or diffuse reflection. It attempt to simulate how light is reflected from surface. It creates matte surfaces with no highlights (specular highlight). This is very simple simulation model, it just calculate dot product between surface normal and light direction. If directions are facing each other dot product will be -1, 1 if they are facing to same direction and 0 when directions are in 90 degrees angle difference. So we can say that when dot product is negative surface is facing away from light source so no light hit to that surface and when dot is positive light hits to the surface. Math is very simple.

shader06

This video shows how values of dot product, between position of the light source (star shape) and normal vector (red arrow), are changed when light source is moving around the surface point.

shader13

So NdotL is kind of like multiplier for light intensity per vertex. This isn’t hard to implement to your shader, but there is few things you need to do as always. First surface normal must be transformed to world space.

float3 n = normalize(mul(normal, _World2Object));

Then we take direction to light source. This is done by simply normalizing light world position vector.

float3 l = normalize(_WorldSpaceLightPos0);

Then take that dot product

float3 NdotL = max(0.0, dot(n, l));

Now we have what we need to calculate lighting effect. So multiply light color with object’s color and all that multiplied with dot product so surface areas which are more turned away from light will get less light (less photons in physical terms).

float3 d = NdotL * _LightColor0 * _Color;

That’s it. Now just put values into vertOutput struct and in fragment function just return interpolated color value.

Right now we just get diffuse reflection, but problem is that parts which are turned away from light became completely dark so shaders often have ambient light effect which prevent objects to go fully dark. This is very simple thing to implement.

Shader "Tutorial/DiffuseAmbientVertex"
{
	Properties
	{
		_Color ("Main Color", Color) = (1,1,1,1)
	}
 
	SubShader
	{
		Pass 
		{
			Tags { "LightMode" = "ForwardBase" } 
 
			CGPROGRAM
 
			#pragma vertex vert             
			#pragma fragment frag
 
			#include "UnityCG.cginc"
 
			uniform half4 _Color;
			uniform float4 _LightColor0;
 
			struct vertInput
			{
				float4 pos : POSITION;
				float3 nor : NORMAL;
			};  
 
			struct vertOutput
			{
				float4 pos : SV_POSITION;				
				half4 col : COLOR;				
			};
 
			vertOutput vert(vertInput input)
			{
				vertOutput o;
 
				float4 normal = float4(input.nor, 0.0);
				float3 n = normalize(mul(normal, _World2Object));
				float3 l = normalize(_WorldSpaceLightPos0);
 
				float3 NdotL = max(0.0, dot(n, l));
				float3 a = UNITY_LIGHTMODEL_AMBIENT * _Color;
 
				float3 d = NdotL * _LightColor0 * _Color;
				float4 c = float4(d+a, 1.0);
				o.col = c;
				o.pos = mul(UNITY_MATRIX_MVP, input.pos);
 
				return o;
			}
 
			half4 frag(vertOutput input) : COLOR
			{	
				return saturate(input.col); 
			}
 
			ENDCG
		}
	}
}

shader05

Only thing I really did is to add this line. Which takes Unity’s ambient light value from Render settings and multiply it with shaders color value.

float3 a = UNITY_LIGHTMODEL_AMBIENT * _Color;

To add it into final color is as simple than simple add.

float4 c = float4(d+a, 1.0);

Let’s move on. Next thing specular highlights. This is getting harder and harder. Hang in there.

Shader "Tutorial/SpecularVertex"
{
	Properties
	{
		_Color ("Main Color", Color) = (1,1,1,1)
		_SpecColor ("Specular Color", Color) = (1,1,1,1)
		_Shininess ("Shininess", Float) = 10
	}
 
	SubShader
	{
		Pass 
		{
			Tags { "LightMode" = "ForwardBase" } 
 
			CGPROGRAM
 
			#pragma vertex vert             
			#pragma fragment frag
 
			#include "UnityCG.cginc"
 
			uniform half4 _Color;
			uniform float4 _LightColor0;
			uniform float _Shininess;
			uniform float4 _SpecColor;
 
			struct vertInput
			{
				float4 pos : POSITION;
				float3 nor : NORMAL;
			};  
 
			struct vertOutput
			{
				float4 pos : SV_POSITION;				
				half4 col : COLOR;				
			};
 
			vertOutput vert(vertInput input)
			{
				vertOutput o;
 
				float4 normal = float4(input.nor, 0.0);
				float3 n = normalize(mul(normal, _World2Object));
				float3 l = normalize(_WorldSpaceLightPos0);
				float3 v = normalize(_WorldSpaceCameraPos);
 
				float NdotL = max(0.0, dot(n, l));
				float3 a = UNITY_LIGHTMODEL_AMBIENT * _Color;
				float3 d = NdotL * _LightColor0 * _Color;
				float3 r = reflect(-l, n);
				float RdotV = max(0.0, dot(r, v));
				float3 s = float3(0,0,0);
				if (dot(n, l) > 0.0) 
					s = _LightColor0 * _SpecColor * pow(RdotV, _Shininess);
 
				float4 c = float4(d+a+s, 1.0);
				o.col = c;
				o.pos = mul(UNITY_MATRIX_MVP, input.pos);
 
				return o;
			}
 
			half4 frag(vertOutput input) : COLOR
			{	
				return saturate(input.col); 
			}
 
			ENDCG
		}
	}
}

shader07

Specular highlights aren’t really there in real world. Real world specular highlights are just normal reflections, but since those are expensive to calculate it’s common to simulate that with specular highlights. This lighting model is called Phong.

Most parts of it is just basic diffuse shading (Lambert), but then highlight part. Highlights are drawn in places where reflection vector between light direction and normal vector is pointing towards view direction. So this is just a few of dot products again. Important step is to calculate that reflection vector.

float3 r = reflect(-l, n);

So we take reflection vector between light direction and normal vector. Note! Light vector should be negated for reflections. Video shows how reflection vector (blue arrow) is behaving.

shader14

When we have reflection vector we know where light rays bounce off from surfaces so we want to draw highlights on these areas. So take dot between reflection vector and view direction to determine if reflection vector is pointing towards a camera.

float RdotV = max(0.0, dot(r, v));

Now we get 1 if vector is pointing directly to camera and less than 1 when vector isn’t pointing directly to camera. If we take that dot product and raise it by power of shininess we can control how much highlight spreading. If you plot curve f(x) = x^2 you get following curve.

shader15

And if you plot curve f(x) = x^5 you get following curve.

shader16

Let’s keep going and plot curve f(x) = x^10.

shader17

That is a curve form representation of highlight spreading. You can basically write whatever expression for this curve, but power is used in Phong model.

This is it for vertex based shaders. Next time we gonna make pixel based shaders.


Comments

Shokri said 2016-12-21 19:39:46 :

Thank you dear Verajankorva. Your explanation plus those video clips are superbly comprehensive (at least for guys like me who are unfamiliar with shader programming). Those short clips make your tutorial much more clear than anything else! Thank you for your good article.

unity shaders tutorial | Code goFrance! said 2016-06-02 20:23:40 :

[...] Vertex shaders in unity - verajankorva Vertex shaders in unity. shader are just like other programs, well actually there is quite a bit differences, but it does look like just normal programming.. [...]

xcx said 2015-10-26 00:10:40 :

Very nice to hear that my rather bad English grammar is something what people can actually understand and of course I'm even more happy that this article is helped some of you.

Clowder said 2015-10-25 22:27:32 :

I just want to thank you for writing this. I had read several other shader tutorials, but none of it made sense until your explanations. I'm finally making progress with my graphics! :D

Chris said 2015-10-24 07:15:45 :

This is exactly what I needed to get started! Thanks!

Vertex and fragment shaders in Unity3D | Alan Zucconi said 2015-07-01 14:01:26 :

[...] of lighting models. If you’re interested in this, Antti Verajankorva has posted an interesting article about it. This post also introduces normal maps, grab passes and how they can be used to [...]

Excellent tutorial on using the Lighting Equation in Unity said 2015-05-24 00:01:32 :

Excellent tutorial on using the Lighting Equation.

xcx said 2015-04-19 21:34:04 :

Willy Chyr, what's incorrect about it?

Willy Chyr | Wind Waker Waterfall or: Adventures in Shader Writing said 2015-04-19 00:13:43 :

[...] Vertex Shaders in Unity A great intro and breakdown of shaders in Unity. I think there’s some issues with terminology, specifically with the post using the term ‘vertex shaders’ incorrectly, but overall, it’s pretty good. [...]

urlGrey said 2014-05-18 19:07:59 :

Awesome! Nice to find an intro to vert shaders that's a bit more up-to-date :D Thanks so much!!

Leave your comment

Your nickname (will be shown on the page)


Comment


Are you human? If so choose images of rabbit, snail and fish to prove it.
1
2
3
4
5
6

Where's send button? Choose a correct image above and send button will appear here.

Send comment