DEV Community

Chetan Sharma
Chetan Sharma

Posted on

Introduction to Shader Coding [for beginners]

What is a Shader?

Code that runs on GPU.

In Unity, shader programs are written in a variant of HLSL language (also called Cg but for most practical uses the two are the same)

In computer graphics, a shader is a type of computer program originally used for shading in 3D scenes (the production of appropriate levels of light, darkness, and color in a rendered image). They now perform a variety of specialized functions in various fields within the category of computer graphics special effects, or else do video post-processing unrelated to shading, or even perform functions unrelated to graphics.

Shader vs Material

The material gives input/parameters to shaders.

Shader works on those inputs.

Different materials can share the same shader with different properties(inputs to shaders).

Object refers to material, material refers to a shader. (in Unity)

Image description

Types of Shaders

Vertex Shader

Each mesh is made up of a bunch of vertices arranged in a 3d space.

A vertex shader takes into account the properties/data associated with these vertices to perform any operation on them.

The most important work of vertex shader is vertex transformation — the first stage in graphics pipeline — it transforms the vertex position(local space position) into screen position — which later goes through rasterization for other operations.

For example, if you want the mesh to deform at random vertices — you can do that with a vertex shader.

Image description

Fragment/Pixel Shader

A frag and pixel are technically two different things but we can now just imagine them to be the same/related for better understanding.

Just like a mesh is made up of many vertices — these vertices are joined together to form triangles/polygons — a fag shader works on the frag/pixels in the area between the vertices forming the triangles.

The rasterization process breaks up each geometric primitive, such as a triangle, into pixel-sized fragments for each pixel that the primitive covers. A fragment has an associated pixel location, a depth value, and a set of interpolated parameters such as a color, a secondary (specular) color, and one or more texture coordinate sets.(Source : nvidia cg tutorials)

Image description

A frag shader works on these fragments formed during the rasterization process.

Thus a frag shader may work on the output from the vertex shader to attach a color to each pixel/frag of the area of the polygons forming the mesh.

Image description

Creating a basic Unlit Shader in Unity

  • Right-click anywhere in the Project panel to create a new Unlit Shader (Name it whatever you want — I am calling it “SimpleShader”)

Image description

  • Right-click the new shader file and create material out of it. (Name it whatever you like — I am calling it “SimpleMaterial”)

Image description

  • Create any primitive shape (3d cube, sphere, etc) and drag and drop the material (SimpleMaterial) on the 3d Gameobjects.

Image description

Now click the SimpleShader file to open it in Visual Studio :

you will see something like this (just go through it — you don’t need to understand anything)

Shader "Unlit/SimpleShader"{
    Properties{
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader{
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata{
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f{
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v){
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target{
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The above code has a lot of template code — that we are not gonna use for now — so I have removed all the code related to UnityFog and Properties — after removal and some changes your shader will look like this

(Read the comments carefully) — you can copy-paste this code

Shader "Unlit/SimpleShader"//heirachy and name of shader file
{
     //this properties will appear in the unity inspectors such as textures 
     //colors etc - you can use them in main shader code 
    Properties
    {
    }

    //main shader code begins here
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass {
            CGPROGRAM
            //we define the vert(vertex shader) and frag(fragment shader)
            #pragma vertex vert //this is our vertex shader
            #pragma fragment frag //this is our fragment shader

            //we include a unitycg library code - which has a lot of functions
            //which will help us
            #include "UnityCG.cginc"

            //input to vertex shader
            struct appdata{
                float4 vertex : POSITION;//positon of vertex from the mesh
                float2 uv : TEXCOORD0;//uv coordinates
            };

            //output of vertex Shader
            struct v2f{
                float4 vertex : SV_POSITION;
            };


            //main vertex shader code
            v2f vert (appdata v){
                v2f o;
              //converting object vertex position to clip position (we have to do this kinda                            compulsary)
               o.vertex = UnityObjectToClipPos(v.vertex);
               return o;
            }

            //we can declare properties if we want to use them in shader code (name should remain same                  as that in Properties block)                        
            //main fragment shader 
            //changed return values from fixed4 to float4
            float4 frag (v2f i) : SV_Target{
            //We are not using textures any more so just returned 
            //a basic white color (Red 1, Green 1 , Blue 1, Alpha 0)
                return float4(1,1,1,0);
            }
            ENDCG
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

What are POSITION, TEXCOORD0, SV_POSITION , SV_Target ?

These are called Semantics.

A semantic is a string attached to a shader input or output that conveys information about the intended use of a parameter. Semantics are required on all variables passed between shader stages.

Thus we can say using semantics can help to decouple the shaders.

An obvious benefit of semantics, you don’t need the same variable names between stages, so the Graphics pipeline does matching via semantics instead of the variable name, and rearranges data as long as you have a compatible layout.

They are just a standard developed by few companies and you will slowly learn all semantics as you start making shaders.

If you want to learn more about semantics: Semantics Microsoft Docs

So if we have to write pseudo code for an unlit shader it will look like this:

Shader "Unlit/SimpleShader"{
Properties {
    //Properties we want to provide to shader through unity inspector
}
SubShader{
Tags { //some tags }
  Pass {
//we define the vert(vertex shader) and frag(fragment shader) with #pragma
//input to vertex shader -> appdata
//output of vertex Shader -> v2f
//main vertex shader code  ->  vert
//we need to declare again the properties(with same name) if we want to use it in main shader code
//main fragment shader  code -> frag
        }
 }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion :

  • Shader code runs on GPU.
  • Two main types of shaders are vertex and frag shader.
  • Vertex shader works on vertices of mesh data and frag colors the pixels(frag)
  • In a simple unlit shader, we pass data from appdata(Vextex Inputs) to vert (main vertex shader code) which make changes and assign values to v2f (vertex outputs for frag shader)
  • Frag shader uses input form v2f to color/shade or make any change and return final output in (RGBA) form.

Image description

I’m still learning about shader coding.

For more details - you can follow this amazing tutorial by Freya Holmér on Youtube :

https://youtu.be/kfM-yu0iQBk

Top comments (0)