You are here:

LightAct can load and use custom fragment shaders written in GLSL (more info here).

Custom shaders are loaded with a Custom Shader node. At its most basic configuration, it has just 2 inputs: Resolution and Filepath. The Resolution input tells the node how big of a texture the shader should create and the Filepath input tells the node where on the computer it can find the shader file. The output of the fragment shader will be used to fill the entire resolution.

Creating a simple shader

Before we begin, let’s just say that we won’t be explaining all the depths of GLSL here as it is much too big of a subject for this article.

But, to illustrate what you can do with Custom Shader node, let’s create a simple shader that will output, say, purple color. We start by creating a new file somewhere on the disk. You can name it however you want and also you can use any extension that you want. For the purpose of this guide, however, let’s name it myshader.frag. Open the file in your favourite text editor such as Notepad++ and enter the following code:

#version 150

out vec4 outColor;


void main()
{
      outColor=vec4(1.0,0.0,1.0,1.0);
}

After you’ve connected the node as shown below and inserted a correct Filepath to the file, you should see this:

custom shader layout

If your shader doesn’t seem to be updating, you might have to click on the Rebuild shader button.

rebuild shader

Great! To understand better what we typed in, let’s go quickly through it line-by-line:

#version 150

This line tells the compiler which version of GLSL the shader is written in. It should always say 150. Let’s move to the next line.

out vec4 outColor;

This line tells the compiler that a vec4 variable with the name outColor is what the shader will output. Vec4 means a 4-component vector representing an RGBA value.

void main()
{
      outColor=vec4(1.0,0.0,1.0,1.0);
}

void main() is the main function that is called by LightAct every frame. You can have define functions if you want, but they need to be called from within the main function. The function has just one line in which we set outColor variable to (1.0,0.0,1.0,1.0), which is purple.

Using vertex coordinates

What if we wanted the shader to output a color based on the position of the pixel in the texture. You can do that by using a variable called vTexCoord0 as shown below.

#version 150

in vec4 vTexCoord0;

out vec4 outColor;

void main()
{
      outColor=vTexCoord0;
}

vTexCoord0 passes the location of each pixel in relative coordinates. These coordinates are passed in the first 2 components of the vec4. The third component is always 0, and the 4th is always 1. So the value of vTexCoord0 in the topleft corner is (0.0,0.0,0.0,1.0), in the top right is (1.0,0.0,0.0,1.0) in the bottom left is (0.0,1.0,0.0,1.0) and in the bottom right it’s (1.0,1.0,0.0,1.0).

custom shader layout vtexcoord

Therefore, the updated shader creates a familiar green-red-yellow texture as shown above.

Using time in the shader

If you want your shader to create a dynamic texture, the easiest way to do it is to use a time variable. LightAct makes it easy with the global time that’s passed to the shader with laGlobalTime float uniform.

#version 150

uniform float laGlobalTime; // shader playback time (in seconds)

in vec4 vTexCoord0;

out vec4 outColor;

void main()
{
  float coorAddR=0.5*sin(laGlobalTime)+0.5;
  float coorAddG=0.5*sin(laGlobalTime*1.5)+0.5;
  float coorAddB=0.5*cos(laGlobalTime*2)+0.5;
  outColor=vec4(vTexCoord0.x+coorAddR,vTexCoord0.y+coorAddG,coorAddB,1.0);
}

As you can see above, we can grab the value of this variable through line uniform float laGlobalTime;. Uniform is a GLSL keyword which signifies that the shader should expect an incoming float value with that name.

float coorAddR=0.5*sin(laGlobalTime)+0.5;
float coorAddG=0.5*sin(laGlobalTime*1.5)+0.5;
float coorAddB=0.5*cos(laGlobalTime*2)+0.5;
outColor=vec4(vTexCoord0.x+coorAddR,vTexCoord0.y+coorAddG,coorAddB,1.0);

Let’s explain the lines we added in our main() function. We first calculate 3 float values from laGlobalTime and then add them to outColor.

custom shader global time

After we click on Rebuild shader button we see that the texture is now slowly moving between 3 colors.

Using texture resolution

There is another uniform being passed from LightAct to the shader by default. It is called laResolution and it is a vec2 variable which tells the resolution of the texture.

uniform vec2 laResolution;

You can get it in the shader by inserting the line above.

Passing additional variables to the shader

In a lot of cases, we’ll want to pass some variables from LightAct to our shader. LightAct allows you to pass float, color or textures and in the following chapters, we’ll go through the options.

Passing a float variable

We start by clicking on the Add float button on the node.

custom shader float variable

When we click it a new Float input pin will appear with the name laFloat0. As with all the other input options, the name shown next to the pin is what you should use in the shader.

custom shader with float input

Now, let’s modify our shader so that it will use this input. We do that by adding

uniform float laFloat0;

above the main() function and changing the line where we set the outColor variable inside the main() function like so:

outColor=vec4(vTexCoord0.x+coorAddR,vTexCoord0.y+coorAddG,coorAddB,laFloat0);

As you can see, we’ve used the laFloat0 variable to set the opacity (alpha) of the texture. If you wanted to use more float inputs, the names that you should use in the shader will be laFloat1, laFloat2 and so on.

custom shader layout with opacity

After clicking on Rebuild shader you should notice that the opacity of the texture below Render to canvas node, corresponds to the laFloat0 input in the Custom Shader node.

Passing a color variable

LightAct allows you to pass a color variable as well. Similarly to the process of passing a float variable, we start by clicking on Add color button on the node itself.

custom shader add color

You’ll see a new color input. It doesn’t have a label, but the names of the uniforms you should use in the shader, are laColor0, laColor1, laColor2 and so on.

custom shader color input

Now let’s modify our shader so that it will use this new color.

#version 150

uniform float laGlobalTime; // shader playback time (in seconds)
uniform float laFloat0;
uniform vec4 laColor0;

in vec4 vTexCoord0;

out vec4 outColor;

void main()
{
  float coorAddR=0.5*sin(laGlobalTime)+0.5;
  float coorAddG=0.5*sin(laGlobalTime*1.5)+0.5;
  float coorAddB=0.5*cos(laGlobalTime*2)+0.5;
  outColor=vec4(vTexCoord0.x+coorAddR,vTexCoord0.y+coorAddG,coorAddB,1.0);
  outColor=vec4(vec3(outColor.xyz-laColor0.xyz),laFloat0);
}

As you can see, we’ve added uniform vec4 laColor line above the main() function. Please note that LightAct’s color [0-255] range is automatically remapped to GLSL’s vec4 [0-1] range.

We’ve also changed the way how we set the outColor value so that laColor0 is subtracted from the initial calculation.

custom shader with color input

We get a result not too dissimilar to what we had before, but some interesting effects can be created if you play with the color input on the node.

Passing a texture

There is one more variable type that we need to go through and that is a Texture. We click on Add texture button on the Custom Shader node and besides that, let’s create an Animated checkerboard node, which will serve as our texture source.

animated checkerboard and custom shader

In the Resolution input of the Animated checkerboard node, insert 1920 x 1080. Then connect its output to the laTexture0 input of the Custom Shader node.

Now it’s time to modify our shader for the last time.

#version 150

uniform float laGlobalTime; // shader playback time (in seconds)
uniform float laFloat0;
uniform vec4 laColor0;
uniform sampler2D laTexture0;

in vec4 vTexCoord0;

out vec4 outColor;


in vec4 vertColor;


void main()
{
  vec2 p = vTexCoord0.st;
  
  float coorAddR=0.5*sin(laGlobalTime)+0.5;
  float coorAddG=0.5*sin(laGlobalTime*1.5)+0.5;
  float coorAddB=0.5*cos(laGlobalTime*2)+0.5;
  outColor=vec4(vTexCoord0.x+coorAddR,vTexCoord0.y+coorAddG,coorAddB,1.0);
  outColor=vec4(vec3(outColor.xyz-laColor0.xyz),laFloat0);
  outColor=outColor*texture(laTexture0,p);
}

As you can see, we’ve added 3 lines to our shader:

  • uniform sampler2D laTexture0; tells the shader that it should grab a texture connected to the laTexture0 input.
  • vec2 p = vTexCoord0.st; creates a vec2 variable p from the first 2 components of vTexCoord0. This means p represents relative coordinates of the pixel.
  • outColor=outColor*texture(laTexture0,p); multiplies the outColor created in previous lines (and explained in previous paragraphs) with a texture created from laTexture0 mapped according to p.

animated checkerboard and custom shader layout

The result is as shown above.

When is the shader (re)loaded

A shader file is loaded when the program starts. It can be manually reloaded if you press the Reload button in the node properties on the right.

rebuild shader

That’s useful when you are working on the shader file in an external editor and you want LightAct to use the changes.

Previous JSON parsing and packaging
Next Lua scripting