WebGL2 シェーダーが突然コンパイルに失敗する理由
WebGL1 コードを WebGL2 に移植したところ、突然フラグメント シェーダーがコンパイルを拒否しました。エラー メッセージは未定義の関数を示していますが、関数が存在することはわかっています。 GLSL ES 1.00 から使用している場合は、バージョン 3.00 で新たに追加された「使用前の関数宣言」要件に該当する可能性があります。これはドライバーや WebGL 実装のバグではなく、言語仕様の意図的な変更です。
GLSL ES 1.00 では、関数はシェーダ ソース内のどこにでも定義でき、コンパイラは順序に関係なく関数を解決します。 WebGL2 のシェーディング言語である GLSL ES 3.00 では、最初の呼び出し前にすべての関数を宣言または定義する必要があります。このルールはすべてのユーザー定義関数に適用されるため、多くの開発者は移行中に不意を突かれます。
正確なルール: 宣言または定義は呼び出しの前になければなりません
GLSL ES 3.00 仕様は明確です。「関数宣言 (プロトタイプ) は、関数が定義または呼び出される前にシェーダーに表示されなければなりません。」これは、次の 2 つのオプションがあることを意味します。
オプション A: 呼び出しの前に関数を定義します

// 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
// 有効: 使用前のプロトタイプ、どこでも定義
vec4 computeColor(vec3 Normal, vec3 lightDir);
void main() {
vec3 n = 正規化(vNormal);
gl_FragColor = computeColor(n, uLightDir);
}
vec4 computeColor(vec3 Normal, vec3 lightDir) {
float diff = max(dot(normal, lightDir), 0.0);
戻り値 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[[TRPRO] T_0007]]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(...) { ... }
ほとんどの開発者が行き詰まる場所
最も一般的な失敗は、相互に再帰的または複雑な依存関係チェーンがある場合に発生します。たとえば:
// 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 は、相互再帰関数の前方宣言をサポートしていません。これは、両方の関数の「使用前の宣言」要件を同時に満たす方法がないためです。この言語は一般に再帰をサポートしていません (実装によっては限定的な再帰が許可される場合もありますが、3.00 では仕様で禁止されています)。したがって、相互再帰が必要な場合は、アルゴリズムをリファクタリングする必要があります。
もう 1 つのよくある間違いは、ソース内の最初の呼び出し後にプロトタイプが定義されているときに、プロトタイプの前に関数を使用することです。このルールは、論理的な順序ではなく、ソース テキスト内の順序に適用されます。誤ってプロトタイプを条件ブロック内または最初の呼び出しの後に配置すると、コンパイラーはそれを拒否します。
比較: GLSL ES 1.00 と 3.00 の宣言ルール
| 側面 | GLSL ES1.00 | GLSL ES 3.00 |
|---|---|---|
| 関数の呼び出し順序 | ソース内の任意の順序 | 宣言/定義は呼び出しの前に行う必要があります |
| プロトタイプは必要ですか? | いいえ | はい、使用後に定義されている場合 |
| 再帰サポート | 禁止 | 禁断(同) |
| 典型的なエラー | 注文にはなし | 「未定義の関数」または「一致するオーバーロードがありません」 |
この違いだけが、WebGL2 にアップグレードするときに多くのシェーダーが壊れる理由です。ルールがわかれば修正は簡単ですが、古いチュートリアルやコード スニペットを使用している場合は明らかではありません。
実践的な方法: シェーダーの移行チェックリスト
コードベースを WebGL1 から WebGL2 に移行する場合、宣言順序の問題を回避するための段階的なチェックリストを次に示します。
- バージョン ディレクティブの変更: すべてのシェーダーの上部にある
#version 100を#version 300 esに置き換えます。 - ヘルパー関数の特定:
main()ではないすべてのユーザー定義関数をリストします。 - プロトタイプの並べ替えまたは追加: 定義を
main()の前に移動するか (小さなシェーダーの場合は簡単)、プロトタイプを先頭に追加するか (大きなファイルの場合はより良い) を決定します。 - 一貫した名前を使用する: プロトタイプのパラメーター名は定義と一致する必要はありませんが、混乱を避けるために同じにしておきます。
- コンパイルとテスト:
gl.getShaderInfoLog()を使用して、残りの注文の問題を検出します。 - 再帰を確認します: 別の関数を呼び出す関数があり、その関数が最初の関数を呼び出す場合は、リファクタリングして循環を削除します。
失敗シナリオ: ルールを無視するとどうなるか
既存のコードをそのままにして、ドライバーが処理してくれることを期待したいと思うかもしれません。通常は次のようなことが起こります。
- WebGL2 コンテキストの作成は成功します。バージョン ディレクティブが認識されるためです。
- 頂点シェーダーは正常にコンパイルされます (関数がないか、関数がすでに整っている可能性があります)。
- フラグメント シェーダーのコンパイルが不可解なエラーで失敗します。ブラウザの WebGL エラー メッセージは必ずしも役に立つとは限りません。
fooという名前の関数が後で定義されていることが明らかな場合でも、「エラー: 0:10: 'foo' : 一致するオーバーロードされた関数が見つかりません」が表示される場合があります。 - シェーダー プログラムのリンクが失敗します。3D シーンは何もレンダリングされません。
このサイレント エラーは実稼働環境では特に危険です。エラーは JavaScript コンソールでのみ表示され、コンパイル ステータスを確認しないとシーンが黒く表示されるか、表示されないからです。
これがワークフローにとって何を意味するか
このルールを理解することは、エラーを修正するためだけでなく、クリーンで保守可能なシェーダー コードを作成するためにも不可欠です。使用前宣言の設計は、プロトタイプが標準的な慣行である C や C++ などの言語と GLSL を調整します。これにより、関数のシグネチャについて明示的にする必要が生じ、コードが読みやすくなり、リファクタリングが容易になります。
WebGL1 から WebGL2 に移行する開発者にとって、宣言順序の違いは最も一般的な障害の 1 つです。ただし、一旦それを内部化すると、シェーダーの移植は日常的なものになります。

コメントはまだありません