Godot 4 Shaders: Write 2D shaders for your game from scratch
Godot 4 Shaders: Write 2D shaders for your game from scratch
Learn to create visual effects (VFX) with 2D shaders via GDShader language of Godot 4. PART 1: Beginner to intermediate.
Enroll Now
The Godot Engine, renowned for its flexibility and open-source nature, continues to evolve, and Godot 4 brings a wealth of new features and improvements. Among these are enhancements to the shader language and shader capabilities, making it an exciting time for developers looking to push the visual boundaries of their games. In this guide, we'll delve into writing 2D shaders in Godot 4 from scratch, exploring the basics and then moving on to more advanced techniques.
Understanding Shaders
Shaders are small programs that run on the GPU, allowing for real-time image processing and rendering effects. In Godot, shaders are written in a language similar to GLSL (OpenGL Shading Language) but tailored to the Godot environment. Shaders can be used for various effects, such as changing colors, creating patterns, or generating complex visual effects like water reflections, lighting, and more.
There are three primary types of shaders in Godot:
- CanvasItem Shader: Used for 2D rendering.
- Spatial Shader: Used for 3D rendering.
- Particle Shader: Used for customizing particle systems.
In this guide, we'll focus on CanvasItem shaders, which are used for 2D elements.
Getting Started with a Basic Shader
To start, let's create a simple shader that changes the color of a sprite. This will help us understand the basic structure and syntax of Godot shaders.
Create a New Shader: In your Godot project, create a new ShaderMaterial and attach it to a 2D node, such as a Sprite.
Open the Shader Editor: Click on the ShaderMaterial to open the shader editor.
Write the Shader Code:
glslshader_type canvas_item; void fragment() { // Set the color of the pixel to red COLOR = vec4(1.0, 0.0, 0.0, 1.0); }
This simple shader sets every pixel of the sprite to red. Let's break down the code:
shader_type canvas_item;
tells Godot that this is a 2D shader.- The
fragment()
function runs for each pixel (or fragment) and determines its final color. TheCOLOR
variable is a built-in that represents the pixel's color.
Adding Uniforms
Uniforms are variables that can be passed from your game to the shader, allowing you to control shader properties from the editor or code. Let's modify our shader to change the color dynamically.
glslshader_type canvas_item; uniform vec4 modulate_color : hint_color; void fragment() { COLOR = modulate_color; }
Here, modulate_color
is a uniform variable of type vec4
(a vector with four components, representing RGBA). The hint_color
tells Godot to show a color picker in the editor for this uniform.
Adding Textures
Textures are essential in shaders for adding details and patterns. Let's create a shader that uses a texture and applies a grayscale effect.
Attach a Texture: Ensure your ShaderMaterial has a texture applied in the Godot editor.
Modify the Shader Code:
glslshader_type canvas_item; uniform sampler2D texture : hint_albedo; void fragment() { vec4 tex_color = texture(texture, FRAGCOORD.xy / SCREEN_PIXEL_SIZE); float gray = dot(tex_color.rgb, vec3(0.299, 0.587, 0.114)); COLOR = vec4(vec3(gray), tex_color.a); }
sampler2D texture
is a uniform for the texture.texture(texture, FRAGCOORD.xy / SCREEN_PIXEL_SIZE)
samples the texture at the current pixel position.dot(tex_color.rgb, vec3(0.299, 0.587, 0.114))
calculates the grayscale value using a standard formula.COLOR = vec4(vec3(gray), tex_color.a)
sets the final color, preserving the alpha value.
Creating Patterns and Animations
Shaders can also be used to create dynamic patterns and animations. Let's create a simple shader that generates a moving wave pattern.
glslshader_type canvas_item; uniform float time; uniform vec2 wave_frequency = vec2(10.0, 10.0); uniform float wave_amplitude = 0.1; void fragment() { vec2 uv = FRAGCOORD.xy / SCREEN_PIXEL_SIZE; float wave = sin(uv.x * wave_frequency.x + time) * wave_amplitude; wave += sin(uv.y * wave_frequency.y + time) * wave_amplitude; COLOR = vec4(vec3(0.5 + wave), 1.0); }
time
is a uniform that you can update from your game code to animate the wave.wave_frequency
andwave_amplitude
control the wave's frequency and amplitude.- The
sin
functions generate the wave pattern, anduv
is the normalized screen coordinates.
In your game code, update the time
uniform to animate the wave:
gdfunc _process(delta): $Sprite.material.set_shader_param("time", OS.get_time())
Advanced Techniques
Normal Mapping
Normal mapping can add depth to 2D surfaces by simulating lighting effects. To implement normal mapping, you'll need a normal map texture.
glslshader_type canvas_item; uniform sampler2D texture : hint_albedo; uniform sampler2D normal_map : hint_normal; uniform vec2 light_dir = vec2(1.0, -1.0); void fragment() { vec2 uv = FRAGCOORD.xy / SCREEN_PIXEL_SIZE; vec4 tex_color = texture(texture, uv); vec3 normal = texture(normal_map, uv).rgb * 2.0 - 1.0; float light = max(dot(normalize(normal.xy), normalize(light_dir)), 0.0); COLOR = vec4(tex_color.rgb * light, tex_color.a); }
Procedural Textures
Procedural textures are generated by the shader itself. Here’s a shader that generates a simple checkerboard pattern.
glslshader_type canvas_item; uniform vec2 resolution; uniform vec2 checker_size = vec2(10.0, 10.0); void fragment() { vec2 uv = FRAGCOORD.xy / resolution * checker_size; float checker = mod(floor(uv.x) + floor(uv.y), 2.0); COLOR = vec4(vec3(checker), 1.0); }
resolution
is the size of the screen or texture.checker_size
controls the size of the checker squares.
Post-Processing Effects
Shaders can also be used for post-processing effects, such as blurring or edge detection. Here’s a simple blur shader.
glslshader_type canvas_item; uniform sampler2D texture : hint_albedo; uniform float blur_size = 1.0 / 512.0; void fragment() { vec2 uv = FRAGCOORD.xy / SCREEN_PIXEL_SIZE; vec4 color = vec4(0.0); for (int x = -2; x <= 2; x++) { for (int y = -2; y <= 2; y++) { color += texture(texture, uv + vec2(x, y) * blur_size); } } COLOR = color / 25.0; }
blur_size
controls the blur intensity.- The nested loops sample the surrounding pixels and average their colors.
Conclusion
Writing 2D shaders in Godot 4 opens up a world of creative possibilities for your game. By understanding the basics and gradually incorporating more advanced techniques, you can create stunning visual effects that enhance your game's aesthetic and immerse players in your world. Whether you're modulating colors, generating procedural patterns, or implementing complex lighting effects, the power of shaders in Godot 4 is at your fingertips. Happy coding!