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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Shaders in Godot 4
45 mins
- Getting Started
- What Is a Shader?
- Types of Shaders
- Basics of Texture Manipulation
- Fragment Shaders
- Overwriting Colors
- Manipulating Colors
- Vertex Function
- Moving Vertices
- Using Time
- Time to S(h)ine
- Selective Movement With If-Statements
- Circular Movement
- Getting the Dimensions of a Sprite
- Making Shaders Customizable
- Improving the Sway Shader
- Using Code to Set Shader Parameters
- Shader Hinting
- Combining Vertex and Fragment Functions
- Bonus Shaders
- Where to Go From Here?
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.
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.
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:
-
time_multiplier
is the speed of the bounce, it’s a multiplier. -
sine_wave
is the result of thesin
function, a value between -1 and 1. It gets multiplied by thetime_multiplier
to control the speed of the bounce. -
abs_sine_wave
is the absolute value of thesine_wave
result, a value between 0 and 1. This makes sure that the vertices won’t stretch, but only compress. -
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. -
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:
- The
if
statement will apply to vertices with ay
value that is less than 0.5. These are the top vertices. - Change the position of the vertex in the
y
direction by multiplying theabs_sine_wave
value byshrink
. This will add an offset to the position of the top vertices between 0 and theshrink
value.
Save the shader and look at the flower, it should be happily bouncing up and down!
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.
Now click on the Shader Parameters section to show the parameters.
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.
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:
- Godot’s engine calls the
_input
function when there’s an input event. Theevent
variable is passed to the function and holds the type of event, along with its properties. - This
if
statement checks if the event is anInputEventMouseMotion
event, which is called whenever the player moves the mouse. - Store the material of the Sway node in the
mat
variable as aShaderMaterial
. - Use the
set_shader_parameter
function to set the shader parametersway_amount
to the X position of the mouse divided by 10.0. - 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.
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.