godot-shader
Skill2 starsChanged 4 months agoMITView on GitHub
You MUST consult this skill when writing or reviewing `.gdshader` files, GDSL shader code, visual effects, lighting models, or post-processing in Godot. Also trigger on Shadertoy porting, vertex displacement, procedural shapes in shaders, screen-space effects, normal maps, transparency sorting, ray marching, or particle shader logic. NOT for GDScript game logic, scene architecture, or node patterns (see godot). NOT for C# or visual shaders.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- fetasty/godot-skillstheir own version, first seen May 16, 2026
- alexmeckes/godot-claude-skillstheir own version, first seen Jan 17, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- alexmeckes/godot-claude-skills★ 35same name
- fetasty/godot-skills★ 29same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository67 other files it gives its agents.
- AGENTS.mdAGENTS.md
- agent-architectureSkill
- agent-evaluationSkill
- agent-memorySkill
- agent-post-trainingSkill
- agent-self-evolutionSkill
- agent-tool-designSkill
- api-designSkill
- application-architectureSkill
- brainstormSkill
- code-reviewerSkill
- coding-agent-designSkill
- concurrency-designSkill
- context-engineeringSkill
- dockerSkill
- game-audioSkill
- game-designSkill
- game-narrativeSkill
- game-patternsSkill
- game-performanceSkill
- game-renderingSkill
- game-visualsSkill
- git-commitSkill
- git-prSkill
- godotSkill
- gpu-rendering-architectureSkill
- grafanaSkill
- helmSkill
- homelab-monitoringSkill
- k3sSkill
- k8s-networkingSkill
- k8s-operationsSkill
- k8s-storageSkill
- k8s-workloadsSkill
- knowledge-baseSkill
- level-designSkill
- llm-inference-servingSkill
- loggingSkill
- logqlSkill
- love2d-fennelSkill
- love2dSkill
- ml-platform-architectureSkill
- ml-post-trainingSkill
- multi-agent-collaborationSkill
- nix-dendriticSkill
- nix-packagingSkill
- nixSkill
- odin-designSkill
- odin-gamedevSkill
- orchestratorSkill
- physical-ai-systemsSkill
- plannerSkill
- prd-to-storiesSkill
- prd-writingSkill
- promqlSkill
- pr-reviewerSkill
- python-designSkill
- rag-designSkill
- ray-data-pipelinesSkill
- ray-kubernetes-infrastructureSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 106
Matrix multiplication order: Always MATRIX * vector, never vector * MATRIX.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Godot 4.x Shader Development
Godot 4.x (baseline: 4.6) GDSL shaders. Three shader types: spatial (3D), canvas_item (2D/UI), particles (GPU particles). This skill is for a hobbyist developer; prefer built-in variables and render modes over custom implementations where they exist.
References
| Reference | When to read it |
references/gdsl-reference.md | Full built-in variable tables, all render modes, uniform hints, varyings, ShaderInclude, particle lifecycle |
references/lighting.md | Lambertian, Blinn-Phong, Fresnel, anisotropic, hemispheric, normal maps — complete implementations |
references/procedural-and-vertex.md | SDF shapes, procedural animation, vertex displacement, 2D/3D rotations, quaternions |
references/vfx-and-postprocessing.md | Post-processing setup, Shadertoy porting, transparency/depth, ray marching, stencil buffer |
Shader Types
| Type | Processor Functions | Use For |
spatial | vertex(), fragment(), light() | 3D meshes — MeshInstance3D, terrain, particles mesh |
canvas_item | vertex(), fragment(), light() | 2D sprites, UI, TextureRect, Sprite2D, post-processing |
particles | start(), process() | GPU particle simulation — GPUParticles2D / GPUParticles3D only |
light() note: Defining light() overrides Godot's entire lighting pipeline. You must compute diffuse, specular, and any rim/fresnel manually. Built-in fragment() outputs like RIM and RIM_TINT are silently ignored when light() is defined.
Particles note: start() runs once on spawn; process() runs every frame. TRANSFORM, COLOR, CUSTOM, and USERDATA1–6 persist across frames — particle state accumulates naturally.
GDSL Essentials
Most-used built-ins across all shader types. For the full tables, see gdsl-reference.md.
Spatial — fragment() outputs (write to these)
| Variable | Type | Description |
ALBEDO | vec3 | Base color (linear space). Never assign gamma-encoded values. |
ALPHA | float | Transparency [0,1]. Writing this triggers the transparent pipeline. |
EMISSION | vec3 | Emissive color (additive, unaffected by lighting) |
METALLIC | float | Metallic [0,1] |
ROUGHNESS | float | Roughness [0,1] |
NORMAL_MAP | vec3 | Assign raw [0,1] texture sample — Godot remaps to [-1,1] internally. Do NOT remap first. |
NORMAL | vec3 | Override surface normal directly (view space) |
Spatial — inputs available in fragment()
| Variable | Type | Description |
UV | vec2 | Primary texture coordinates |
UV2 | vec2 | Secondary texture coordinates |
COLOR | vec4 | Vertex color (from vertex() or mesh data) |
NORMAL | vec3 | Surface normal (view space) |
VERTEX | vec3 | Fragment position (view space) |
VIEW | vec3 | Normalized fragment→camera direction (view space) |
FRAGCOORD | vec4 | Pixel position; .xy = screen coords, .z = depth [0,1] |
SCREEN_UV | vec2 | Normalized screen UV for this pixel |
TIME | float | Seconds since engine start (resets at 3600s) |
Spatial — vertex() key built-ins
| Variable | Type | Description |
VERTEX | vec3 | Position in model space (write to displace geometry) |
NORMAL | vec3 | Normal in model space (update after displacing VERTEX) |
UV | vec2 | Primary UV (writable — modify to scroll/transform) |
Canvas Item — fragment() key built-ins
| Variable | Type | Description |
COLOR | vec4 | Output color (read vertex color, write final pixel color) |
TEXTURE | sampler2D | Main sprite/rect texture |
TEXTURE_PIXEL_SIZE | vec2 | 1.0 / texture_size |
SCREEN_UV | vec2 | Normalized screen UV |
SCREEN_PIXEL_SIZE | vec2 | vec2(1/width, 1/height) of screen |
Coordinate Spaces
Object space → [MODEL_MATRIX] → World space → [VIEW_MATRIX] → View space → [PROJECTION_MATRIX] → Clip space → [÷W] → NDC → Screen| Space | Where you see it | Notes |
| Object/Model | vertex(): VERTEX, NORMAL, TANGENT | Default in vertex() unless world_vertex_coords render mode |
| World | MODEL_MATRIX * vec4(VERTEX, 1.0) | Use W=1 for positions, W=0 for directions |
| View | fragment(): VERTEX, NORMAL, TANGENT, VIEW | After auto-transform; camera is at origin |
| Clip | Write to POSITION in vertex() | Overrides auto-projection |
| Screen | FRAGCOORD.xy, SCREEN_UV | Pixel coords or normalized [0,1] |
Key rule: light() receives NORMAL in view space. To get world-space normal: (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz
Matrix multiplication order: Always MATRIX * vector, never vector * MATRIX. These produce different results for non-symmetric matrices.
Example Shaders
1. Spatial — Lambert Diffuse + Blinn-Phong Specular
Custom lighting in light(). Reads albedo texture, computes diffuse and specular manually.
shader_type spatial;
render_mode ambient_light_disabled; // isolate custom lighting
uniform sampler2D _MainTex : source_color;
uniform float _Shininess : hint_range(1.0, 256.0, 1.0) = 64.0;
// Helper functions — place in a .gdshaderinc for reuse
float lambert(vec3 n, vec3 l, float attenuation) {
return max(0.0, dot(n, l)) * attenuation;
}
float blinn_phong(vec3 n, vec3 l, vec3 v, float shininess) {
vec3 h = normalize(l + v); // halfway vector
float s = pow(max(0.0, dot(n, h)), shininess);
s *= float(dot(n, l) > 0.0); // no specular in shadow
return s;
}
void fragment() {
ALBEDO = texture(_MainTex, UV).rgb;
ROUGHNESS = 0.5;
}
void light() {
// light() runs once per light per fragment, after fragment()
// DIFFUSE_LIGHT and SPECULAR_LIGHT accumulate across lights — use +=
float d = lambert(NORMAL, LIGHT, ATTENUATION);
float s = blinn_phong(NORMAL, LIGHT, VIEW, _Shininess);
DIFFUSE_LIGHT += vec3(d) * ALBEDO; // ALBEDO is readable in light()
SPECULAR_LIGHT += vec3(s);
}2. Canvas Item — Scrolling UV Distortion
2D post-processing effect: reads the screen texture and applies a time-based UV distortion.
shader_type canvas_item;
// hint_screen_texture: reads the rendered framebuffer
// repeat_disable: clamp to [0,1] — no tiling artifacts at screen edges
// filter_linear: smooth sampling for distortion effects
uniform sampler2D _ScreenTexture : hint_screen_texture, repeat_disable, filter_linear;
uniform float _DistortStrength : hint_range(0.0, 0.05, 0.001) = 0.01;
uniform float _ScrollSpeed : hint_range(0.0, 2.0, 0.1) = 0.5;
void fragment() {
// Animate distortion offset with TIME
vec2 offset = vec2(
sin(SCREEN_UV.y * 20.0 + TIME * _ScrollSpeed) * _DistortStrength,
cos(SCREEN_UV.x * 20.0 + TIME * _ScrollSpeed) * _DistortStrength
);
// Sample screen at distorted UV
vec4 screen_color = texture(_ScreenTexture, SCREEN_UV + offset);
COLOR = screen_color;
}Setup: Attach to a ColorRect (Anchor: Full Rect) inside a CanvasLayer. Effect only appears at runtime, not in the editor viewport.
3. Particles — Gravity + Fade Out
Minimal particle shader: spawns particles at emitter position, applies gravity, fades alpha over lifetime.
shader_type particles;
void start() {
// RESTART_* flags are true when particle first spawns
if (RESTART_POSITION) {
// Place particle at emitter world position
TRANSFORM[3].xyz = EMISSION_TRANSFORM[3].xyz;
}
if (RESTART_VELOCITY) {
// Spread particles in a cone using golden angle
float angle = float(NUMBER) * 2.399963;
VELOCITY = vec3(cos(angle) * 0.5, 2.0, sin(angle) * 0.5);
}
if (RESTART_CUSTOM) {
CUSTOM.y = 0.0; // initialize lifetime phase (0=just spawned, 1=dead)
}
}
void process() {
// Manually track lifetime phase
// Guard against LIFETIME == 0 to avoid NaN
CUSTOM.y = clamp(CUSTOM.y + DELTA / max(LIFETIME, 1e-6), 0.0, 1.0);
float age = CUSTOM.y; // CUSTOM persists across frames
// Apply gravity and integrate position
VELOCITY.y -= 9.8 * DELTA;
TRANSFORM[3].xyz += VELOCITY * DELTA; // TRANSFORM persists — position accumulates
// Fade alpha over lifetime
COLOR.a = 1.0 - age;
// Deactivate when lifetime expires
if (age >= 1.0) {
ACTIVE = false;
}
}Common Patterns
Read the screen texture (post-processing / refraction)
// Declare at top of shader
uniform sampler2D _ScreenTexture : hint_screen_texture, repeat_disable, filter_linear;
void fragment() {
vec4 screen = texture(_ScreenTexture, SCREEN_UV);
// For refraction: offset SCREEN_UV by NORMAL.xy before sampling
// Caveat: only captures objects rendered before this draw call
}Pass data from vertex() to fragment()
// Declare outside all functions
varying vec3 vertex_world; // suffix _ws = world space convention
varying vec2 custom_uv;
void vertex() {
vertex_world = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
custom_uv = UV * 2.0 - 1.0; // remap to [-1,1]
}
void fragment() {
// vertex_world and custom_uv are available here, interpolated across the triangle
// Restriction: varyings cannot be assigned in custom functions or in light()
}Make a property editable in the Inspector
// Scalar with slider
uniform float _Speed : hint_range(0.0, 10.0, 0.1) = 1.0;
// Color picker (applies sRGB→linear conversion automatically)
uniform vec4 _Tint : source_color = vec4(1.0);
// Texture with normal map hint (triggers correct reimport in editor)
uniform sampler2D _NormalMap : hint_normal;
// Set from GDScript at runtime:
// material.set_shader_parameter("_Speed", 2.5)Sample a texture in vertex() (heightmap displacement)
uniform sampler2D _HeightMap;
uniform float _HeightScale = 1.0;
void vertex() {
// Must use textureLod — implicit derivatives are unavailable in vertex()
float height = textureLod(_HeightMap, UV, 0.0).r;
VERTEX.y += height * _HeightScale;
// Update NORMAL after displacing VERTEX or lighting will be wrong
}Common Pitfalls
Forgetting render_mode ambient_light_disabled when isolating custom lighting. If you implement all lighting in light() but omit render_mode ambient_light_disabled, Godot's ambient and environment lighting still accumulates on top of your result. Add render_mode ambient_light_disabled to isolate custom lighting, or accept the engine ambient as a base layer. Note: render_mode unshaded is different — it disables the entire lighting pipeline (no light() runs at all), which is for fully self-lit materials like particles or UI overlays.
Writing to ALBEDO inside light(). ALBEDO is read-only in light() — it reflects the value set in fragment(). Write diffuse contributions to DIFFUSE_LIGHT and specular to SPECULAR_LIGHT. Using = instead of += on these also breaks multi-light scenes — always accumulate.
Wrong matrix multiplication order. GLSL uses column-vector convention: MATRIX * vector, not vector * MATRIX. These are transposes of each other and produce incorrect results for non-symmetric matrices. Applies to all transforms: MODEL_MATRIX * vec4(VERTEX, 1.0), EULER_ZXY * VERTEX, etc.
Assigning remapped values to NORMAL_MAP. NORMAL_MAP expects a raw [0,1] texture sample — Godot remaps to [-1,1] internally. If you remap first (* 2.0 - 1.0) and then assign, the normal will be double-remapped and incorrect. Assign the raw texture(...).rgb directly.
Using texture() in vertex(). Implicit mip-level derivatives are unavailable in vertex(), so texture() produces undefined results. Always use textureLod(tex, uv, 0.0) for deterministic sampling in vertex().
pow(negative, float) is undefined in GLSL. This surfaces in SDF shapes and lighting. For SDF: use abs() before pow() — pow(abs(u), 4.0). For lighting: guard with max(value, 0.0) before any pow() call.
hint_screen_texture feedback loops. The screen texture only captures objects rendered before this draw call. Sampling it while rendering to the same viewport creates undefined behavior. For transparent objects that need screen sampling, add render_mode depth_draw_always to ensure correct draw ordering.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

