Why Your WebGL2 Shader Suddenly Fails to Compile
You've just ported your WebGL1 code to WebGL2, and suddenly your fragment shader refuses to compile. The error message points to an undefined function, but you know the function exists. If you're coming from GLSL ES 1.00, you're likely hitting the "function declaration before use" requirement that's new in version 3.00. This isn't a bug in your driver or WebGL implementation—it's a deliberate change in the language spec.
In GLSL ES 1.00, functions could be defined anywhere in the shader source, and the compiler would resolve them regardless of order. GLSL ES 3.00, which is the shading language for WebGL2, requires that every function must be declared or defined before its first call. This rule applies to all user-defined functions, and it catches many developers off guard during migration.
The Exact Rule: Declaration or Definition Must Precede Call
The GLSL ES 3.00 spec is clear: "Function declarations (prototypes) must appear in the shader before the function is defined or called." This means you have two options:
Option A: Define the function before any call

// Valid: definition comes before use
vec4 computeColor(vec3 normal, vec3 lightDir) {
float diff = max(dot(normal, lightDir), 0.0);
return vec4(diff, diff, diff, 1.0);
}
void main() {
vec3 n = normalize(vNormal);
gl_FragColor = computeColor(n, uLightDir);
}
Option B: Provide a function prototype before any call, then define later
// Valid: prototype before use, definition anywhere
vec4 computeColor(vec3 normal, vec3 lightDir);
void main() {
vec3 n = normalize(vNormal);
gl_FragColor = computeColor(n, uLightDir);
}
vec4 computeColor(vec3 normal, vec3 lightDir) {
float diff = max(dot(normal, lightDir), 0.0);
return vec4(diff, diff, diff, 1.0);
}
The prototype must match the definition exactly—same return type, same parameter types (and names in the prototype are optional but recommended for clarity).
A Concrete Scenario: Porting a Lighting Shader
Let's walk through a realistic case. You have a WebGL1 shader that computes Blinn-Phong lighting with several helper functions:

// GLSL ES 1.00 (WebGL1) - works fine
// No declaration needed
vec3 computeHalfway(vec3 lightDir, vec3 viewDir) {
return normalize(lightDir + viewDir);
}
float computeSpecular(vec3 normal, vec3 halfway) {
return pow(max(dot(normal, halfway), 0.0), 64.0);
}
void main() {
vec3 halfway = computeHalfway(uLightDir, uViewDir);
float spec = computeSpecular(vNormal, halfway);
// ...
}
You change the #version to 300 es and update other syntax (like texture() instead of texture2D()). Now the shader fails to compile with "'computeHalfway': no matching overloaded function found" or similar. The problem: computeHalfway is called in main() but defined after main(). In 1.00 this was fine; in 3.00 it's an error.
Fix: Either move all function definitions before main(), or add prototypes at the top. The prototype approach is cleaner for larger shaders:
#version 300 es
precision highp float;
// Prototypes first
vec3 computeHalfway(vec3 lightDir, vec3 viewDir);
float computeSpecular(vec3 normal, vec3 halfway);
void main() {
// ... now safe
}
// Definitions later
vec3 computeHalfway(...) { ... }
float computeSpecular(...) { ... }
Where Most Developers Get Stuck
The most common failure occurs when you have mutually recursive or complex dependency chains. For example:
// This will cause a compile error regardless of order
float foo(float x) { return bar(x) + 1.0; }
float bar(float x) { return foo(x - 1.0); }
GLSL ES 3.00 does NOT support forward declarations for mutually recursive functions because there is no way to satisfy the "declaration before use" requirement for both functions simultaneously. The language does not support recursion in general (though some implementations may allow limited recursion, the spec forbids it in 3.00). So if you need mutual recursion, you must refactor your algorithm.
Another frequent mistake: using a function before its prototype when the prototype is defined after the first call in the source. The rule applies to the order in the source text, not to any logical order. If you inadvertently place the prototype inside a conditional block or after the first call, the compiler will reject it.
Comparison: GLSL ES 1.00 vs 3.00 Declaration Rules
| Aspect | GLSL ES 1.00 | GLSL ES 3.00 |
|---|---|---|
| Function call order | Any order in source | Declaration/definition must precede call |
| Prototype required? | No | Yes, if defined after use |
| Recursion support | Forbidden | Forbidden (same) |
| Typical error | None for order | "undefined function" or "no matching overload" |
This difference alone is why many shaders break when upgrading to WebGL2. The fix is straightforward once you know the rule, but it's not obvious if you're using older tutorials or code snippets.
Practical Path: A Migration Checklist for Your Shaders
If you're migrating a codebase from WebGL1 to WebGL2, here's a step-by-step checklist to avoid declaration-order issues:
- Change version directive: Replace
#version 100with#version 300 esat the top of every shader. - Identify helper functions: List all user-defined functions that are not
main(). - Reorder or add prototypes: Decide whether to move definitions before
main()(easy for small shaders) or add prototypes at the top (better for larger files). - Use consistent naming: The prototype parameter names don't need to match the definition, but to avoid confusion, keep them the same.
- Compile and test: Use
gl.getShaderInfoLog()to catch any remaining order issues. - Check for recursion: If you have any function that calls another function which in turn calls the first, refactor to remove the cycle.
Failure Scenario: What Happens When You Ignore the Rule
You might be tempted to keep your existing code as-is and hope the driver handles it. Here's what typically happens:
- WebGL2 context creation succeeds because the version directive is recognized.
- Vertex shader compiles fine (maybe you have no functions or they're already in order).
- Fragment shader compilation fails with a cryptic error. The browser's WebGL error messages are not always helpful—you might see "ERROR: 0:10: 'foo' : no matching overloaded function found" even when you clearly have a function named
foodefined later. - The shader program linking fails, and your 3D scene doesn't render anything.
This silent failure is especially dangerous in production because the error is only visible in the JavaScript console, and if you don't check compilation status, the scene appears black or missing.
What This Means for Your Workflow
Understanding this rule is essential not just for fixing errors, but for writing clean, maintainable shader code. The declaration-before-use design aligns GLSL with languages like C and C++, where prototypes are standard practice. It forces you to be explicit about function signatures, which makes your code easier to read and refactor.
For developers transitioning from WebGL1 to WebGL2, the declaration-order difference is one of the most common stumbling blocks. Once you internalize it, however, porting shaders becomes routine.

暂无评论,快来发表你的见解吧