Daily free asset available! Did you claim yours today?
The cover for Creating Shaders in Unity: A Step-by-Step Tutorial

Creating Shaders in Unity: A Step-by-Step Tutorial

February 25, 2025

Ever wondered how to make water shimmer realistically or create a fiery explosion in Unity? The answer lies in shaders. They dictate how objects look in Unity. They unlock stunning effects and optimized performance. This tutorial will walk you through crafting your own shaders. No prior experience is needed. If you are new to game development, consider that Wayline, a comprehensive game development platform, can provide tools and resources for every stage of the development process.

Introduction to Shaders and Shader Languages

Shaders are programs that run on the GPU, defining how every object is rendered on screen. Think of them as the artist behind the visual scene.

Photograph of a tranquil lake reflecting a clear blue sky, with gentle ripples disturbing the surface

They’re integral to the rendering pipeline, handling color, lighting, and textures.

Shader languages like HLSL/Cg are used to write shaders, following a basic structure: input, calculations, and output color.

There are different types of shaders, such as surface shaders, which simplify the process, and vertex/fragment shaders, which offer the deepest control.

Setting Up Your Unity Project for Shader Development

Start with a new or existing Unity project. This is your canvas. To create a shader, right-click in the Project window and select Create > Shader. Let’s pick “Unlit Shader” for now. Name it "MyShader.shader". Double-click to open it in your code editor.

Next, create a new material (Create > Material). Select the newly created material. In the Inspector panel for the material, click the “Shader” dropdown menu (usually at the top). Select your custom shader (it will likely be under the “Custom” or “Unlit” category, depending on the type you chose, e.g. Custom/Unlit/MyShader). Finally, drag and drop the material onto a GameObject in your scene. This applies your shader. Learn How To Import Assets Into Unreal Engine

Writing Your First Simple Shader: A Basic Color Shader

Let’s get started. Here’s the code for a basic color shader. Copy and paste this into your shader file, replacing the default content:

Shader "Unlit/ColorShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1) // Defines a color property named "_Color"
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" } // Tags for rendering type
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert // Declares the vertex shader function
            #pragma fragment frag // Declares the fragment shader function

            #include "UnityCG.cginc" // Includes common Unity shader functions

            struct appdata
            {
                float4 vertex : POSITION; // Vertex position
                float2 uv : TEXCOORD0; // UV coordinates
            };

            struct v2f
            {
                float2 uv : TEXCOORD0; // UV coordinates
                float4 vertex : SV_POSITION; // Clip space position
            };

            fixed4 _Color; // Color property

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex); // Transforms object space to clip space
                o.uv = v.uv; // Passes UV coordinates to fragment shader
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _Color; // Returns the defined color
            }
            ENDCG
        }
    }
}

The Properties block defines adjustable variables in the Inspector. _Color is a color property, defaulting to white. Without it, the color would be fixed.

The #pragma vertex vert and #pragma fragment frag lines tell Unity which functions are the vertex and fragment shaders. These are the entry points for each rendering stage.

The appdata struct defines the input data passed to the vertex shader, including vertex position and UV coordinates. The v2f struct defines the data passed from the vertex shader to the fragment shader, including UV coordinates and clip space position.

The UnityObjectToClipPos function transforms the vertex position from object space to clip space, which is necessary for rendering.

The frag function determines the final color for each pixel. It simply returns the color from the _Color property. This results in a solid color output.

Save the shader, and Unity compiles. Now, in the material’s Inspector panel, adjust the color. Watch your object change.

Adding Textures to Your Shader

Solid colors are a starting point. Textures add richness. Think of textures as wrapping paper for your objects. To add one, first declare a texture property. Do this in the Properties block of your shader:

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }

Place this inside the Properties block, before the _Color property. This creates a texture slot in the material’s Inspector.

Next, we need to declare a variable to hold the texture. Add this line outside the frag function, but inside the Pass block:

sampler2D _MainTex;

Then, replace return _Color; inside the frag function to read the texture color:

fixed4 col = tex2D(_MainTex, i.uv);
return col;

This reads the color from the texture at each pixel’s UV coordinate. Save the shader. Assign a texture to your material. See the magic.

You can also control the tiling and offset of the texture. Do this directly in the material’s Inspector. Experiment to see how these parameters affect the appearance.

Implementing Simple Lighting Effects

Basic lighting models include Lambert (diffuse) and Blinn-Phong (diffuse + specular).

Lambert lighting simulates light scattering. Surfaces facing a light source appear brighter. This is because of the way light interacts with the surface.

Photograph of a dense forest with dappled sunlight filtering through the canopy

This is calculated using the dot product.

This code calculates the diffuse lighting component, simulating how light scatters across a surface. Insert it inside the frag function. Place it before the return statement. Put it after declaring the col variable:

float3 normal = normalize(IN.Normal);
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float diffuse = max(0, dot(normal, lightDir));
fixed4 color = _LightColor0 * diffuse;

The code calculates the diffuse lighting component. It uses the surface normal (normal), which is the direction the surface is facing. The light direction (lightDir) is the direction from the surface to the light source. The light color (_LightColor0) is the color of the light. The dot product calculates the cosine of the angle between the normal and light direction, determining how much light falls on the surface.

Add specular highlights by calculating the reflection vector. Normal maps “fake” surface detail. They do this without adding polygons. Unity supports directional, point, and spot lights. Each requires different calculations.

Creating a More Advanced Shader: A Simple Water Shader

Simulating water often involves displacing vertices. This is based on a sine wave. It creates the illusion of movement. Here’s how you might implement this (simplified example that would typically go in the vertex shader):

float timeScale = _Time.x * 0.1;
o.vertex.y += sin(o.vertex.x * _WaveScale + timeScale) * _WaveHeight;

_Time.x is the time since the game started. Multiplying it by 0.1 slows down the wave animation, making it more visually appealing. _WaveScale adjusts the wave frequency: lower values for gentle waves, higher for choppy ones. _WaveHeight controls the wave amplitude. It goes from ripples to swells.

Implement normal map distortion for realistic waves. Reflection probes add reflections. They mirror the surrounding environment. Control water color and transparency. Do this through shader properties.

Photograph of a rocky coastline with waves crashing against the shore

Optimization Techniques for Shaders

Shader performance is critical. Simpler calculations are always better.

For instance, pow(x,2) is slower than x*x.

Using the fastmath pragma can enable faster math. This potentially increases shader performance.

Avoid unnecessary texture lookups. These operations are resource-intensive.

Leverage shader LOD (Level of Detail). Scale performance based on distance.

Debugging and Troubleshooting Shaders

Use the Unity Frame Debugger to inspect shader execution. Step through each draw call.

Common errors include syntax mistakes and incorrect variable types.

Identify performance bottlenecks using profiling tools. Pinpoint slow areas of code.

Photograph of a fiery sunset over a mountain range, with vibrant oranges and reds painting the sky

Next Steps: Exploring More Advanced Shader Techniques

Enhance your game’s visuals. Use post-processing effects like bloom and color grading.

Shader Graph offers a visual workflow. It is node-based. It allows artists to create complex shaders without coding.

Custom render textures enable advanced effects. Dynamic reflections and feedback loops are possible.

Explore online resources. The Unity documentation and shader tutorials will deepen your knowledge.

Conclusion

You’ve now taken your first steps into the world of shaders. Remember the key takeaways: shaders control visual appearance, performance matters, and experimentation is key. Next, try modifying existing shaders. Explore Shader Graph. Dive deeper into lighting models. If you are feeling stuck and in Tutorial Hell the possibilities are endless.