Friday, Nov 12, 2021

Some times ago I decided to start again to dive into GLSL after I paused the development of personal computer graphics projects due to lack of time.

This is the first post of a series of tutorials and notes I took while I was studying the language and the shader development.

GLSL development tool

As a developer, I spend most of the time using an IDE to write the code and particularly to debug it, so the right IDE must be chosen carefully to have the most confortable development environment. Among many tools tried, the one that has, in my opinion, the better mix of functionalities and easy of development is SHADERed (in this tutorials I’m using the version 1.5.6 and above).

For example it can automatically infer, from the name of the uniform or varying glsl variables, the proper type passed from the application to the shader. Just be sure to have ticked the right flags in the options window.

Options

Another nice option is the preconfigured set of variables, for example the ViewProjection and GeomtryTransform matrices

Variables Manager

As you can see from the image above, the uTime is automatically inferred as the elapsed time since the program has started, while uTimeFreq is the uniform used to scale the animation (frequency) and doesn’t link to any “system” (that is, given by the application in some predefined way) variables, but it is shown as a “pinned” variable thanks to the options Pin detected uniforms in the options window

Pinned variables

This variable can be changed on the fly to slow down or speed up the animation

Input variables

Other than the above uniform variable, there is an important set of variables that are set by default when a new project is created by clicking on the File -> New -> GLSL menu item : the Input variables . They can be shown by clicking on the Shader pass , in this case Simple and press Input layout .

Input variables item Input variables

The default view shows the variables for the vertex position, normal, and texture uv coordinates

New project

Now that the main concepts have been set, let’s start with a new project and the vertex shader.

  1. Press File -> New -> GLSL to create a new project
    • By default a Shader Pass called Simple will be created. It includes a default Box with the vertex and fragment shader already set.
  2. Double click on Simple to open the shader editor
  3. Two shaders will be shown:
    • The vertex shader -> Simple(VS) where VS stands for vertex shader
    • The fragment/pixel shader -> Simple(PS) where PS stands for pixel shader

Initial view

As you can see the default vertex shader is capable of setting a different color to each face of the box.

Vertex shader

In the window with header Simple(VS) we will change only the gl_Position variable and pass the pos variable, but before that, let’s see what they are and how they can be used in the shader.

The pos variable and OpenGL default coordinate system

OpenGL has a default coordinate system with the X going from left to right, Y from bottom to top and positive Z going towards the viewer (right handed coordinates). A good explanation of coordinate systems and the right-handed system is here.

This variable pos contains the a three dimensional vectory (glsl type vec3) object (or local) space position of the single cube vertex passed by the application to the shader.

The shown cube has side length 1, is centered in (0,0,0) and has 8 vertices where each coordinate X,Y,Z ranges from -0.5 to 0.5.

Cube opposite vertices coordinate. The center is in (0,0,0), the X axis goes to right as positive direction, Y axis goes up for positive direction and Z goes towards the viewer

The first line of the shader specify the version of the GLSL language to use, while the other declare the variables (discussed above and set by SHADERed)

1uniform mat4 matVP;
2uniform mat4 matGeo;
3layout (location = 0) in vec3 pos;

From local space to screen space

In order to set the vertex position, a set of matrix multiplications must be applied to pass from the vertex specified in local space to screen space.

The variables matGeo and matVP and are used to transform, respectively, the local coordinate to world space and then to screen space. To apply the matrix multiplication the vertex must be transformed to homogeneous coordinate with vec4(pos,1)

1...
2void main() {
3    gl_Position = matVP * matGeo * vec4(pos, 1);
4    ...
5}

Output variable

The position will be later used by the fragment shader, so a out vec3 posObj variable is declared and then set in the main() function.

The full shader is

 1#version 330
 2
 3uniform mat4 matVP;
 4uniform mat4 matGeo;
 5layout (location = 0) in vec3 pos;
 6
 7out vec4 color;
 8out vec3 posObj;
 9
10void main() {
11   	   	gl_Position = matVP * matGeo * vec4(pos, 1);
12		posObj = pos;
13}

The followin screenshot shows what you should see in the vertex shader window:

Vertex shader

Fragment shader

The vertex shader has been responsible to set the final position of the vertex, while the fragment shader sets the final pixel value.

User variables

As described above, SHADERed allow to set shader variables defined by the user or passed from a set of predefined value. One of them is the time passed since the application has been started. By setting

1uniform float uTime;
2uniform float uTimeFreq;

the Input Variables option windows should automatically infer the uTime system variable and the user defined uTimeFreq uniform. If not, enter them as shown in the image below

Pinned variables

When you entered uTimeFreq press the + button to add it and then the other + button on the right to pin in the “Pinned” tab in the main editor.

The shader uses the time and the sin function to get a smooth value between -1 and 1, then it set the absolute value to get only the positive value.

The final multiplcation posObj * abs(sin(uTime * uTimeFreq)) set the color based on the coordinate of the local space position of the vertex that was set in the vertex shader.

posObj is a vec3 with values in the range [-0.5, 0.5]

 1#version 330
 2
 3out vec4 outColor;
 4in vec3 posObj;
 5uniform float uTime;
 6uniform float uTimeFreq;
 7
 8void main() {
 9  outColor = vec4(posObj * abs(sin(uTime * uTimeFreq)), 1);  
10}

The final result is an animation of a cube that fades from black to color.

This is a screen shot of the effect

Final result

and here a video of the animation

In the next post we’ll se why there is a black spot in two faces of the cube and how to get rid of it.