Monday, 4 April 2016

Custom Unlit Shader

Shaders are both fascinating and frustrating.

Writing shaders is very rewarding, however, it can lead you to total madness.

I already showed you how to write shaders which use the Stencil buffer; now I want demonstrate how to create a shader from the ground up in Unity.

I should probably point out that I am not a shader expert, but I do find the topic extremely interesting and Unity has a lot of functionality that makes writing shaders easier.

In this tutorial we will write the simplest of the shaders, one that does not compute lights or textures but simply returns a single color.  Also, the shader we are going to write is a so called "vertex/fragment" shader.

Unity has its own language for writing these programs, called ShaderLab. Other languages are also used, and one that we are going to be looking at is CG, which stands for C for Graphics, a shader language developed by Nvidia. The shaders we write are a mix of these 2 languages.

To begin with, right click in the project folder, select Create -> Shader -> UnlitShader. It doesn't really matter which options of shader you choose as we are going to completely delete the whole content.

I'm going to post the code below and I will then explain the details.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Shader "Custom/UnlitSingleColor"
{
 Properties
 {
 _Color("Tint",Color) = (1,1,1,1)
 }

 Subshader
 {
   Pass
   {

   CGPROGRAM

   #pragma vertex vert
   #pragma fragment frag


   struct input
   {

   float4 pos : POSITION;

   };

   struct v2f
   {

   float4 pos : SV_POSITION;
   };

   float4 _Color;

   v2f vert(input i)
   {
   v2f o;

   o.pos = mul(UNITY_MATRIX_MVP,i.pos); //unity_matrix_mvp goes first. these are matrices, orders in multiplications matters

   return o;

   }

   float4 frag(v2f i) : COLOR
   {
   return _Color;
   }

   ENDCG

   }

 }

when we write a shader we start with the keyword Shader, followed by the name. The name itself can be put after  "/" so it will appear in subfolders when we select it in the material. (Fig 1).

Fig 1
Then we create some proprierties. These variables are public and can be tweaked from the inspector in the shader submenu. The only variable we have is called _Color. Within the brackets, we first pass the name with which the variable will be displayed (in this case, "Tint") and we then pass it the type, which is of type Color. When passing this type, in the inspector we will have the option to choose the color we want to have using the palette. (Fig 2).

Fig 2
Now, on line 8, we start the Subshader. The subshader is, well, the shader itself. You can have as many as you need, and usually they perform differently and are written for different platforms. The graphics card will read each subshader you write and use the first one that is compatible with its system.

Right after that, we have the Pass keyword. A pass is basically a draw call. For this simple example, a single pass is enough for what we want to achieve. Some other cases require multiple passes, like the stencil buffer I mentioned earlier or if we want to use multiple lights.

With the CGPROGRAM statement we are now telling Unity that we are going to write in CG. This will end at line 49, with the ENDCG instruction.

If you remember, earlier i mentioned that we are going to write a vertex/fragment shader. This means that this program will contain a function called fragment, and one called vertex.

It is common practice to use frag and vert as names for these 2 functions. On lines 15 and 16 we tell Unity where to look for these 2 functions and what names they have: the vertex method is called vert and the fragment method is called frag.

Before I continue, let's explain what these 2 methods do. It's actually pretty simple to understand: the vertex program is the portion of code that runs for each vertex of the mesh, so you can use it to create animations or special effects like curved worlds. The fragment function runs for each pixel, so it is used to color our mesh and render textures.

The next thing we see is a struct called input. Here we basically grab all the information we need forom "outside". By that I mean we pass in all the variables regarding our model that needs to be processed like, in this case, the vertex position pos, of type float4, which is basically a Vector 4 of floats. The POSITION keyword you see written after is called a semantic, and it's used to communicate to the gpu what kind of variable this is. This variable is pretty much required for every shader we write as it is used to display the actual mesh that is going to run this particular shader.

The next struct called v2f also contains a float4 variable called pos, but the semantic is different. This is because we are going to take the vertex position from the input struct, which is a local position, and convert it into clip space position (with semantic SV_POSITION), which is basically a bunch of coordinates that Unity understands.

On line 32 I declare a float4 type variable called _Color. As you may have noticed this is the same name we gave to the Color type variable in the Proprieties section. This is done because as we are now writing in CG, this portion of code is not aware of what happend outside of it. So, we need a reference to the _Color parameter we declared earlier in the program. To do so, we simply re declare it in the CG section of our shader program using the same name. The type float4 is commonly used for colors as RGBA.

We finally got to the vertex method. This method is of type v2f with the struct input passed in.

In the method, we first declare a v2f object, called o.

The code on line 38 is something you will see in every shader: this does what I explained earlier, takes the local position of the vertex of the mesh and converts them into clip space so the model can be rendered in the scene. This is done by multiplying the pos variable of the input struct to the UNITY_MATRIX_MVP, which I believe it stands for model view projection. Remember, this is a matrix, so the order in which you multiply the 2 parameters matters.

You can try doing  mul(i.pos,UNITY_MATRIX_MVP) for fun, see what happens.

Lastly, we return the object o.

Finally, the fragment method.

This is of type float4, to represent a color, as its semantic suggests.

All we do here is to return the _Color value, which is nothing but the color we will pick in the inspector. So, every single pixel used by this mesh will be colored as dictated by the _Color variable.

Now you can just create a capsule or sphere, anything really, create then a new material, select this shader and attach it to the 3D object and see the result.

With white color, looks like this




No comments:

Post a Comment