Your First HLSL Shader

Write a small shader that puts a texture on a shape and tints it, and learn what each part does.

Imagine painting a paper model. First you decide where each corner goes (the vertex function). Then you decide what colour each spot on the surface is (the fragment function). The shader is your painting instructions; the material is the actual paint and pictures you picked.

Before you start

This page is for URP (a "Universal 3D" project). Old tutorials using CGPROGRAM or surface shaders are for the Built-in pipeline and won't work here.

Words to know

WordMeaning
ShaderA tiny program that runs on the graphics card (GPU) to draw things.
HLSLThe language shaders are written in. Looks a lot like C#.
VertexA corner of a mesh.
FragmentRoughly, one pixel of the shape on screen.
UVAn address on a texture: (0,0) is bottom-left, (1,1) is top-right.
Mesh cornersVertex functionGPU fills trianglesFragment functionPixels
The vertex function runs once per corner, the fragment function once per pixel, all at the same time on the GPU.

1. Create it

  1. In the Project window: Create > Shader > Unlit Shader, name it SimpleTexture, and replace everything inside with the code below.
  2. Create > Material, then pick Simple/Texture from its Shader dropdown.
  3. Give the material a texture and a tint, and drag it onto a Cube.

2. The shader

Shader "Simple/Texture"
{
    Properties   // what shows up in the material Inspector
    {
        _BaseMap ("Texture", 2D) = "white" {}
        _BaseColor ("Tint", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            // Data coming IN from the mesh
            struct Attributes
            {
                float4 positionOS : POSITION;   // corner position
                float2 uv         : TEXCOORD0;  // texture address
            };

            // Data passed from Vert to Frag
            struct Varyings
            {
                float4 positionCS : SV_POSITION; // where on screen
                float2 uv         : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)   // the material's numbers
                float4 _BaseMap_ST;           // tiling and offset
                half4  _BaseColor;
            CBUFFER_END

            // Runs for every corner: work out where it goes on screen
            Varyings Vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                return output;
            }

            // Runs for every pixel: work out its colour
            half4 Frag(Varyings input) : SV_Target
            {
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
                return texColor * _BaseColor;
            }
            ENDHLSL
        }
    }
}

3. What each part does

PartIn plain words
PropertiesThe sliders and slots in the material Inspector. Names must match the variables further down.
TagsTell Unity "this is a solid object for URP".
AttributesWhat each corner of the mesh gives you.
VaryingsWhat Vert hands to Frag. The GPU blends these smoothly between corners.
CBUFFER blockGroups the material's values so URP can draw faster (SRP Batcher). Put every non-texture property here.
TransformObjectToHClipConverts a corner from "on the model" to "on the screen".
SAMPLE_TEXTURE2DReads the texture's colour at a UV address.

HLSL in two minutes

4. Experiments

Change the last line of Frag and save; the Scene view updates instantly.

Replace withYou'll see
return half4(input.uv, 0, 1);The UVs as colours: red across, green up.
return texColor * _BaseColor * (0.75 + 0.25 * sin(_Time.y * 3));A gentle pulse.
return half4(1 - texColor.rgb, 1);Inverted colours.

What happens at runtime

  1. Unity sends the mesh and the material's values to the GPU.
  2. Vert runs for every corner and places it on screen.
  3. The GPU works out which pixels each triangle covers, blending the UVs between the corners.
  4. Frag runs for every covered pixel and returns its colour.

Common Mistakes

Prefer nodes?

Shader Graph (Create > Shader Graph) builds the same thing by connecting boxes, with lighting included. Many games never write HLSL by hand.

See also