Introduction to Shaders

Afterverse
Beyond
Published in
7 min readJan 18, 2022

What is a shader

Shaders are programs that run in the GPU and define how each pixel should be rendered into the screen. They can be used to manipulate and create vertices, render images, render solid colors and create more complex visual output, such as lighting, vfx, etc

There are many kinds of shaders: surface(Unity only), vertex, fragment, tessellation, compute and geometry.

In this post we will be focusing on using Vertex and Fragment shader.

Vertex shader:

This type of shader runs once per vertex of a model. It’s responsible for manipulating a model’s vertex information to display it on the screen. In this shader it is possible to manipulate some information like vertex position, normals, color, etc.

Pixel/Fragment shader:

This shader calculates the color of each pixel in the screen. It allows us to render objects with a solid color, map textures and create more advanced results, such as dissolve effects, lighting, shadows, etc.

In Unity, shaders are written using two languages:

Shader Lab: This language is used to declare some shader configurations and properties.

CG/HLSL: It’s used inside the CGPROGRAM scope. The vertex and fragment shaders are written inside it.

Structure of a shader

-Name

The name of the shader is defined in the first line using the following syntax:

Shader “Category/ShaderName”.

In this line is defined the shader name and its category. The category helps to keep the project organized. For example, separate Character shaders from Particle shaders.

Code:

Shader “ShaderIntroduction/Example”

-Properties

In the Properties scope, it’s defined the properties that will be available in the material inspector to be modified.

The variables created here has the following format:

_VariableName(“VariableInspectorName”, Type) = “DefaultValue”

There are many types of data that can be used, such:

  • Color (RGBA)
  • Float
  • 2D (Texture)
  • Vector

etc.

Code:

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

-Subshader

In the subshader scope, we can define some shader characteristics, as Tags, LOD, culling mode, etc.

A shader can have multiple subshaders to support different types of hardware.

Pass

The pass scope can define shader characteristics too, but they will only affect the current pass.

A shader can have multiple passes, each one costing one draw call to execute.

CGPROGRAM

This scope contains the HLSL code scope. Here you can import code libraries (.cginc), create functions and define variables.
The vertex and fragment shader must be first declared using the directive #pragma.

#pragma vertex vert — Defines a vertex function called vert
#pragma fragment frag — Defines a fragment function called frag.

Struct data

The vertex and fragment shader use structs to store and transfer data. The default structs that come with the default Unlit Shader code are the Appdata and the v2f structs.

The variables declared in the structs follows this syntax:

Type VariableName : Semantics

Here you can read more about semantics: https://docs.unity3d.com/Manual/SL-ShaderSemantics.html

Appdata

This struct is passed to the vert function and contains information from the model. Its data is processed in the vertex shader and could be passed forward to the fragment shader. Here you can check the variables that this struct accepts: https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html

v2f

This struct contains processed data from the vertex shader that will be used to render the final result later on in the fragment shader. Generally, in the vertex shader a instance of v2f is created, populated and returned to be used in the fragment shader. It’s commun to use TEXCOORD0…3 semantics here to store data.

For example:

struct v2f{float2 worldPosition : TEXCOORD0;float2 viewDirection : TEXCOORD1;float2 worldNormals : TEXCOORD2;};

Variables

To use the variables created in the ShaderLab Properties scope, you must redefine it here in the CGPROGRAM scope to make them visible to the HLSL code.

Code:

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include “UnityCG.cginc”//Struct used in the vertex shader
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
//Struct used in the fragment shader
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
//Variables
sampler2D _MainTex;
float4 _MainTex_ST;
.
.
.
ENDCG

vertexProgram

In this function, we define the position of the object on the screen. This step allows the manipulation of vertex positions, normal conversions, and passes information to the next step, the fragment shader.

Code:

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

fragmentProgram

This function receives data from the vertex shader through the v2f struct. The fragment program defines the color of the object in the screen. It can be used to simply render a solid color or for more complex visuals, such VFX and Post processing.

Code:

fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}

Fallback

This step defines which shader should be used if the GPU doesn’t support the shader you’ve written. It’s useful when we deal with older GPUs.

Example 01 — Solid Color

In this example we will create a simple shader that will render an object with a solid color.

First, create a Unlit shader and rename it to SolidColor.

Open the shader and change its first line to Shader “ShaderIntroduction/SolidColor”. It will put our SolidColor shader in the ShaderIntroduction category in the material inspector.

Create a material and rename it to Mat_SolidColor.

Select the material and set it to use the shader we’ve just created.

Create a cube in your scene and change its material by drag and drop the Mat_SolidColor over it.

Notice that now the cube is rendered with a solid white color, without any shadow or lights.

That’s because the shader we’ve created is a Unlit shader. It means that our shader renders the object without using light information.

In the code, add the color parameter to the Properties and CGPROGRAM scope.

Properties
{
_Color (“Main Color”, Color) = (1, 1, 1, 1)
}
CGPROGRAM
.
.
float4 _Color;
.
.
ENDCG

In the frag function return the _Color variable. It will make the object be rendered in this color.

fixed4 frag(v2f i) : SV_Target
{
return _Color;
}

Back to Unity, select the Mat_SolidColor and notice that the Color parameter appears in the inspector. Change it and see the result.

Example 02 — Texture Scroll

In this example we will create a shader to scroll a texture over an image component in the Canvas.

To do it, first we need to change the type of the texture we will use.

Select the texture you will use and change the texture type from Default to Sprite (2D and UI)

Then change the Wrap Mode to Repeat, it will allow us to manipulate beyond its limits.

Now, create a Unlit shader and rename it to TextureScroll.

Open the shader and change its first line to Shader “ShaderIntroduction/TextureScroll”. It will put our TextureScroll shader in the ShaderIntroduction category in the material inspector.

Create a material and rename it to Mat_TextureScroll.

Create a UI image in the scene, add a texture and put the new material on it.

To implement this effect, we need to understand a bit of UV Mapping.

UV is a bidimensional vector that contains values from 0 to 1 in each axis and tells how texture pixels should be mapped to the object.

To scroll the texture mapped in our object, it’s necessary to increment the UV. As we changed the type of texture to Repeat, we can use values outside the range [0,1] to map a texture pixel to our object.

Back to the code, create the speed parameters in the Properties and CGPROGRAM scopes.

Properties
{
_MainTex (“Texture”, 2D) = “white” {}
_XSpeed (“X Speed”, Range(0, 1)) = 0
_YSpeed (“Y Speed”, Range(0, 1)) = 0
}
CGPROGRAM
.
.
float _XSpeed, _YSpeed;
.
.
ENDCG

In the frag function, we need to increment the UV every time. To do it, create a variable called speedVector using the speed parameters created above and multiply it by the variable _Time.y, that is a built-in variable that Unity gives us that can be used to animate values.

Here you can check more build-in variables: https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

Map the texture into the object by using the tex2D function. It receives the texture and the uv that will be used to map the texture.

Pass the _MainTex in the first parameter and the sum of the object’s uv with the animated speedVector.

fixed4 frag(v2f i) : SV_Target
{
float2 speedVector = float2(_XSpeed, _YSpeed) * _Time.y;
fixed4 color = tex2D(_MainTex, i.uv + speedVector);
return color;
}

Back to Unity, select the Mat_TextureScroll and change the speed parameters to see the result.

Felipe Carmo — Game Developer

--

--

Afterverse
Beyond
Editor for

Criamos universos inesquecíveis que derrubam barreiras e conectam bilhões de pessoas onde quer que estejam!