Free Tutorial · Godot 4 Shaders

Godot 4 Shader Tutorial — Write Your First
Visual Shader and GLSL Code

SScriptSnap/March 2026/14 min read

Shaders are the secret weapon behind every beautiful game effect — glowing outlines, rippling water, fiery dissolves, retro CRT filters. In Godot 4 the shader pipeline got a major overhaul: a revamped Visual Shader editor for node-based workflows and a modern GLSL-like shading language for devs who prefer raw code. Whether you're a designer who loves drag-and-drop or a coder who lives in a text editor, Godot 4 has you covered.

In this Godot 4 shader tutorial, you'll learn how ShaderMaterial works under the hood, walk through the visual shader editor step by step, then write your own GLSL fragment and vertex shaders. We'll finish with four practical Godot shader effects you can drop straight into your project — animated water, sprite outlines, dissolve transitions, and a chromatic-aberration post-process filter.

No prior shader experience required. If you can write basic GDScript, you can learn shaders. Let's jump in.

Tutorial

What Is a Shader (and Why Should You Care)?

A shader is a tiny program that runs on the GPU. Instead of processing one pixel at a time like GDScript on the CPU, a shader processes millions of pixels in parallel every frame. That's what makes effects like real-time water, glow, and distortion possible at 60 fps.

Godot 4 supports three shader types:

  • Spatial shaders — for 3D meshes and environments
  • CanvasItem shaders — for 2D sprites, UI, and TileMaps
  • Particle shaders — for GPU-accelerated particle effects

Every shader in Godot starts with a shader_type declaration, then defines one or more processor functions: vertex() manipulates geometry, fragment() determines each pixel's color, and light() handles per-light calculations.

In this tutorial we'll focus primarily on CanvasItem shaders (2D), since most indie devs start there. Everything you learn transfers directly to spatial shaders when you're ready for 3D.

ShaderMaterial: How Godot Connects Shaders to Nodes

Before writing any shader code, you need to understand ShaderMaterial. In Godot 4, every visual node has a material property. When you assign a ShaderMaterial to it, Godot replaces the default rendering pipeline for that node with your custom shader.

Here's the setup flow:

  • Select your Sprite2D (or any CanvasItem node)
  • In the Inspector → Material → assign a new ShaderMaterial
  • Inside the ShaderMaterial → Shader → create a new Shader or VisualShader
  • Write your code or build your node graph

Uniforms (shader parameters) that you declare with the uniform keyword are automatically exposed in the Inspector, so designers can tweak values without editing code. You can also set them from GDScript at runtime using material.set_shader_parameter().

set_uniform_from_gdscript.gd
# Assign a shader param at runtime
func _ready():
    var mat = $Sprite2D.material as ShaderMaterial
    mat.set_shader_parameter("dissolve_amount", 0.5)

This pattern of declaring uniforms in the shader and driving them from tweens or signals is the core of every interactive shader effect.

Want shader-powered dissolve effects you can drop into any project? Our Shader Tricks micro-course covers it in 15 minutes.

Check it out →

Visual Shader Editor Walkthrough

Not everyone wants to write raw GLSL right away — and that's fine. Godot 4's Visual Shader editor lets you build shaders by connecting nodes in a graph, just like Unreal's Material Editor or Blender's Shader Nodes. It's the fastest way to prototype Godot visual shader effects without memorizing GLSL syntax.

Here's how to create your first visual shader:

  • Create a ShaderMaterial on your node (as described above)
  • In the Shader slot, choose New VisualShader instead of New Shader
  • Double-click the VisualShader resource to open the graph editor
  • You'll see an Output node — this is where final color (Albedo/Color) and other outputs connect
  • Right-click the canvas → Add Node → browse categories like Input, Color, Texture, Math
  • Drag connections between node ports to build your effect pipeline

The visual shader editor is organized into processor tabs at the top — Vertex, Fragment, and Light. Each tab has its own Output node. Most 2D effects only need the Fragment tab (which controls pixel color).

Example: Tinting a Sprite with Visual Shader

Let's build a simple color tint effect entirely in the visual editor:

  • Add a Texture2D node → connect it to a ColorOp (Multiply) node
  • Add a ColorUniform node → set the default to a warm orange → connect it to the other input of Multiply
  • Connect the Multiply output → Fragment Output's Color port
  • The sprite is now tinted orange, and you can change the tint color from the Inspector at any time

Visual shaders compile down to the same GLSL code that text shaders use. There's no performance difference. The editor even has a "Generated Code" button that shows you the raw GLSL output — a great way to learn the syntax while building visually.

Once you outgrow the node graph, you can switch to writing code directly. The concepts (uniforms, fragment function, UV coordinates) are exactly the same.

📧

Want more Godot tricks?

Get our free GDScript Cheat Sheet with 20+ copy-paste snippets for signals, exports, state machines & more.

GDSJoin 500+ Godot devs · No spam · Unsubscribe anytime

Writing Raw Shader Code (Godot GLSL)

Godot's shading language is based on GLSL ES 3.0 with some Godot-specific conveniences. If you've used GLSL in OpenGL or WebGL before, you'll feel right at home. If not, don't worry — Godot's version is simpler because the engine handles most of the boilerplate for you.

Here's the minimal skeleton of a Godot custom shader:

minimal_shader.gdshader
shader_type canvas_item;

// Uniforms — exposed in the Inspector
uniform vec4 tint_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);

void fragment() {
    // Read the sprite's original color
    vec4 tex = texture(TEXTURE, UV);
    // Mix original color with our tint
    COLOR = tex * tint_color;
}

Let's break down the key concepts:

  • shader_type canvas_item — tells Godot this shader applies to 2D CanvasItem nodes
  • uniform — declares a parameter accessible from GDScript and the Inspector
  • source_color — hint that tells the Inspector to show a color picker
  • fragment() — runs once per pixel, every frame. This is where the magic happens
  • TEXTURE — built-in that references the node's current texture
  • UV — normalized coordinates (0,0 top-left → 1,1 bottom-right) of the current pixel
  • COLOR — the output variable. Whatever vec4 you assign here becomes the pixel's final RGBA color

That's the foundation of every Godot GLSL shader. From here, you can add vertex() functions for geometry manipulation, time-based animations with the built-in TIME variable, and noise for organic effects. Let's build some real effects.

Learning Godot from scratch? Start with our micro-courses — each one is focused on a single topic and takes just 15 minutes.

Browse courses →

Practical Effect #1: Animated 2D Water

Water is one of the most-requested Godot shader effects. This shader uses sine-wave UV distortion and a subtle color shift to create a convincing animated water surface for 2D games.

water_2d.gdshader
shader_type canvas_item;

uniform float wave_speed : hint_range(0.0, 5.0) = 1.5;
uniform float wave_amplitude : hint_range(0.0, 0.1) = 0.02;
uniform float wave_frequency : hint_range(1.0, 50.0) = 15.0;
uniform vec4 water_tint : source_color = vec4(0.2, 0.5, 0.9, 0.7);

void fragment() {
    // Offset UVs with two overlapping sine waves
    vec2 uv_offset = UV;
    uv_offset.x += sin(UV.y * wave_frequency + TIME * wave_speed) * wave_amplitude;
    uv_offset.y += cos(UV.x * wave_frequency * 0.8 + TIME * wave_speed * 0.7) * wave_amplitude * 0.6;

    vec4 tex = texture(TEXTURE, uv_offset);
    COLOR = mix(tex, water_tint, water_tint.a);
}

How it works:

  • Two overlapping sine/cosine waves distort the UV coordinates, creating organic motion
  • TIME (built-in) drives the animation — no GDScript needed
  • wave_speed, wave_amplitude, and wave_frequency are uniforms you can tweak in the Inspector
  • mix() blends the original texture with a water_tint color based on the tint's alpha

Apply this to a TextureRect or Sprite2D with a water texture and you've got yourself a living, breathing lake. Pair it with a TileMap for a complete 2D world.

Practical Effect #2: Sprite Outline Shader

Sprite outlines are essential for highlighting interactive objects, selected units, or enemies. This shader samples neighboring pixels and draws a colored border around any non-transparent area.

outline.gdshader
shader_type canvas_item;

uniform vec4 outline_color : source_color = vec4(1.0, 0.85, 0.0, 1.0);
uniform float outline_width : hint_range(0.0, 10.0) = 1.0;

void fragment() {
    vec4 tex = texture(TEXTURE, UV);
    vec2 ps = TEXTURE_PIXEL_SIZE * outline_width;

    // Sample 4 neighbors (up, down, left, right)
    float a = texture(TEXTURE, UV + vec2(ps.x, 0)).a;
    a += texture(TEXTURE, UV + vec2(-ps.x, 0)).a;
    a += texture(TEXTURE, UV + vec2(0, ps.y)).a;
    a += texture(TEXTURE, UV + vec2(0, -ps.y)).a;

    // If any neighbor is opaque but this pixel is transparent → outline
    if (tex.a < 0.1 && a > 0.0) {
        COLOR = outline_color;
    } else {
        COLOR = tex;
    }
}

The trick is TEXTURE_PIXEL_SIZE — a Godot built-in that gives you the size of a single pixel in UV space. Multiplying it by outline_width scales the sampling distance. For thicker outlines, sample diagonals too (8-directional sampling). You can drive the outline_color from GDScript to flash red when an enemy takes damage or pulse gold for collectibles — perfect for adding tween-driven juice.

Practical Effect #3: Dissolve Transition

Dissolve effects are everywhere — enemy death animations, scene transitions, magic spells. The idea is simple: use a noise texture as a threshold map and discard pixels whose noise value is below a controllable cutoff.

dissolve.gdshader
shader_type canvas_item;

uniform sampler2D dissolve_noise;
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
uniform float edge_width : hint_range(0.0, 0.1) = 0.03;
uniform vec4 edge_color : source_color = vec4(1.0, 0.4, 0.1, 1.0);

void fragment() {
    vec4 tex = texture(TEXTURE, UV);
    float noise = texture(dissolve_noise, UV).r;

    // Discard pixel if noise is below threshold
    if (noise < dissolve_amount) {
        discard;
    }

    // Glowing edge at the dissolve boundary
    float edge = smoothstep(dissolve_amount, dissolve_amount + edge_width, noise);
    COLOR = mix(edge_color, tex, edge);
}

To use it: create a NoiseTexture2D resource, assign it to the dissolve_noise uniform in the Inspector, then animate dissolve_amount from 0 to 1 with a tween:

dissolve_controller.gd
func play_dissolve():
    var mat = $Sprite2D.material as ShaderMaterial
    var tween = create_tween()
    tween.tween_method(
        func(val): mat.set_shader_parameter("dissolve_amount", val),
        0.0, 1.0, 1.2
    )

The edge_color creates a hot ember glow at the dissolve boundary — smoothstep() gives it a soft gradient rather than a hard line. This is one of the most popular Godot shader effects and it works for death animations, portals, scene wipes — anything that needs to dramatically appear or disappear.

Get the complete Shader Tricks course with dissolve, water, outline, and 3 more effects — ready to drop into your game.

Grab the bundle →

Practical Effect #4: Chromatic Aberration

Chromatic aberration — that RGB color-fringe effect you see in horror games and retro filters — is surprisingly simple. We sample the red, green, and blue channels at slightly different UV offsets to simulate lens distortion.

chromatic_aberration.gdshader
shader_type canvas_item;

uniform float aberration_amount : hint_range(0.0, 0.02) = 0.005;

void fragment() {
    vec2 offset = (UV - 0.5) * aberration_amount;

    float r = texture(TEXTURE, UV + offset).r;
    float g = texture(TEXTURE, UV).g;
    float b = texture(TEXTURE, UV - offset).b;
    float a = texture(TEXTURE, UV).a;

    COLOR = vec4(r, g, b, a);
}

Apply this shader to a ColorRect covering your entire viewport (make sure it's the top-most layer). The (UV - 0.5) calculation pushes the offset radially from the center, so the effect is strongest at the screen edges — just like a real camera lens.

Pro tip: animate aberration_amount from 0 to a high value and back on damage hits. Combined with a screen-shake tween, it creates incredible impact feedback.

Vertex Shaders: Manipulating Geometry

So far we've only used fragment(). But shaders can also transform geometry with a vertex() function. This is useful for wobbly sprites, wind effects on foliage, and wave-deformed meshes. In 2D, the VERTEX built-in gives you the position of each corner of the sprite quad.

wobble_vertex.gdshader
shader_type canvas_item;

uniform float wobble_strength : hint_range(0.0, 20.0) = 5.0;
uniform float wobble_speed : hint_range(0.0, 10.0) = 3.0;

void vertex() {
    VERTEX.x += sin(TIME * wobble_speed + VERTEX.y * 0.05) * wobble_strength;
}

This creates a gentle sideways wobble — great for idle animations on NPCs, signs, or tree sprites. The wobble varies with the VERTEX.y position so the bottom stays anchored while the top sways, simulating wind.

You can combine vertex and fragment functions in a single shader. For example, a flag that wobbles in the wind (vertex) and has a glowing edge (fragment).

Uniform Hints and Best Practices

Godot provides special uniform hints that control how parameters appear in the Inspector. Using them makes your shaders designer-friendly:

  • hint_range(min, max, step) — clamps the value and shows a slider
  • source_color — shows a color picker (important for sRGB-correct colors)
  • hint_default_white — texture defaults to a white 1×1 if none assigned
  • filter_nearest — disables texture filtering (pixel-art friendly)
  • repeat_enable — allows the texture to tile beyond 0–1 UV range

Performance tips for Godot custom shader development:

  • Minimize texture lookups — each texture() call has a cost. Cache results in a variable if you use the same sample twice
  • Use step() and smoothstep() instead of if/else for branchless GPU code
  • Avoid pow() in hot loops — pre-compute values as uniforms where possible
  • Profile with the Godot debugger's shader profiler (Project → Debugger → Monitors → Shaders)

If you're already comfortable with @export annotations in GDScript, think of uniform hints as the shader equivalent. They serve the same purpose: making your code designer-friendly while keeping things type-safe.

Visual Shaders vs. Code Shaders: When to Use Each

Both approaches compile to the same GPU instructions. Performance is identical. So when should you choose one over the other?

Visual Shaders

  • Rapid prototyping and experimentation
  • Non-programmers and technical artists
  • Quick one-off effects
  • Learning how shader nodes map to code

Code Shaders

  • Complex math and multi-pass effects
  • Version control-friendly (plain text diffs)
  • Reusable shader libraries and includes
  • Full control over every instruction

Many developers start with the visual editor to get the look right, then click "Convert to ShaderMaterial" and refine the generated code for production. It's the best of both worlds.

Where to Go From Here

You've now covered the entire foundation of Godot 4 shaders — from ShaderMaterial setup, through the visual editor, to writing raw GLSL with practical effects. Here's how to keep leveling up:

  • Experiment with spatial shaders for 3D effects like rim lighting and fresnel
  • Explore Godot's built-in noise functions (available in visual and code shaders)
  • Build a shader library — save reusable .gdshader files and share them across projects
  • Use the shader profiler to optimize hot shaders on low-end devices
  • Combine shaders with GDScript signals for interactive, gameplay-driven effects

If you found this tutorial useful, check out our other Godot 4 tutorials on state machines, save systems, and inventory management. Shaders are even more powerful when paired with solid game architecture.

EOF
Free download
+ weekly tips

Get the Free GDScript Cheat Sheet + Weekly Shader Tips

Join 50+ Godot devs getting weekly shader tricks, GDScript patterns, and indie dev insights straight to their inbox.

20+ GDScript snippets
Weekly Godot tips
Zero spam, cancel anytime
>
GDSJoin 500+ Godot devs. Unsubscribe in one click.