Writing Your First Shader

I know many engineers who are interesting in writing shaders, but don’t know where to begin. Shaders, especially in Unity, are a lot of fun to write and work on. Generally when you’re working on a shader it’s for a specific feature, and your goal is to make that thing look awesome. You can iterate quickly on shaders, and Unity does a great job of providing an abstraction layer so you don’t have to worry as much about individual platforms. In other engines I have worked with, the pipeline for adding a new shader involved a lot of work. I would write a new shader file to be referenced in Maya, add the new shader to the interface in Maya for selecting shaders, update the code that exports data from Maya to export the new shader, update the per-platform compilation process for the game to push the new shader data through, and then work with the graphics programmers on each platform to load in that data in-game, and write the shader code per-platform. This entire process could take as long as three weeks to write a new shader. In Unity, it can be as quick as five minutes. Shaders are assigned within the Unity editor, after the art has been exported from Maya, and Unity handles compiling your shader for each platform.

Unity provides you three ways to write shaders: fixed function shaderlab shaders, surface shaders, and the standard CG vertex and fragment shaders. I’m going to focus on the vertex and fragment shaders written in Nvidia’s C for Graphics language, as it’s a shader language not exclusive to Unity, so the code is a little more portable. Unity has a lot of great reference available on its site for writing shaders, such as herehttp://docs.unity3d.com/Documentation/Components/SL-Reference.html. Even with that infor available, developers are still hungry for more info on shaders. I’m going to walk through a simple shader setup here, at the end of this walkthrough you will have an unlit shader that displays a texture.

First, make a new Unity project to work in. Next, right click in your project view, and create your shader. For now, you should just give it a name like “Unlit_Texture.” Naming shaders can be pretty difficult, but it is extremely important you provide a strong name for your shaders that make it obvious what it does, so the content creators who are browsing through the shader list later have a clear idea of the purpose of each shader. Next, create a material in your project, and assign your new shader to that material. After that, get a texture file and place it in your project. If you have nothing available, do a google image search for something like “The best texture ever”, and use one of those as your test texture. Finally, create a sphere in your Unity scene, and apply the material to it. You now have an object in your scene using the shader you will work in. As you make changes, you can tab from your code editor back to Unity, and see the changes you have made to your shader easily.
Now that you have an environment setup to work on a shader, open that shader up in your code editor of choice. You’ll see Unity has pre-populated it with some logic and functionality. What you have by default with a new shader is a diffuse lit surface shader. Today we are focusing on CG shaders, so let’s clear the file out down to the following code:

Shader “Custom/Unlit_Texture”
{

}

All this does is define the name of a shader, and provide the scope for writing the shader within the curly braces. I recommend matching the shader name to the name of the file, so it’s easy to match the code to the interface for selecting shaders. The “Custom” folder path is how Unity will present the shader interface for content creators to find shaders. For now this is OK, but as you write more shaders, I recommend devising a strict naming convention for folder paths, so content creators can find the shaders they intend to use.

Next, add a subshader to your shader.

Shader “Custom/Unlit_Texture”
{

SubShader
{

}

}

We’re only going to have one subshader in this file, subshaders are generally used to handle different video cards and different platforms. The shader we are building is simple enough we don’t need to worry about this.

After a subshader, add a pass to your shader.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

}

}

}

When rendering an object with a shader, each pass will be rendered. For our shader, we will only have one pass. Multiple passes are used for a variety of reasons, such as rendering a character in an RTS with a solid color if they are obscured by a building.

Next, we need define this shader as a C for Graphics shader.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

CGPROGRAM
ENDCG

}

}

}

Any code within the “CGPROGRAM” and “ENDCG” tags is handled as CG code.

We are finally getting to the meat of the shader, up next are the function declarations for the vertex and fragment programs.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

vert()
{

}

frag() : COLOR0
{

}

ENDCG

}

}

}

The pragmas there tell the compiler what the vertex and fragment program are called. You will often hear fragment and pixel shader used interchangeably, fragment is technically more accurate, but people seem more familiar with the concept of a pixel shader. Another pattern you’ll start seeing a lot in shaders is a colon after a declaration, followed by a word. In the declaration of the fragment program in the above code, you’ll notice : COLOR0. This is called a semantic, it is used as a system to inform the shader compiler the intended usage of whatever it is next to. See the MSDN page on semantics for more information:http://msdn.microsoft.com/en-us/library/windows/desktop/bb509647(v=vs.85).aspx. Selecting semantics can sometimes get tricky, and if you select your semantics incorrectly, you might have problems that only occur on certain video card families.

Up next we are going to add some structure declarations.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct v2f
{

};

struct appdata
{

};

v2f vert(appdata v)
{

}

frag(v2f i) : COLOR0
{

}

ENDCG

}

}

}

The basic flow of rendering a mesh is, each vertex is run through the vertex program you define in the shader. Then, each pixel between the vertices that form a triangle are rendered with the fragment program. The data input into the fragment program is automatically interpolated from the three vertices in the triangle the pixel is a part of. The appdata structure defines what information we want passed into our vertex program. The v2f structure is the data we want passed from the vertex program to the fragment program.

The next set of code will be the first shader you can actually run. It will render the sphere you assigned this shader to entirely white.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

struct v2f
{

float4 pos : SV_POSITION;

};

struct appdata
{

float4 vertex : POSITION;

};

v2f vert (appdata v)
{

v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
return o;

}

fixed4 frag (v2f i) : COLOR0
{

return fixed4(1,1,1,1);

}

ENDCG

}

}

}

A few new things here. The position of the vertex is passed into the vertex program. This position is local to the model you are rendering, so it needs to be moved into the world to be rendered. The vertex position is multiplied by the model * view * projection matrix to place it in the world. You can see other global state data available to you with Unity shaders herehttp://docs.unity3d.com/Documentation/Components/SL-BuiltinStateInPrograms.html. Finally, the fragment program returns a color value for the pixel rendered. In this case, the fragment program is returning pure white, an RGBA value of 1,1,1,1. I’m sure you are tempted at this point to get something a little more interesting of a color, maybe display that position value as a color. If you change the fragment program to return i.position, you will notice that your game object turns bright pink in the Unity editor, and you have an error “Shader error in ‘Custom/Unlit_Texture’: Program ‘frag’, Shader model ps_4_0_level_9_3 doesn’t allow reading from position semantics. (compiling for d3d11_9x) at line 27.” The position value is used by the GPU to calculate where to display the fragment, and is not something you can access directly with your fragment program.

Lets get some basic color in there, we’ll pass a color value from the vertex program to the fragment program.

Shader “Custom/Unlit_Texture”
{

SubShader
{

Pass
{

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

struct v2f
{

float4 pos : SV_POSITION;
float4 col : COLOR0;

};

struct appdata
{

float4 vertex : POSITION;

};

v2f vert (appdata v)
{

v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.col = o.pos;
return o;

}

fixed4 frag (v2f i) : COLOR0
{

return i.col;

}

ENDCG

}

}

}

You’ll notice that the color of the object depends on it’s position in the scene, move the camera around in the scene and notice that the sphere changes colors. What we’ve added here is a color value to the v2f structure, populated by the position of the vertex in the vertex program, and referenced by the fragment program to return as a color.

We are ready for the final, unlit texture shader, as follows.

Shader “Custom/Unlit_Texture”
{

Properties
{

_MainTex(“Texture”, 2D) = “white” { }

}
SubShader
{

Pass
{

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include “UnityCG.cginc”
sampler2D _MainTex;
float4 _MainTex_ST;

struct appdata
{

float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;

};

struct v2f
{

float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;

};

v2f vert(appdata v)
{

v2f o;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
return o;

}

fixed4 frag(v2f i) : COLOR
{

return tex2D (_MainTex, i.uv);

}

ENDCG

}

}

}

The first new thing here is the properties block. This is the data that needs to be set externally from the shader. In this case, we are looking for a texture. If no texture is provided, the fallback value is white. Next, you’ll notice that we defined a sampler2D _mainTex and a float4 _MainTexture_ST within the shader pass. This is a collection of information that our vertex and fragment programs can use to access the texture. It includes tiling and offset information, as well as the information to pull a color out of the texture. Within the appdata structure, you will notice we have added a texture coordinate. This is a piece of data stored on every vertex on the mesh, a two dimensional vector generally referenced as “UV” coordinates, used to reference a location within a texture. Generally, the artists will define these texture coordinates within their content creation tool, such as Maya, and build and layout the texture to be used by the mesh. The TRANSFORM_TEX is a function defined within the file we included, “UnityCG.cginc.” These include files contain a lot of really useful tools and shortcuts for building your shaders, and are worth looking through to see what is available to you. What this function does is apply the offset and tiling information to the texture. To see the effect, change the tiling and offset information on your material. You’ll notice the texture in the scene view changes position on your sphere. If you change the line of code to “o.uv = v.texcoord” you will notice that you lose this tiling and offset information, the texture behaves as if you have a tiling of 1,1 and an offset of 0,0. The final new line of code here is in the fragment program, the call to the tex2D function. This function takes in the texture we want to pull information from, and the UV coordinates, and returns the color value at that position.

Now you have a very simple shader that renders an unlit texture. Next time, I will cover lighting, to bring a little more life to the model displayed.

Advertisements
About

Joseph Stankowicz is a software engineer who has worked in the video games industry for over eight years. The last two years have had a heavy focus on Unity development, where he helped ship over eleven titles to iOS and Android platforms. He also is really excited about 3D printing, and keeps his Solidoodle 3 printing out stuff as often as possible. You can view his LinkedIn profile here http://www.linkedin.com/pub/joseph-stankowicz/60/294/420

Tagged with:
Posted in Unity3D
4 comments on “Writing Your First Shader
  1. Mike says:

    Hi Joe,

    I somehow failed to learn much about shaders over the years, so I wanted to try out your tutorial and educate myself. I’ve got it all working and it looks great. But I wanted to list the things that tripped me up, or else just weren’t entirely clear for a beginner (like myself). I haven’t yet looked any of this stuff up to answer my own questions because I don’t want to pollute my ignorant state of mind yet. 🙂

    1. As I was going through the steps, I wasn’t initially clear how to assign my shader to the material. I think this is because I looked in the Shader drop-down with the material selected and couldn’t find the one I had just created (the trick is that it was in the Custom menu, but I think I solved it by dragging and dropping). This is actually a really minor issue in retrospect but someone unfamiliar with the process might not know what to do here.

    2. Applying the created material to the newly created sphere might need a little bit of explanation, since beginners might not know that you need to assign it to the materials property of the Mesh Renderer component, since a sphere has a Sphere Collider component which also has a Material Property (for physics material). Again in retrospect this seems like a really basic issue but I’m thinking of someone not completely familiar with Unity.

    3. I was never really clear what a SubShader is or how this differs from a pass. Can I have multiple SubShaders, and what’s the difference between having multiple subshaders versus multiple passes? I guess it just struck me as something unexpected, because I would just think “Hey, we have a shader… why are we defining something called a ‘sub’ shader?”

    4. I really often see shader code that has very simple, short, single-letter variable names like ‘v’, etc.? There’s no harm if I make long variable names right? I doubt it, I just want to make sure there isn’t some convention at work here.

    5. I may have missed it, but I didn’t see an explanation of the types used and their typical purposes. Seeing a “float4”, for example, was pretty self-explanatory, but it threw me off when the fragment shader returned a “fixed4”. I would have just expected it to return a “float4” but I didn’t see any explanation as to why this was different.

    6. I was confused about the Properties block. So, is that just data that gets initialized for the shader before it runs? Is it initialized once for the entire render operation? The _MainTex syntax was confusing also and I wasn’t sure what all the components meant. It looks like an odd method call with parameters being passed, or just some properties table or something, and the parentheses at the end didn’t make sense to me. It reminds me of Lua script or something. I also see that _MainTex is being referenced elsewhere in the program, so clearly it’s important. Is “Texture” some reserved string in that initialization? What does the ‘2D’ mean?

    7. Where is _MainTex_ST ever used? I see it declared but never used, which is really confusing.

    Thanks again for the great tutorial and I’m sorry to spam you with all of this, but it’s just because I’m excited to learn and wanted to give you impressions from an actual beginner.

  2. Joe says:

    This is awesome feedback, and you have some really fantastic questions! I will write up answers to them all later (it’s time to play video games right now), I will probably just do it as next week’s blog if you’re OK with me using your questions as source for a blog post.

  3. […] of really well worded and well thought out questions on my “Writing Your First Shader” post https://josephstankowicz.wordpress.com/2013/06/22/writing-your-first-shader/#comment-72. Answering these questions seemed like a great topic for this week’s […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: