Get an 20% OFF using the RELEASE code on your book purchase. For a limited time.

Introduction to the shader programming language

3.3.2. Cg / HLSL vertex shader stage

This post is also available in…

The vertex shader corresponds to a rendering pipeline’s programmable stage, where the vertices are transformed from a 3D space to a two-dimensional projection on the screen. Its smallest unit of calculation corresponds to an independent vertex.

Inside the USB_simple_color shader, there is a function called “vert” which corresponds to our vertex shader stage. The reason why we know that it is our vertex shader is that it was declared as such in the #pragma vertex.

#pragma vertex vert
...
v2f vert (appdata v)
{
    ...
}

Before continuing with the explanation of this stage, we must remember that our shader USB_simple_color is an Unlit type (it does not have light) that’s why it includes a function for the vertex shader and another for the fragment shader (#pragma fragment frag). It is essential to mention this since Unity provides a quick way to write shaders in the form of “Surface Shader” (surf) that generates Cg code automatically, exclusively for materials that are affected by lighting. This allows optimization of development time but does not help us understand it because many functions and calculations occur internally in the program. This is why we created an Unlit shader at the beginning of this book; to understand its operation in detail.

We are going to analyze the structure of the vertex shader stage. Our function begins with the word “v2f” which means “vertex to fragment”. This name makes a lot of sense when we understand the internal process that is happening inside the program. V2f will be used later as an argument in the fragment shader stage, hence its name. So, as our function starts with v2f that means it is a vertex output type, therefore, we will have to return a value associated with this data type.

Continue with “vert” which is the name of our vertex shader stage and then the arguments in parentheses where appdata fulfils the function of vertex input.

v2f vert (appdata v) {} ...

This function can transform and connect appdata properties with v2f properties. If we continue our analysis, we will notice that inside the function, the struct v2f has been initialized with the letter “o”, so “o” would be our pointer, therefore, all the properties that were declared in struct v2f will be found inside this variable.

v2f o;
o.vertex …
o.uv …
return o;

SSo the first operation that occurs within the vertex shader stage is the transformation of the object vertices from object-space to clip-space through the “UnityObjectToClipPos” method. Let’s remember that our objects are within a three-dimensional space in the scene, and we must transform those coordinates into a two-dimensional projection of pixels on the screen. That transformation occurs precisely within the “UnityObjectToClipPos” function. What this function does is multiply the matrix of the current model (unity_ObjectToWorld) by the factor of the multiplication between the current view and the projection matrix (UNITY_MATRIX_VP).

UnityObjectToClipPos( float3 pos).
{
    return mul(
        UNITY_MATRIX_VP, 
        mul(unity_ObjectToWorld, float4(pos , 1.0)));
}

This operation is declared in the “UnityShaderUtilities.cginc” file which has been included as a dependency in UnityCG.cginc and that is why we can use it inside our shader. So, we take the vertex input from our object (v.vertex), transform the matrix from object-space to clip-space (UnityObjectToClipPos) and save the result in the vertices output (o.vertex).

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
}

A factor that we must consider when working with inputs or outputs is that both properties must have the same number of dimensions, e.g., if we go to the vertex input appdata we will see that the vector “float4 vertex” has the same number of dimensions as “float4 vertex” in the vertex output (v2f). 

If one property is of type float4 and the other float3 then Unity may return an error because in most cases you cannot transform from a four-dimensional vector to a three-dimensional vector or less. 

Then we can find the TRANSFORM_TEX function. This function asks for two arguments which are: 1. the input UV coordinates of the object (v.uv) and 2. the texture that we are going to position over those coordinates (_MainTex). It fulfils the function of controlling the “tiling and offset” in the UV coordinates of the texture.

Finally, we pass these values ​​to the UV output (o.uv) since later they will be used in the fragment shader stage.

Follow us to stay informed about all the latest news, updates, and more.

Join the group to share your experiences with other developers.

Subscribe to our channel and keep learning game dev!

jettelly-logo

Jettelly Team

We are a team of indie developers with more than 9 years of experience in video games. As an independent studio, we have developed Nom Noms in which we published with Hyperbeard in 2019. We are currently developing The Unity Shader Bible.

Follow us on our social networks.