Now that we've covered the data types, it's time to learn how to use them! Let's go over operators and functions.
One of the first things you need to know about is the operators. Thankfully, they are quite similar to GML, so this should quick to cover. All operators must both terms to use the same data type. So for x + y
, x and y should both be floats or both be ints, and not mixed data types.
i++
is equivalent to i = i+1
.
(bool_condition) ? a: b
, if bool condition is true, it will return 'a' and if not, it returns 'b'. Helpful with writing concise code.
Functions are a fundamental part of shaders, a bit like an extension of the operators. The built-in shader functions use inputs, called "parameters" to "return" an output from the function. This can be anything from simple math functions to getting a pixel's color from a texture. Everything beyond operators has to be done with functions.
Here's a quick overview of all supported functions in GLSL ES (if want any more detail about specific functions, you can click them to view their page):
lerp
in GML) work exactly the same as they do in GML!
frac
in GML, and 'mod' both handle negatives differently, 'min' and 'max' are limited to only two arguments each, leaving 'step' and 'smoothstep' as the only unfamiliar functions. I won't summarize them here so I encourage you to check out those pages.
power
, 'inversesqrt' is the reciprocal of the square root, the "exp" functions are exponential functions and the "log" are logarithms with different bases (The Natural Number and 2).
degtorad
in GML) converts angles from degrees to radians and 'degrees' (radtodeg
in GML) converts radians to degrees. 'cos', 'sin', 'tan' function just like in GML, 'acos', 'asin', 'atan' function just like the "arc" equivalents in GML.
Now that you have an overview of the operations and functions, it's much easier to explain matrices. So we know from the last tutorial that mat2 contains 4 (2x2) components and that the order of multiplication matters, but now I can show you why. First, let's consider these two cases:
//Remember, a matrix is like an array of vectors rather than a larger vector.
mat2 matrix = mat2(2, 3, 4, 5); //First row is 2 and 3, second row: 4 and 5.
vec2 A = vec2(0, 1) * matrix; //Computed as vec2(3, 5)
vec2 B = matrix * vec2(0, 1); //Computed as vec2(4, 5)
To perform a vector-matrix multiplication, you can generalize with this forumla for each component 'i': new_vector[i] = vector[0] * matrix[0][i] + ... + vector[n] * matrix[n][i];
So in the example above, we are essentially calculating: A = vec2(0*2 + 1*3, 0*4 + 1*5)
.
If you swap the order putting the matrix first, then you swap the matrix rows for columns and columns for rows and you get this: B = vec2(0*2 + 1*4, 0*3 + 1*5)
.new_vector[i] = dot(vector, matrix[i]);
Logic statements are a crucial part of programming languages and GLSL ES is no exception. You're probably already familiar with these so I'll be brief and just clarify the syntax differences. Here is a list of those statements:
if (bool_condition) //example: a == b
{
do_something();
}
Specifically, your condition must be a bool (unlike in GML) and MUST be placed in parentheses. I recommend using curly brackets at the start and end of the conditional code for ease of reading, but it's not required in one-liners.
for(; bool_condition; ) //example: i > 10
{
do_something();
}
But more commonly you'll see something like this:
for(int i = 0; i<10; i++)
So you don't have to initialize a variable or increment inside the for-loop, but you must have two semicolons to separate the condition.
while (bool_condition)
{
do_something();
}
while
, except the condition is checked after the first iteration. This means it will always do at least one loop. This is the standard format:
do
{
do_something();
}
while(bool_condition);
Custom functions are another useful feature for organizing your code. Here's a simple example function:
vec4 tex(vec2 coordinates)
{
return texture2D(gm_BaseTexture, coordinates);
}
When called, this outputs a (vec4) pixel color from the base-texture at the given texture coordinates. This shows you set the output datatype before the function name and you can add parameters to your functions inside the parentheses.
vec4 vignette_tex(in vec2 coordinates, out float vignette)
{
vec4 tex = texture2D(gm_BaseTexture, coordinates);
vignette = (1.-length(coordinates-.5));
return tex*vignette;
}
Here we have an input (texture coordinates) and an output (vignette brightness) along with a vec4 return. Sometimes you need to output multiple values and out
is a way to do that!
The main function is a bit of unusual because it doesn't have any parameters, doesn't return anything, and is the one custom function required in every single shader! Instead of parameters, you just have to work off of the built-in "gl_" and "gm_" variables, attributes (vertex shader), and varyings (fragment shader).
gl_Position
, which is the vertex position in "projection-space". More on that later, but for now, you just need to know that it's the vertex position, usually transformed by the gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]
. Here you can set the vertex position however like, for example, if you want to flip the y-axis, you can do this:
//Vertex position with an inverted y-axis.
vec4 inverted_pos = vec4(in_Position.x, -in_Position.y, in_Position.z, 1.0);
//Set the vertex position.
gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * inverted_pos;
You may have noticed the "inverted_pos" is a vec4. The W component is used with the transformation matrices and should always be 1.0. This will make sense in the next tutorial so don't worry about it for now.
gl_FragColor
, which determines the output fragment/pixel color. For example, the default shader simply outputs the texture color and alpha.
vec4 tex = texture2D(gm_BaseTexture, v_vTexcoord);
//Discard all fragments that are less than 50% alpha.
if (tex.a<.5) discard;
Note: You should avoid using discard if it's possible to do so because it is very slow on some platforms.
That was a lot to cover so don't worry if you don't understand some parts! The purpose of these tutorials is to help provide a solid foundation that you can refer to when you need to. If you have a decent grasp of operations and functions, you can begin experimenting. As you become more comfortable with shaders, you'll learn how to write code more efficiently and clearly.
At this point you have all you need to begin writing your own shaders, however the next tutorial will explain tips and tricks I've learned to help you launch into it! Congrats, you've almost completed!