Files
skills/shader-dev/techniques/webgl-pitfalls.md
shihao 6487becf60 Initial commit: add all skills files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:52:49 +08:00

5.4 KiB

WebGL2 Pitfalls & Common Errors

Use Cases

  • Avoiding common GLSL compilation errors when generating standalone WebGL2 shader pages
  • Debugging shader compilation failures
  • Ensuring shader templates from ShaderToy work correctly in WebGL2

Critical WebGL2 Rules

1. Fragment Coordinate — Use gl_FragCoord.xy

ERROR: 'fragCoord' : undeclared identifier

In WebGL2 fragment shaders, fragCoord is not a built-in variable. Use gl_FragCoord.xy instead.

// WRONG
void main() {
    vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
}

// CORRECT
void main() {
    vec2 uv = (2.0 * gl_FragCoord.xy - iResolution.xy) / iResolution.y;
}

2. Shadertoy mainImage — Must Wrap in main()

ERROR: '' : Missing main()

If your fragment shader uses void mainImage(out vec4, in vec2), you must provide a main() wrapper.

// WRONG — only defines mainImage but no main()
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // shader code...
    fragColor = vec4(col, 1.0);
}

// CORRECT
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // shader code...
    fragColor = vec4(col, 1.0);
}

void main() {
    mainImage(fragColor, gl_FragCoord.xy);
}

3. Function Declaration Order — Declare Before Use

ERROR: 'functionName' : no matching overloaded function found

GLSL requires functions to be declared before they are used. Forward declarations or reordering is needed.

// WRONG — getAtmosphere() calls getSunDirection() which is defined after
vec3 getAtmosphere(vec3 dir) {
    return extra_cheap_atmosphere(dir, getSunDirection()) * 0.5;  // Error!
}
vec3 getSunDirection() {
    return normalize(vec3(-0.5, 0.8, -0.6));
}

// CORRECT — reorder functions
vec3 getSunDirection() {  // Define first
    return normalize(vec3(-0.5, 0.8, -0.6));
}
vec3 getAtmosphere(vec3 dir) {  // Now can call getSunDirection()
    return extra_cheap_atmosphere(dir, getSunDirection()) * 0.5;
}

4. Macro Limitations — #define Cannot Use Functions

ERROR: Various compilation errors with #define macros

Macros are text substitution and cannot call functions or use parentheses in the same way as C++.

// WRONG
#define SUN_DIR normalize(vec3(0.8, 0.4, -0.6))
#define WORLD_TIME (iTime * speed())

// CORRECT — use const
const vec3 SUN_DIR = vec3(0.756, 0.378, -0.567);  // Pre-computed normalized value
const float WORLD_TIME = 1.0;

5. Vector Component Access — Terrain Functions

ERROR: 'terrainM' : no matching overloaded function found

When passing positions to terrain functions that expect vec2, extract the XZ components properly.

// WRONG — terrainM expects vec2, but passing vec3
float calcAO(vec3 pos, vec3 nor) {
    float d = terrainM(pos + h * nor);  // Error: pos + h*nor is vec3
    ...
}

// CORRECT — extract xz components
float calcAO(vec3 pos, vec3 nor) {
    float d = terrainM(pos.xz + h * nor.xz);
    ...
}

6. Loop Index — Use Runtime Constants

ERROR: Loop index must be a runtime expression

GLSL ES requires loop indices to be determinable at runtime, not compile-time constants in some contexts.

// WRONG — AA is a #define constant
for (int i = 0; i < AA; i++) { ... }

// CORRECT — use a runtime-safe approach
for (int i = 0; i < 4; i++) { ... }  // Or pass as uniform

7. Uniform Usage — Avoid Unused Uniforms

ERROR: Uniform optimized away causes gl.getUniformLocation() to return null

If a uniform is declared but not used, the compiler may optimize it out.

// WRONG — iTime declared but used in a conditional that might be false
uniform float iTime;
if (false) { x = iTime; }  // iTime optimized away

// CORRECT — always use the uniform in a way the compiler can't optimize out
uniform float iTime;
float t = iTime * 0.0;  // Always use iTime somehow
if (someCondition) { x = t; }

Complete WebGL2 Adaptation Checklist

When generating standalone HTML pages:

  1. Shader Version: #version 300 es must be the very first line
  2. Fragment Output: Declare out vec4 fragColor;
  3. Entry Point: Wrap mainImage() in void main() that calls mainImage(fragColor, gl_FragCoord.xy)
  4. Fragment Coord: Use gl_FragCoord.xy not fragCoord
  5. Preprocessor: Don't use functions in #define macros
  6. Function Order: Declare functions before they are used, or use forward declarations
  7. Texture: Use texture() not texture2D()
  8. Attributes: attributein, varyingin/out

Common Error Messages Reference

Error Message Likely Cause Solution
'fragCoord' : undeclared identifier Using fragCoord instead of gl_FragCoord.xy Replace with gl_FragCoord.xy
'' : Missing main() No main() function defined Add wrapper void main() { mainImage(...); }
'function' : no matching overloaded function Wrong argument types or function order Check parameter types, reorder functions
return' : function return is not matching Return type mismatch Verify return expression matches declared return type
#version must be first Leading whitespace in shader source Use .trim() when extracting from script tags
Uniform null from getUniformLocation Uniform optimized away Ensure uniform is actually used in shader code

Further Reading

See reference/webgl-pitfalls.md for additional debugging techniques.