This tutorial covers the data types in GLSL ES and how to use them.
In GML, you can set variables to any value from tiny fractions to large integers to booleans without worrying about data types. However, in shaders you need to initialize what data type you are using for each variable, allowing shaders to be faster and more precise.
The first thing to learn about shader variables is the data types. You can use any of the following types:
To declare your variables in code you must specify the type before the variable name and shader syntax requires a semi-colon at the end of the declaration:
//Floats must include a decimal point.
float A = 5.0;
//You can declare the variable to set later:
//Integers must exclude a decimal point.
int B;
//Bools are either true or false.
bool C = false,
//"D" is also a bool because of the comma after the "C" definition.
D = true;
As you can see with the integer case, you can initialize a variable's type without setting a value. This is like local variables in GM (e.g. var i = 0). Also, you can use commas instead of semicolons when defining multiple variables of the same data type. This is shown with bool "D".
Sometimes you need to convert from one data type to another and this is called "casting". To cast a value you use the replacement data type as function with the value you want to convert as the argument. Here's a quick example:
//Cast an int to a float.
float A = float(5);
//Cast a bool to an int (false = 0, true = 1).
int B = int(true);
Unlike in GML, some shader variables can contain up to 4 values at once and these values are called "components" of the variable. Vectors make it much easier to do math with 2,3 or 4 dimensions. If you want to add two vectors together you can do so with a single operation, like you would with any other variable (which will be very useful later).
So floats, vec2s, vec3s and vec4s are all floating-point numbers, so a float has 1 component while vec4 has 4. To set a vector with you use a function called a constructor which is just like a caster but can have multiple components separated by commas. Here's how it looks to initialize different variable types:
//1D scalar with a value of 1.0. Scalars don't need constructors.
float line = 1.0;
//Construct a 2D integer vector containing 1 and 2 respectively.
ivec2 square = ivec2(1, 2);
//You can include floats as components:
//Construct a 3D vector containing 1.0, 2.0 and 3.0 respectively.
vec3 cube = vec3(line, 2.0, 3.0);
//You can also include vectors:
//Construct a 4D vector containing 1.0, 2.0, 3.0 and 4.0 respectively.
vec4 tess = vec4(cube, 4.0);
You can also do the same with integers by using int, ivec2, ivec3, and ivec4. As you may have guessed, you can do the same with Booleans (bool, bvec2, bvec3, and bvec4), but they aren't used as often.
So you now know how to cast data types and construct vectors, but to extract components from vectors you need to know about "swizzle notation". There are two ways to access components.
Dotted Notation is more widely used because it can be used to access floats or vectors whereas array notation only accesses floats. Here's an example to show both methods:
//Construct a vec4 from ints for example.
vec4 A = vec4(0, 1, 2, 3);
//Get the first component of "A", 0.0.
float B = A.x;
//Get the second component of "A", 1.0.
float C = A[1];
//Reverse the component order of "A", vec4(3, 2, 1, 0).
vec4 D = A.abgr;
//This also works for setting vectors:
//Set the first two component to the last two components, vec2(2, 3).
A.st = A.pq;
It's important to tell you, you CANNOT mix components from one set with another. So you can pick between xyzw, rgba or stpq, but you can't do something like xyrg because this will cause an error.
Matrices are a special data type that contain more components than their vector equivalents. It helps to think of matrices as tables with either 2, 3 or 4 rows and columns, so a mat2 contains 2 vec2s (2x2 or 4 components), a mat3 contains 3 vec3s (3x3 or 9 components) and a mat4 contains 4 vec4s (4x4 or 16 components).
They can be initialized like so:
//Set the first and second columns with vec2s.
mat2 rotate = mat2(vec2(0.6,0.8), vec2(-0.8,0.6));
//Get the vec2 from the second column which is vec2(-0.8,0.6).
vec2 y_dot = rotate[1];
//And here are some mat3 examples:
//Alternatively, you can set all components individually.
mat3 rotate2 = mat3(0.6, 0.8, 0.0, -0.8, 0.6, 0.0, 0.0, 0.0, 1.0);
//Get the first column, second row component which is 0.8
float y_z = rotate2[0][1];
Matrices can be used to scale, rotate or skew vectors simple by multiplying a vector by a matrix. Matrix multiplication is non-commutative, meaning that the result can be different depending on the order of multiplication. You can learn more on this in the next tutorial, but for now you just need to know it's used to "transform" vectors.
"Storage qualifiers" control how you want your variable is handled. This can be used to receive vertex information, receive values from GML and more! These are the storage qualifiers and how they are used:
Variables can be grouped:
//Initialize a integer array with 5 slots.
ivec2 array[4];
//Set the 2 index value as a ivec2.
array[2] = ivec2(2,4);
//Initialize structure format.
struct sample
{
vec2 uv;
sampler2D tex;
};
//Set create a "sample" structure.
sample pixel = sample(v_vTexcoord,gm_BaseTexture);
//Get pixel's uvs.
vec2 pix_uv = pixel.uv;
//Set pixel's texture to "uni_diffuse".
pixel.tex = uni_diffuse;
Lastly, there are precision qualifiers, which can control the range and quality of variables. Modern hardware may use a higher precision than you specify, but precision qualifiers set the minimum acceptable precision (if the hardware supports it). This isn't used often, but it can be useful with cross-platform shaders. These are the three precision types:
//Set the default float precision to highp.
precision highp float;
//Set the default integer precision to lowp.
precision lowp int;
//Set the color at low precision.
lowp vec3 color = vec3(0.2, 0.4, 0.8);
Great job! You've learned about all the different data types and what they store. You've learned how to handle multi-dimensional variables (vectors) and their components. You now know how to access shader inputs (attributes, varyings, uniforms) and how to use constants, data groups (arrays and structs) and precision. This lays the framework necessary to understand functions and logic which is covered in the next tutorial. For now, pat yourself on the back and take a break if you need it!