Skillshub defold-shaders-editing
Creates and edits Defold shader files (.vp, .fp, .glsl). Use when asked to create, modify, or configure any Defold vertex shader, fragment shader, or GLSL include file.
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/indiesoftby/defold-agent-config/defold-shaders-editing" ~/.claude/skills/comeonoliver-skillshub-defold-shaders-editing && rm -rf "$T"
skills/indiesoftby/defold-agent-config/defold-shaders-editing/SKILL.mdEditing Defold Shaders
Creates and edits Defold shader files: vertex programs (
.vp), fragment programs (.fp), and GLSL include snippets (.glsl).
When to use
This skill covers GLSL shader files used in Defold's rendering pipeline. It does NOT cover
.material files — for those, use the defold-proto-file-editing skill (see references/material.md).
Shader pipeline and GLSL version
Defold supports two shader pipelines:
- Legacy pipeline — shaders written in OpenGL ES 2.0 compatible GLSL (no
directive). Deprecated since Defold 1.9.2.#version - Modern pipeline — shaders written in SPIR-V compatible GLSL with
or higher. This is the current standard.#version 140
Always write shaders using
(OpenGL 3.1) at the top of the file. This directive selects the modern pipeline during the build process. If no #version 140
#version is found, Defold falls back to the legacy pipeline.
#version 140
Cross-compilation and platform targets
Defold compiles shaders for multiple graphics APIs from a single GLSL source:
- OpenGL 3.x / 4.x (desktop: Windows, macOS, Linux)
- OpenGL ES 2.0 / 3.0 (mobile: Android, iOS)
- WebGL 1.0 / 2.0 (HTML5)
- SPIR-V (Vulkan on Android, desktop)
- Metal (iOS, macOS — via SPIR-V cross-compilation)
Because of this cross-compilation, not all GLSL features are available everywhere. Some functions (e.g.,
dFdx, dFdy, fwidth) require extensions on ES 2.0 / WebGL 1.0 targets but are built-in on ES 3.0+ / desktop GL. Use #extension and preprocessor guards for conditional features:
#ifdef GL_OES_standard_derivatives #extension GL_OES_standard_derivatives : enable #endif #if !defined(GL_ES) || __VERSION__ >= 300 || defined(GL_OES_standard_derivatives) // Use derivative functions #else // Provide fallback #endif
Key limitations to keep in mind:
- Dynamic loops with variable bounds may not work on ES 2.0 / WebGL 1.0
- Integer operations are limited on ES 2.0
requires ES 3.0+ / WebGL 2.0+sampler2DArray- Storage buffers and compute shaders require Vulkan / Metal
File types
Vertex program (.vp
)
.vpRuns once per vertex. Transforms vertex positions from model/world space to screen space. Outputs
gl_Position and passes data to the fragment shader via out variables.
Fragment program (.fp
)
.fpRuns once per fragment (pixel). Computes the final color. Outputs to a user-defined
out vec4 variable (not gl_FragColor, which is deprecated in #version 140).
GLSL include snippet (.glsl
)
.glslReusable GLSL code included by
.vp or .fp files via #include. Does not run standalone. Use header guards to prevent double-inclusion.
Modern GLSL syntax rules (#version 140)
Attributes → in
inIn vertex shaders, use
in instead of attribute:
in highp vec4 position; in mediump vec2 texcoord0;
Fragment shaders do NOT have vertex attributes.
Varyings → out
/ in
outinIn vertex shaders, use
out instead of varying. In fragment shaders, use in:
// vertex shader out mediump vec2 var_texcoord0; // fragment shader in mediump vec2 var_texcoord0;
Fragment output → out vec4
out vec4Use a declared
out variable instead of the deprecated gl_FragColor:
out vec4 out_fragColor; void main() { out_fragColor = vec4(1.0, 0.0, 0.0, 1.0); }
Uniform blocks
Non-opaque uniforms (matrices, vectors, floats) must be placed in a uniform block. Use
vs_uniforms for vertex shaders and fs_uniforms for fragment shaders (by convention):
uniform vs_uniforms { highp mat4 view_proj; };
Opaque uniforms (samplers, images) remain standalone:
uniform mediump sampler2D texture_sampler;
Members of the uniform block are accessed directly by name (no block prefix):
gl_Position = view_proj * vec4(position.xyz, 1.0);
Texture sampling → texture()
texture()Use
texture() instead of the deprecated texture2D() / texture2DArray():
vec4 color = texture(texture_sampler, var_texcoord0.xy);
Precision qualifiers
Explicit precision (
lowp, mediump, highp) is optional in #version 140 — the pipeline sets precision automatically for platforms that need it. However, you may still use them for clarity or to match existing code style in the project. Follow the convention of surrounding files.
Editor-specific code (EDITOR
define)
EDITORWhen shaders are rendered in the Defold Editor viewport, the preprocessor define
EDITOR is available. Use #ifdef EDITOR to write code that behaves differently in the editor vs the game:
#ifdef EDITOR // Simplified rendering for editor preview out_fragColor = texture(texture_sampler, var_texcoord0.xy); #else // Full rendering with effects for the game out_fragColor = apply_effects(texture(texture_sampler, var_texcoord0.xy)); #endif
Common use cases:
- Disable expensive effects (RGSS, post-processing) in editor for performance
- Show debug/fallback visuals for materials that don't preview well
- Skip features that depend on runtime-only data (e.g., skinning)
Including snippets (#include
)
#includeShader files can include
.glsl snippets using #include with project-relative or file-relative paths:
// Absolute (project-relative) path #include "/main/my-snippet.glsl" // Relative to current file #include "my-snippet.glsl" #include "../shared/utils.glsl"
Rules:
- Only
files can be included.glsl - Paths must be within the project (or library dependencies)
- Absolute paths start with
/
cannot be used inline within a statement#include
Header guards
Use header guards in
.glsl snippets to prevent double-inclusion:
#ifndef MY_SNIPPET_GLSL #define MY_SNIPPET_GLSL // ... snippet code ... #endif // MY_SNIPPET_GLSL
Relationship with materials
Shaders and materials (
.material) are tightly coupled. The material file defines what data the shader receives. For material file editing, use the defold-proto-file-editing skill (reference: references/material.md).
Constants (uniforms)
Constants declared in the material's
vertex_constants / fragment_constants become uniform variables in the shader. Place them inside the uniform block:
| Material constant type | Shader uniform type | Description |
|---|---|---|
| | Combined view × projection matrix |
| | World transform matrix |
| | View (camera) matrix |
| | Projection matrix |
| | World × view matrix |
| | World × view × projection matrix |
| | Normal matrix — . Produces view-space normals, not world-space. |
| | Custom data, mutable via / |
| | Custom matrix data, mutable via |
The
name in the material must match the variable name in the uniform block.
Samplers
Samplers declared in the material's
samplers section become sampler2D uniforms. The sampler name in the material must match the uniform name in the shader:
uniform mediump sampler2D texture_sampler; // Matches samplers { name: "texture_sampler" ... }
For sprites, tilemaps, GUI, and particles — the first
sampler2D is automatically bound to the component's image.
Vertex attributes
Attributes declared in the material's
attributes section (or default attributes provided by the engine) become in variables in the vertex shader.
Default attributes by component type:
| Component | Attributes |
|---|---|
| Sprite | , |
| Tilemap | , |
| GUI node | , , |
| ParticleFX | , , |
| Model | , , |
| Font | , , , , |
Vertex space
The material's
vertex_space setting affects how position data arrives:
(default) — positions are pre-transformed to world space. Used for 2D components (sprites, tilemaps). UseVERTEX_SPACE_WORLD
(orview_proj
) to go directly to screen space.CONSTANT_TYPE_VIEWPROJ
— positions are in local/object space. Used for 3D models. You must transform throughVERTEX_SPACE_LOCAL
in the shader.world → view → projection
Instancing
For instanced rendering (Model components), declare
mtx_world and mtx_normal as in attributes (not uniforms):
in mediump mat4 mtx_world; in mediump mat4 mtx_normal;
The material must have
vertex_space: VERTEX_SPACE_LOCAL. These attributes are automatically configured for per-instance step function.
Normal matrix (mtx_normal
) — view-space vs world-space
mtx_normalThe built-in
CONSTANT_TYPE_NORMAL computes transpose(inverse(view * world)) on the CPU. This produces normals in view-space (camera-space), not world-space.
View-space normals are fine when:
- All lighting calculations are done in view-space
- Camera position is implicitly at origin
(simplifies specular)(0,0,0)
World-space normals are needed for:
- Cubemap reflections, environment mapping
- World-space lighting, world-space effects
To get world-space normals, compute the normal matrix from
mtx_world in the vertex shader using the adjugate matrix trick (cheaper than full inverse() — 3 cross products instead of cofactor expansion):
// transpose(adjugate(M)) for upper-left 3x3 of mat4. // Equivalent to transpose(inverse(M)) up to a uniform scale factor, // which is eliminated by normalize(). mat3 adjoint(mat4 m) { return mat3( cross(m[1].xyz, m[2].xyz), cross(m[2].xyz, m[0].xyz), cross(m[0].xyz, m[1].xyz) ); }
Usage in vertex shader:
var_world_normal = normalize(adjoint(mtx_world) * normal.xyz);
| Goal | Method |
|---|---|
| View-space normals | Use built-in () |
| World-space normals | Compute in shader |
Key rule: Never mix coordinate spaces — if light direction is in world-space, normals must also be in world-space.
Canonical examples
Builtin reference shaders
Use
.deps/builtins/materials/ as reference for standard shaders:
/sprite.vp
— 2D sprite (world space,sprite.fp
,view_proj
)tint
/model.vp
— 3D model with lighting (local space,model.fp
,mtx_worldview
,mtx_normal
)light
— 3D model with instancing (model_instanced.vp
,mtx_world
asmtx_normal
attributes)in
/gui.vp
— GUI nodes (world space,gui.fp
attribute, premultiplied alpha)color
/particlefx.vp
— Particle effectsparticlefx.fp
/tile_map.vp
— Tilemapstile_map.fp
— Skeletal animation include (usesskinning.glsl
for fallback)#ifdef EDITOR
GLSL include snippet (with header guards and extension)
#ifndef MY_UTILS_GLSL #define MY_UTILS_GLSL #ifdef GL_OES_standard_derivatives #extension GL_OES_standard_derivatives : enable #endif // Utility function available on platforms that support derivatives #if !defined(GL_ES) || __VERSION__ >= 300 || defined(GL_OES_standard_derivatives) mediump float edge_smoothing(mediump float dist) { return smoothstep(0.0, fwidth(dist), dist); } #else mediump float edge_smoothing(mediump float dist) { return step(0.0, dist); } #endif #endif // MY_UTILS_GLSL
Workflow
Creating a new shader pair (.vp + .fp)
- Determine the component type (sprite, model, GUI, etc.) to know which attributes and vertex space to use.
- Check the corresponding
file (or plan one) to know which constants, samplers, and attributes the shader will receive. Use the.material
skill for material editing.defold-proto-file-editing - Start both files with
.#version 140 - In the
: declare.vp
attributes,in
varyings, uniform block with constants, and computeout
.gl_Position - In the
: declare.fp
varyings,in
for color output, samplers, uniform block, and compute the output color.out vec4 - Ensure uniform names match the material's constant names exactly.
- Ensure sampler names match the material's sampler names exactly.
Creating a GLSL include snippet
- Create a
file..glsl - Add header guards (
/#ifndef
/#define
).#endif - Add
directives with preprocessor guards if using features not available on all targets.#extension - Write reusable functions or constants.
Editing an existing shader
- Read the current shader file and its corresponding
file..material - Modify only the requested parts.
- Keep the existing code style (precision qualifiers, naming, spacing).
- Ensure any new uniforms are also added to the material file (use
skill).defold-proto-file-editing - Ensure any removed uniforms are also removed from the material file.