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

Introduction to the shader programming language

3.2.7. Data Types

This post is also available in…

Before continuing to define the properties and functions of our shader, we must look at data types because there is a small difference between Cg and HLSL. 

When we create a default shader in current versions of Unity, we can find floating-point numbers that differ in precision, among them:

  • float,
  • half
  • and fixed

A shader written in Cg can compile perfectly in these three types of precision, however, HLSL is not capable of compiling the “fixed” data type, so, if we work with this language, we will have to replace all the variables and vectors of this type either with half or float. 

“Float” (Cg and HLSL) is a high precision data type, it is 32 bit and generally used in calculating positions in world-space, texture coordinates (UV), or scalar calculations involving complex functions such as trigonometry or exponentiation.

“Half” (Cg and HLSL) is half-precision, is 16 bit and is generally used in the calculation of low magnitude vectors, directions, object-space positions, and high dynamic range colors. 

“Fixed” (Cg) is low precision, it is only 11 bit and is generally used in the calculation of simple operations (e.g. basic color storage). 

A question that commonly arises in the vector use is, what would happen if we only use a floating data type (Float) for all our variables? In practice this is possible, however, we must consider that float is a high precision data type, which means that it has more decimals, therefore, the GPU will take longer to calculate it, increasing times and generating heat. 

It is essential to use vectors and/or variables in their required data type, thus we can optimize our program, reducing the graphic load on the GPU. 

Other widely used data types that we can find in both languages ​​are “Int, sampler2D and samplerCube”. 

“Sampler” refers to the sampling state of a texture. Within this type of data, we can store a texture and its UV coordinates. 

Generally, when we want to work with textures in our shader we must use the “Texture2D” type to store the texture and create a “SamplerState” to sample. The data type “sampler” allows us to store both the texture and the sampling status in a single variable. To understand in detail the function of a sampler we will do the following:

// declare our _MainTex texture as a global variable
Texture2D _MainTex;

// declare the _MainTex sample as a global variable 
SamplerState sampler_MainTex;

// go to the fragment shader stage 
half4 frag(v2f i) : SV_Target
{
    // inside the col vector sample the texture in UV coordinates. 
    half4 col = _MainTex.Sample(sampler_MainTex, i.uv);

    // return the colour of the texture. 
    return col;
}

The above process can be optimized by simply using a “sampler”. 

// declare our sampler for _MainTex 
sampler2D _MainTex;

// go to the fragment shader stage 
half4 frag(v2f i) : SV_Target
{
    // sample the texture in UV coordinates using the function 
    // tex2D().
    half4 col = tex2D(_MainTex, i.uv)

    // return the colour of the texture. 
    return col;
}

Both examples return the same value that corresponds to the texture that we generate in our properties and will be assigned from the Unity Inspector. 

We can use scalar values, vectors and matrices in our program. 

Scalar values ​​are those that return a real number, either an integer or decimals (e.g. 1, 0.4, 4, etc) and to declare them in our shader we must first add the “data type”, then the “scalar value name” and finally initialize its default value.

Its syntax is as follows:

float nombre = n;   // e.g. float a = 0;
half nombre = n;    // e.g. float b = 1.456;
fixed nombre = n;   // e.g. float c = 2.5;

Vectors are those that return a value with more than one dimension (e.g. XYZW) and to declare them in our shader we must first add the “data type”, then “dimension number”, then “vector name” and finally initialize its default value. 

Its syntax is as follows:

float2 nombre = n;   // e.g. float2 uv = float2(0.5, 0.5);
half3 nombre = n;    // e.g. half3 position = float3(0, 0, 0);
fixed4 nombre = n;   // e.g. fixed4 color = float4(1, 1, 1, 1);

Matrices are those that have values ​​stored in rows and columns, they have more than one dimension and are used mainly for shearing, rotation and change of vertex position.

To declare a matrix in our shader we must first add the “data type”, then the “dimension quantity multiplied by itself”, then the “matrix name” and finally “initialize its default values” taking into consideration that the coordinates Xx, Yy & Zz will be equal to 1.

Its syntax is the following:

float3x3 nombre = float3x3    // three rows and three columns 
(
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
);

half2x2 nombre = half2x2        // two rows and two columns
(
    1, 0,
    0, 1
);

fixed4x4 nombre = fixed4x4    // four rows and four columns 
(
    1, 0, 0, 0,
    0, 1, 0, 0, 
    0, 0, 1, 0,
    0, 0, 0, 0
);

If you are starting in the world of Unity shaders, you may well not fully understand what has been explained. Don’t worry, later in this chapter we will review all these parameters in detail and see how the fragment shader stage works.

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.