Shaders in Unity I

What’s a shader?

A shader is a program that indicates how an object should be shaded in the rendering process, it indicates what color a something should have at a certain position, it can also manipulate positions. The shader runs in the GPU.

Types of shader in Unity

  • Surface Shaders: Makes it easier to write lit shaders, it already has all the basic stuff on how to handle light n’ stuff so you don’t have to write it.
  • Unlit Shaders: Doesn’t interact with lights.
  • Image Effect Shader: Takes an image, does some math, outputs the image to render, useful for things like postprocessing.
  • Compute Shader: Needs knowledge of GPU’s, useful to make parallel processing.

CGPROGRAM Functions

dot(x,y) – dot product between x and y
normalize(x) – normalizes a vector
saturate(x) – clamps to 0 and 1
mul(x, y) – used to multiply vectors and matrices
pow(x, y) – powers x to the y
reflect(x, y) – returns a reflected vector based on the incidence vector x and normal vector y
step(x, y) – returns 0 or 1 if x => y, making a hard transition
lerp(x,y,z) – interpolates x and y based on z

Simple Shader Anatomy

// name and path of the shader from here you can add sub-menus
// and change the name of the shader without changing the name of the file
Shader "Unlit/ShaderMine"
{
    // all the inputs from the material
    Properties
    {
        // _MainTex = internal name
        // "Texture" = inspector name
        // 2D = property type
        // = "white" = default value
        // {} avoids errors, include it
        _MainTex("Texture", 2D) = "white" {}
        _Tint("Tint Color", Color) = (1,1,1,1)
    }

    // shader code
    // you can have multiple SubShaders for different devices
    SubShader
    {
        // used to tell how and when it's expected to be rendered
        // used to determine rendering order and other stuff
        // queue order (from first to last):
        // Background (skybox, etc...)
        // Geometry (default, opaque geometry)
        // AlphaTest (renders from closest to further away)
        // Transparent (renders from further away too closest)
        // Overlay (renders last, useful for thing like lens flares)
        Tags { "RenderType"="Opaque" }

        // gpu call
        Pass
        {
            // indicates that what's next is written in CG code
            CGPROGRAM

            // tells what's the name of the vertex and fragment shader
            // 'vert' and 'frag' can be called whatever
            // vertex shader is per vertex of the mesh (gets model data and can modify it)
            // fragment shader runs per pixel that the mesh occupies
            #pragma vertex vert
            #pragma fragment frag

            // includes function
            // UnityCG.cginc includes useful math stuff for shaders
            #include "UnityCG.cginc"

            /*
                DATA TYPES
                float (highest precision, 32bits) float(0), float2(0,0), float3(0,0,0), etc...
                int (32bit integer)
                bool
                half (medium precision, 16bits)
                fixed(lowest precision, 12 bits, fixed number of decimal digits, range from -2 to +2)
            */

            // the data of the mesh
            // vertex position, normal, UVs, tangents, vertex colors
            // says what data you want from the mesh
            // can be renamed
            struct appdata
            {
                // position of the vertex in local space (float3 or float4)
                float4 vertex : POSITION;
                // color of the vertex (float4)
                float4 colors : COLOR; // not used
                // normal of the vertex in local space (float3)
                float3 normal : NORMAL; // not used
                // tangent of the vertex, used for normal mapping (float4)
                float4 tangent : TANGENT; // not used
                // first uv channel (float2, float3 or float4)
                float2 uv0 : TEXCOORD0;
                // second uv channel
                // different uv channels can be used to store different data
                // for example, you can store baked light in one
                float2 uv1 : TEXCOORD1; // not used
            };

            // output of vertex that goes into the fragment shader
            // (v2f = vertex to fragment) 
            struct v2f
            {
                // most compatible with platforms like XBOX
                // SV_POSITION is screen space position
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            // the sampler is how you define textures in the subshader
            // it needs to have the same name to the texture in Properties
            sampler2D _MainTex;
            // _ST is used to do stuff with the texture in UnityCG.cginc
            float4 _MainTex_ST;
            float4 _Tint;

            // vertex shader function
            v2f vert (appdata v)
            {
                // creates v2f with name o
                // this is what we return from the vertex function
                v2f o;
                // converts the vertex position in local space (vertex of appdata v)
                // to Clip position, assigning it to vertex from v2f o
                // clip position is the position relative to the camera
                // UnityObjectToClipPos(float4/float3) is a helper function from UnityCG.cginc
                o.vertex = UnityObjectToClipPos(v.vertex);
                // TRANSFORM_TEX() makes sure the offset and tiling works in the inspector
                o.uv = TRANSFORM_TEX(v.uv0, _MainTex);
                // you can also assign uvs by doing (o.uv = v.uv0), but then offset and tiling wouldn't work
                // returns the v2f o
                return o;
            }

            // return type fixed4
            // takes a v2f, outputs to SV_Target
            float4 frag(v2f i) : SV_Target
            {
                // tex2D takes a texture and maps it to the uv
                // multiplies the texture by the tint
                float4 col = tex2D(_MainTex, i.uv) * _Tint;
                return col;
            }

            // ends CG code
            ENDCG
        }
    }
}

Simple Lambert shading

Properties
    {
        // base color for the object
        _BaseColor("Base Color", Color) = (1,1,1,1)
    }

    SubShader
    {
        Pass
        {
            // used to get data from scene lights
            #include "Lighting.cginc"

            struct appdata
            {
                // the normal is needed for the dot product
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float3 normal : NORMAL;
            };

            float4 _BaseColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.normal = v.normal;
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                // we normalize the light position of the scene
                // _WorldSpaceLightPos0 (Lighting.cginc) gets the orientation of a directional light
                float3 normalizedLight = normalize(_WorldSpaceLightPos0.xyz);
                // we get the intensity of the light in the object
                // by doing the dot product of the normal with the light direction
                // saturate clamps the value to 0-1, so the light does not subtract from the
                // base color when it's facing opposite to the light
                float3 light = saturate(dot(i.normal, normalizedLight));

                // we add the light intensity multiplied by 
                // the color of the directional light (_LightColor0)
                // to the base color
                return float4(_BaseColor + (light * _LightColor0.rgb), 0);
            }

            ENDCG
        }

Specular Lighting

    Properties
    {
        _BaseColor("Base Color", Color) = (1,1,1,1)
        // controls how fine is the glossiness
        _Gloss("Gloss", Range(1, 200)) = 1
        // controls the intensity of the glossiness
        _GlossIntensity("Gloss Intensity", Range(0, 10)) = 1
    }

    SubShader
    {
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                // interpolator
                float3 worldPos : TEXCOORD0;
            };

            float4 _BaseColor;
            float _Gloss;
            float _GlossIntensity;

            v2f vert (appdata v)
            {
                v2f o;

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.normal = v.normal;
                // gets the world pos of the vertex
                // so the specular changes depending
                // on the world pos of the object
                // multiplies the local space vertex
                // by the ObjectToWorld matrix
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            float3 frag(v2f i) : SV_Target
            {
                // the normal needs to be normalized
                // to avoid facets
                float3 normalizedNormal = normalize(i.normal);
                float3 normalizedLight = normalize(_WorldSpaceLightPos0.xyz);
                float3 light = saturate(dot(normalizedNormal, normalizedLight));

                // Specular
                // gets the world position of the camera
                float3 camPos = _WorldSpaceCameraPos;
                // vector from camera to world pos of vertex
                float3 viewDir = normalize(i.worldPos - camPos);
                // reflects the viewDir vector based on the normal
                // (same angle that viewDir has with normal, reflected)
                // normal needs to be normalized
                float3 reflectedDir = reflect(viewDir, normalizedNormal);
                // gets the specular amount of specular
                // doing the dot product between the reflected vector with the light pos
                // using saturate to clamp to 0-1 to later control intensity
                float3 specularNormalizedIntensity = saturate(dot(reflectedDir, normalizedLight));
                // we composite the final specular color
                // multiplying the normalized intensity with the gloss, and then by the color 
                float3 specularComposite = pow(specularNormalizedIntensity, _Gloss) * 
                    (_LightColor0.rgb * _GlossIntensity);

                // we add the specular to the diffuse lighting
                float3 lightComposite = (light * _LightColor0.rgb) + specularComposite;

                return float3(_BaseColor.rgb + lightComposite);
            }

Easy conversion to Cel shader

                // _ColorStep and _GlossStep are range properties from .1f to 1f
                // step(x, y)
                // step returns 0 or 1 if x >= y, making a hard transition 
                float3 lightComposite = (step(_ColorStep, light) * _LightColor0.rgb)
                    + step(_GlossStep ,specularComposite);
Share this

Leave a Reply