The Godot 4.x shading language (.gdshader): shader types, render modes, processor functions, built-ins per
type, uniforms and hints, screen and depth textures, varyings, global and instance uniforms, driving parameters
from GDScript, materials, visual shaders and how it differs from GLSL. For raw GLSL see
WebGL; tweening shader parameters is in
UI, animation & audio.
3D meshes: MeshInstance3D, CSG*, GPUParticles3D draw passes
vertex(), fragment(), light()
particles
GPUParticles2D / 3D process material
start(), process()
sky
Sky resource in a WorldEnvironment
sky()
fog
FogVolume nodes (volumetric fog, Forward+ only)
fog()
GDScript / inspector ShaderMaterial ── shader: .gdshader │ set_shader_parameter("u", v) ▼ vertex() once per vertex: move VERTEX, set varyings │ (canvas_item: local space; spatial: model space) ▼ rasterise varyings interpolated ▼ fragment() once per pixel: COLOR (2D) or ALBEDO, │ ROUGHNESS, NORMAL_MAP ... (3D) ▼ light() once per pixel per light (optional): LIGHT (2D) or DIFFUSE_LIGHT / SPECULAR_LIGHT (3D)
Omitted processor functions keep Godot's defaults. Write shaders in the built-in editor (errors inline, and
4.7 adds inline previews) or any editor with a .gdshader plugin; .gdshaderinc files hold shared code for
#include "res://shaders/common.gdshaderinc".
Most particle effects need no shader: ParticleProcessMaterial covers emission shapes, curves, color ramps and
turbulence, and can be converted to a ShaderMaterial to customize.
gradient_sky.gdshader
shader_type sky;uniform vec3 top : source_color = vec3(0.2, 0.4, 0.9);uniform vec3 horizon : source_color = vec3(0.9, 0.8, 0.7);void sky() { float t = clamp(EYEDIR.y, 0.0, 1.0); COLOR = mix(horizon, top, pow(t, 0.5)); // sun disc from the first DirectionalLight3D float sun = dot(EYEDIR, LIGHT0_DIRECTION); COLOR += LIGHT0_COLOR * smoothstep(0.998, 1.0, sun);}
Colors without source_color are treated as linear data, so a picked color looks washed out or too dark:
always add source_color to color uniforms and albedo textures.
Global & instance uniforms
Kind
Declared
Set from
Use
Regular
uniform float x;
the material: set_shader_parameter
per material (shared by every node using it)
Instance
instance uniform vec4 tint : source_color;
the node: set_instance_shader_parameter
per node without duplicating materials; scalars and vectors only, 16 per shader
Global
global uniform vec3 player_pos;
Project Settings > Shader Globals, or RenderingServer
one value for every shader (wind, player position, time of day)
# Instance uniform on a GeometryInstance3D$Crate.set_instance_shader_parameter("tint", Color.RED)# Global uniform (declare it in Project Settings first)func _process(_d: float) -> void: RenderingServer.global_shader_parameter_set( "player_pos", $Player.global_position)
Instance uniforms are the fix for "I changed one enemy's material and all of them flashed", without
duplicate() or Local to Scene.
Varyings, consts & functions
Feature
Syntax / note
Varying
varying vec3 world_pos; written in vertex(), read in fragment() or light()
Interpolation
varying flat int id; (no interpolation), smooth (default)
Constants
const float K = 2.0; (any type except samplers)
Functions
defined before use; in, out, inout parameters; overloads allowed
copy of what was drawn so far; canvas_item and spatial
Transparent-only (3D)
the screen texture in 3D contains opaque geometry only; other transparent objects are missing
Overlapping 2D readers
the copy happens once per canvas; add a BackBufferCopy node between effects that must see each other
Depth texture
3D only; non-linear, reconstruct with INV_PROJECTION_MATRIX as above
Normal-roughness
hint_normal_roughness_texture, Forward+ only
Full-screen post effect
a ColorRect (full rect) in a CanvasLayer with a canvas_item shader reading the screen texture
Parameters from GDScript
@onready var mat := $Sprite2D.material as ShaderMaterialfunc _ready() -> void: mat.set_shader_parameter("amount", 0.5) mat.set_shader_parameter("tex", preload("res://n.png")) print(mat.get_shader_parameter("amount"))func flash() -> void: var t := create_tween() t.tween_property(mat, "shader_parameter/amount", 0.0, 0.25).from(1.0)# 3D mesh: material slotsvar m := $Mesh.get_active_material(0) as ShaderMaterial$Mesh.set_surface_override_material(0, m.duplicate())
Fact
Detail
Name
set_shader_parameter (Godot 3 was set_shader_param)
Property path
"shader_parameter/<name>" works with tweens, AnimationPlayer tracks and set()
Sharing
materials are Resources: every node using the same material sees the change
Per-node options
instance uniforms; Resource > Local to Scene on the material; material.duplicate()
right-click a StandardMaterial3D > Convert to ShaderMaterial
Multi-pass
next_pass on any material draws the mesh again (inverted-hull outlines)
Sorting
render_priority orders transparent materials
Stutter
ubershaders (4.4+) hide first-use compilation; the shader baker (4.5+) precompiles at export
Visual shaders
VisualShader is a node-graph resource for the same shader types. Create it via ShaderMaterial > New
VisualShader. Useful for artists and quick iteration; an Expression node accepts inline shader code, and
VisualShaderNodeCustom scripts (@tool + class_name) add reusable nodes. There is no automatic conversion
to text, so pick one form per effect.
Differences from GLSL
GLSL
Godot shading language
#version, precision headers
shader_type first; precision qualifiers optional
in / out attributes, gl_Position
built-ins: VERTEX, UV, NORMAL, POSITION
out vec4 fragColor / gl_FragColor
COLOR (2D) or ALBEDO + ALPHA (3D)
gl_FragCoord
FRAGCOORD
uniform set with gl.uniform*
uniform with hints and defaults, set with set_shader_parameter
varying (old) / out-in pairs
varying
implicit int to float in some drivers
never: write 1.0, cast with float(i)
iTime (Shadertoy)
TIME
iResolution
1.0 / SCREEN_PIXEL_SIZE (2D) or VIEWPORT_SIZE (3D)
texture origin bottom-left
UV origin top-left, y down (2D)
manual lighting
automatic PBR; light() to customize, unshaded to skip
Porting Shadertoy: put the body in a canvas_itemfragment() on a full-rect ColorRect, replace
fragCoord / iResolution.xy with UV (flip y if needed) and write the result to COLOR.
Performance & debugging
Tip
Why
Visualize intermediates
COLOR = vec4(UV, 0.0, 1.0); or ALBEDO = NORMAL * 0.5 + 0.5; to see what a value holds
Prefer unshaded for effects
skips the lighting cost for sprites, UI and VFX that do not need lights
Watch 2D overdraw
large transparent quads stacked on each other cost fill rate, especially on mobile and Web
Each screen-texture reader copies the screen
share one BackBufferCopy or one post-process pass instead of many readers
discard and DEPTH writes
disable early depth test optimizations; use ALPHA_SCISSOR_THRESHOLD for cutouts in 3D
Uniform branches are cheap
if (mode == 1) on a uniform is coherent across pixels; data-dependent branches are not
Fewer dependent texture reads
reading a texture at coordinates computed from another texture is slower
Compatibility renderer
Web and old GPUs: fewer features (no hint_normal_roughness_texture, no compute shaders) on an OpenGL backend
Pre-warm new materials
show them once during a loading screen; ubershaders (4.4+) and the shader baker (4.5+) reduce first-use hitches
Profile
Debugger > Visual Profiler for GPU time per pass; Monitors for draw calls
Recipes
Flash white on hit
Use for damage feedback on sprites; tween amount from 1 to 0. Enable Local to Scene on the material, or
use an instance uniform, so only the hit enemy flashes.
For 2D the same idea works in a canvas_itemvertex():
VERTEX.x += sin(TIME + VERTEX.y) * k * (1.0 - UV.y);. Make wind_dir a global uniform (declared in
Project Settings > Shader Globals) to drive every plant from one script.
Full-screen grayscale
Use for pause screens, death states and flashbacks: a ColorRect (full rect, mouse Ignore) in a top
CanvasLayer.