Introduction to Shaders in Godot 4

Discover the art of game customization with shaders in Godot 4. Learn to craft your visual effects, from texture color manipulation to sprite animations, in this guide to writing fragment and vertex shaders. By Eric Van de Kerckhove.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 5 of this article. Click here to view the first page.

Getting the Dimensions of a Sprite

While you can use the UV coordinates to get normalized positions, sometimes you need to know the dimensions of a sprite in pixels. For this, you cam use the VERTEX coordinates. Each vertex has a position that is relative to the center of the sprite.

Vertex illustration with positions

By multiplying the absolute value of the position of a vertex by 2, you can get the dimensions of the sprite in pixels. In the example above, you could take top-left vertex, which has a position of (X: -64, Y: -64). Its absolute value would be (X: 64, Y: 64), as you drop the sign. Multiplying the absolute value by 2 gives you (X: 128, Y: 128), which are the dimensions of the sprite in pixels! Because a sprite only has 4 vertices, this technique works for each vertex.

To showcase how this works in practice, it’s time to create a shader that shows how to get the dimensions of a sprite and bounces it up and down.

Drag a flower.png from the textures folder into the viewport and name the node Bounce.

Flower

Next, create a new shader in the vertex folder and name it bounce.gdshader. Open this new shader in the shader editor.
To start with, add these variable declarations to the vertex() function:

float time_multiplier = 5.0; // 1
float sine_wave = sin(TIME * time_multiplier); // 2
float abs_sine_wave = abs(sine_wave); // 3
float sprite_height = abs(VERTEX.y * 2.0); // 4
float shrink = sprite_height * 0.25; // 5

These variables are used to set the speed of the bounce effects and how much the sprite shrinks. Here’s an overview:

  1. time_multiplier is the speed of the bounce, it’s a multiplier.
  2. sine_wave is the result of the sin function, a value between -1 and 1. It gets multiplied by the time_multiplier to control the speed of the bounce.
  3. abs_sine_wave is the absolute value of the sine_wave result, a value between 0 and 1. This makes sure that the vertices won’t stretch, but only compress.
  4. sprite_height is the height of the sprite in the Y direction using the formula I explained above: the absolute value of a vertex position multiplied by 2.
  5. shrink is the height of the shrink effect. In this case, 25% of the sprite’s height. The higher this value is, the more the sprite shrinks.

Now add the code below:

if (UV.y < 0.5) { // 1
    VERTEX += vec2(0, abs_sine_wave * shrink); // 2
}

This will apply the shrinking effect only to the top vertices:

  1. The if statement will apply to vertices with a y value that is less than 0.5. These are the top vertices.
  2. Change the position of the vertex in the y direction by multiplying the abs_sine_wave value by shrink. This will add an offset to the position of the top vertices between 0 and the shrink value.

Save the shader and look at the flower, it should be happily bouncing up and down!

Flower bouncing

An effect like this can be used to mark that an item is selected, or to make your scene more lively.

Making Shaders Customizable

Like with any script, it’s bad practice to use “magic numbers” in your shaders. You should use variables that you can tweak in the editor instead. With shaders in Godot, these kinds of variables are called uniforms. You can compare them to @export variables in GDScript. Their values will be visible in the editor for you to change.

Improving the Sway Shader

The sway shader is a good candidate for making customizable. Open it in the shader editor to take a look at it. It has two variables with constant values: sway_amount and time_multiplier:

float sway_amount = 20.0;
float time_multiplier = 4.0; 

To convert these to uniform variables, move them above the vertex() function and add the uniform keyword before them. Here’s what that looks like:

shader_type canvas_item;

uniform float sway_amount = 20.0;
uniform float time_multiplier = 4.0;

void vertex() {
    ...
}

Once a variable is a uniform, you can change its value via the editor. Select the Sway node and expand its Material property. This will reveal a new section named Shader Parameters.

Shader parameters

Now click on the Shader Parameters section to show the parameters.

Shader parameters details

Try adjusting the value of Sway Amount and Time Multiplier to see the effect it has on the tree. You can go from a gentle breeze to a full-on storm by playing with the values.

Tree swaying at different speeds

Using Code to Set Shader Parameters

There’s another benefit of using uniforms in your shaders: you can set these parameters with code! This means you can have dynamic effects based on the gameplay. The trees may be swaying differently depending on the weather for example, or the player avatar might flash white for a moment when hit.
The way this works is by accessing a material and setting the shader parameters based on their name in the shader.

To test this out yourself, create a new folder in the root of the FileSystem and name it scripts. Next, attach a new script to the Main node in the editor named main.gd and place it in the scripts folder. The script should open automatically in the code editor.

This script will change the values of Sway Amount and Time Multiplier based on the position of the mouse cursor. To get started, add a reference to the Sway node by adding the following line below the extends Node2D statement:

@onready var sway: Sprite2D = $Sway

This stores a reference to the Sway node in the sway variable. Next up is the code that will get the mouse position and set the shader parameters. Add this below the variable you added above:

func _input(event): # 1
    if event is InputEventMouseMotion: # 2
        var mat = sway.material as ShaderMaterial # 3
        mat.set_shader_parameter("sway_amount", event.position.x / 10.0) # 4
        mat.set_shader_parameter("time_multiplier ", event.position.y / 25.0) # 5

This code uses the _input function to poll for input events. If the event is a MouseMotion event, it will get the mouse position and set the shader parameters based on that. Here’s a line-by-line breakdown of the code:

  1. Godot’s engine calls the _input function when there’s an input event. The event variable is passed to the function and holds the type of event, along with its properties.
  2. This if statement checks if the event is an InputEventMouseMotion event, which is called whenever the player moves the mouse.
  3. Store the material of the Sway node in the mat variable as a ShaderMaterial.
  4. Use the set_shader_parameter function to set the shader parameter sway_amount to the X position of the mouse divided by 10.0.
  5. Do the same for the time_multiplier based on the Y position of the mouse.

With this code, the more you move your mouse to the right in the viewport, the more the tree will sway. The more you move your mouse to the bottom of the viewport, the faster it will sway. The most important part to take away here is the set_shader_parameter function of the ShaderMaterial, I highly recommend playing around with this in your own projects to create cool effects.

Note: If you want to know the internal name of a shader parameter, you can hover your cursor over the parameter in the editor and it will show you.
Shader parameter name

Save the script and run the project by pressing F5 on your keyboard. Try moving the cursor to different positions in the viewport to see the effect it has on the tree.

Tree swaying with mouse movement