Welcome back, in the last article, we saw both how to create a very simple shader and get some fundamental concepts on shader functioning.
In this article, we will delve into HLSL and we will understand the basics of language. Then in the practical part, we will examine in depth the concept of shader properties.
Variables
HLSL is a statically typed language. This means that each variable has its own specific datatype, which is declared at the time of the declaration and cannot change.
Scalar types
The scalar quantities represent the simplest datatype of variables to use and display; they are variables with a single numerical value. There are different types of scalar values in HLS. They differ according to the precision of the value to be represented. Choosing a type with a lower precision value has noticeable positive effects on shader performance even if by now the power of graphics cards is such that the difference is often negligible.
- Integer
Simple numbers without fractional part, not useful for many complex operations but indispensable for the management of arrays and indexes. - Fixed
Numbers between -2 and +2, always within 256 steps between the different values, are usually used optimally for colour information since the colours are saved in 256 colour steps per channel. (RGBA) - Half
Numbers with low precision can contain any numerical value but tend to be inaccurate, the further away from zero. They are commonly used for HDR colours or to handle small vectors. - Float
Numbers with double the precision of half. They are more accurate and usually used to save and manage positions.
int integer = 6; // Integer fixed fixed_point = 0.2; //fixed point number half low_precision = 1.12; //low precision number float high_precision = 155.321; //high precision number
Vector Types
Then there are vector values based on the scalar ones. With each vector, we can represent things like colours, positions and directions. In HLSL, we write the number of dimensions we need at the end of the type to create a vector type. ( Note that the maximum number of dimensions here is 4 if you need more you have to create arrays )
fixed4 color = fixed4(1, 1, 1, 1); float3 position = float3(1, 1, 0); float2 textureCoord = float2(0.2, 0.2);
Packed Arrays
Particularly interesting is the fact that every component of a vector type could be accessed by a standard way know as packed arrays. Typically they are x, y, z and w but the alias used in CG are also r,g,b,a
fixed4 color = fixed4(1,2,3,4); color.x = color.r; color.y = color.g; color.z = color.b; color.w = color.a;
Swizzling
Another thing to remember is the swizzling, the rapid possibility to rearrange components in vectors.
fixed4 color = fixed4(1,2,3,4);<br> color.xyzw = color.wzyx;
Matrix Types
Matrix Types are a vector of vectors. They are used for rotation, move and transformation of vectors. We can create matrices by writing [dimension 1] x [dimension2] behind any scalar type. Remember that in 3D graphics, we need a 3×3 matrix for the rotation or scale and a 4×4 matrix to move objects.
float4x4 transformMatrix3d; //a matrix that can scale rotate and translate a 3d vector //a matrix that can scale and rotate a 2d vector, but not translate it float2x2 rotationMatrix2d = { 0, 1, -1, 0 }; //when doing matrix multiplication we have to use 4d vectors, //the 4th component is just used to make moving the vector via matrix multiplication possible float4 position; //we rotate, scale and translate a position my multiplying a vector with it //(!!! the order of factors is important here !!!) position = transformMatrix3d * position;
Packed Matrices
HLSL allows types such as float4x4, access to a single element of the matrix using the _mRC notation, where R is the row, and C is the column:
float4x4 matrix; // ... float first = matrix._m00; float last = matrix._m33;
The _mRC notation can also be chained:
float4 diagonal = matrix._m00_m11_m22_m33;
An entire row can be selected using squared brackets:
float4 firstRow = matrix[0]; // Equal float4 firstRow = matrix._m00_m01_m02_m03;
Samplers
The Samplers are used to read data from textures. When reading from a sampler, the texture has always fixed coordinates from [0,0] to [1,1]. The UV Space of the textures always starts with the 0,0 as the lower left corner and the 1,1 as the upper right corner.
sampler2d texture; float4 color = tex2D(texture, coordinates);
Structs
The final type is the structs, custom datatypes which can hold several other types. Usually, we use them to represent lights, inputs and other complex data. To use a struct we need to define it and then we can use it somewhere else.
//define the struct struct InputData{ float4 position; fixed4 color; half4 normal; }; //create a instance and use the struct InputData data; data.position = float4(2, 0, 0, 2);
Time to practice
Let’s start with creating a new shader in Unity, let’s call it “Shader_01” and like in the previous article, we use this skeleton to initialize it.
Shader "Shader_01"{ Properties{ } SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry"} Pass{ CGPROGRAM //include useful shader functions #include "UnityCG.cginc" //define vertex and fragment shader #pragma vertex vert #pragma fragment frag ENDCG } } }
So far we’ve always left the property section empty in the shader, now it’s time to start filling it.
The properties of a shader are parameters that we supply to the shader and that we can use in the code to get any effect. All the properties entered must respect a well-defined syntax.
_VariableName ("Inspector GUI Name", Type) = (Default Value)
Each property is then called up in the HLSL code, thus specifying a new variable within CGPROGRAM with the same name as the one in the properties.
type _VariableName;
As, written above, there are different types of properties, each linked to a different type of HLSL variable.
//Variables that appear in the GUI Inspector and are usable by code _MyColor ("Some Color", Color) = (1,1,1,1) _MyVector ("Some Vector", Vector) = (0,0,0,0) _MyFloat ("My float", Float) = 0.5 _MyTexture ("Texture", 2D) = "white" {} _MyCubemap ("Cubemap", CUBE) = "" {} //Same variables called in HLSL in Shaders fixed4 _MyColor; float4 _MyVector; float _MyFloat; sampler2D _MyTexture; samplerCUBE _MyCubemap;
However, for our practice today, we will need to create only two properties.
_Color ("Tint", Color) = (0, 0, 0, 1)<br> _MainTex ("Texture", 2D) = "white" {}
With the first, we define colour, and with the second, we define a texture instead. We will use this as a texture with the MOLO17 logo, but it will be possible to use any image of course.

Now add after the statements #pragma the two HLSL variables and a new float4 variable that derives from the texture sampler.
sampler2D _MainTex; fixed4 _Color; float4 _MainTex_ST;
We define a simple struct that allows us to insert elements into the vertex shader.
struct appdata { float4 vertex: POSITION; float2 uv: TEXCOORD0; };
also, a struct that is returned to us by the vertex shader to pass to the fragment shader.
struct v2f { float4 position: SV_POSITION; float2 uv: TEXCOORD0; };
Once done, we can proceed to write the vertex shader code.
v2f vert (appdata v) { v2f o; // convert position from 3d Space to Normalized Space useful to avoid // problems with multiple devices o.position = UnityObjectToClipPos (v.vertex); // Apply UV position to Vertex Coordinates o.uv = TRANSFORM_TEX (v.uv, _MainTex); return o; }
Then we move on to the fragment shader.
fixed4 frag (v2f i): SV_TARGET { fixed4 col = tex2D (_MainTex, i.uv); col * = _Color; return col; }
Unlike the Vertex Shader, where we are only converting vertex values to UV values, in the fragment shader in addition to applying the texture with tex2D, we also make an additional modification.
After receiving the colour from the texture, multiply the variable col
by our _Color
property. This will allow us to dye the texture.

For this third article, we will stop here, in the next we will talk about the different lighting methods.
//Full Shader Code Shader "Shader_01"{ Properties{ _Color("Tint", Color) = (0, 0, 0, 1) _MainTex("Texture", 2D) = "white" {} } SubShader{ Tags{ "RenderType" = "Opaque" "Queue" = "Geometry"} Pass{ CGPROGRAM //include useful shader functions #include "UnityCG.cginc" //define vertex and fragment shader #pragma vertex vert #pragma fragment frag sampler2D _MainTex; fixed4 _Color; float4 _MainTex_ST; struct appdata { float4 vertex: POSITION; float2 uv: TEXCOORD0; }; struct v2f { float4 position: SV_POSITION; float2 uv: TEXCOORD0; }; v2f vert(appdata v) { v2f o; // convert position from 3d Space to Normalized Space useful to avoid // problems with multiple devices o.position = UnityObjectToClipPos(v.vertex); // Apply UV position to Vertex Coordinates o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag(v2f i) : SV_TARGET{ fixed4 col = tex2D(_MainTex, i.uv); col *= _Color; return col; } ENDCG } } }
Continue following this article series reading the next episode here.