跳到主要内容
← 全部 Shader
故意编译失败 · 错误回退演示 程序化生成 原始语言:GLSL ES 3.00 浏览器:GLSL ES 3.00

编译失败回退演示(故意出错的 GLSL)

一个刻意保留语法错误的 GLSL ES 3.00 片元着色器,用来端到端验证错误处理路径:驱动日志必须可见、静态回退必须出现、页面不能留下空白画布。

GLSL ES 3.00 全屏四边形 等待初始化
示意图:着色器编译失败时页面应当显示的区域,包含错误面板与静态回退预览

静态预览:实时渲染需要启用 JavaScript 与支持 WebGL 2 的浏览器。初始化成功后会自动切换为实时画面。

— 0.0 s
运行环境与资源诊断
尚未初始化。
本页目录

这个条目为什么存在

任务书里有一条硬性要求:不允许渲染失败后只留下空白区域。这句话只有在真的触发一次失败时才能被验证,所以仓库里保留了这个「故障演练」条目。

src/shaders/glsl/compile-error-demo.frag 含有两处刻意留下的错误:

  1. 第一行赋值语句缺少分号;
  2. 调用了一个不存在的函数 undefinedHelper()。

因此这个条目的 runtime.status 依然是 live(预览确实会挂载并尝试编译),但额外设置了 runtime.expectedCompileFailure: true。这个标记会让列表页与详情页的徽标显示为「故意编译失败 · 错误回退演示」,而不是宣称有一个可以实时运行的效果。

请不要修复这个文件。tests/shader-error-path.spec.ts 依赖它的失败行为。

期望看到的页面状态

打开本页后,运行时应当依次做到:

  1. checkWebGL2() 通过,状态短暂进入 loading;
  2. assertCompiles() 在把源码交给 three.js 之前,用同一个 WebGL 2 上下文单独编译一次,捕获驱动返回的原始日志;
  3. 抛出 ShaderCompileError,状态变为 error;
  4. 画布被隐藏,静态回退预览显示出来;
  5. 红色错误面板展开,<pre> 中是驱动日志,并且用 annotateShaderError() 在 ERROR: 0:行号 之后附上对应的源码行,方便直接定位;
  6. 已经创建的 three.js WebGLRenderer 被 dispose(),不会留下孤立的上下文。

日志的具体文字由驱动决定,因此断言只检查「非空」和「包含 ERROR 或编译失败关键字」,不去匹配驱动的实现细节。

为什么要在 three.js 之外单独编译一次

three.js 遇到着色器编译错误时只调用 console.error,不会抛出异常,也不会把日志交回调用方。对一个需要向访问者解释失败原因的作品集网站来说这不够用,所以 src/lib/shader-runtime/compileCheck.ts 先用同一个上下文创建一个临时 program 做完整编译 + 链接,读取 getShaderInfoLog / getProgramInfoLog,然后在 finally 中删除临时对象。

这样做的额外好处是:错误信息来自真实驱动,而不是我们自己写的解析器,因此不同 GPU / 不同浏览器上的日志差异会被如实呈现。

源码

浏览器 GLSL 与原始 HLSL 分开存放、分开展示。HLSL 仅作参考,浏览器无法直接执行 Unity URP 的 HLSL。

顶点着色器 fullscreen.vert glsl GLSL ES 3.00 · 浏览器中实际编译 22 行

three.js 会自动在最前面加入 `#version 300 es`,因此源文件本身不包含该指令。

// Fullscreen quad vertex shader (GLSL ES 3.00 / WebGL 2).
//
// NOTE: Three.js injects `#version 300 es` because the material is created as
// `new THREE.RawShaderMaterial({ glslVersion: THREE.GLSL3, ... })`.
// If you paste this file into a raw WebGL 2 context, add `#version 300 es`
// as the very first line and bind the attribute locations yourself.
//
// Built-in uniforms/attributes below are supplied by Three.js but must be
// declared explicitly because RawShaderMaterial adds no prefix code.

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

in vec3 position;
in vec2 uv;

out vec2 vUv;

void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
片元着色器 compile-error-demo.frag glsl GLSL ES 3.00 · 浏览器中实际编译 25 行
// =====================================================================
// 这个文件是【故意写错】的,用于验证错误处理路径,请不要修复它。
//
// INTENTIONALLY BROKEN - this file exists so the compile-failure path can be
// verified end to end (Playwright test `shader-error-path`). It contains a
// missing semicolon and a call to an undeclared function, so the WebGL 2
// driver rejects it and the page must fall back to the static preview and
// print the driver log.
//
// 对应的 Playwright 用例:tests/shader-error-path.spec.ts
// =====================================================================

precision highp float;

in vec2 vUv;

uniform float uTime;

out vec4 fragColor;

void main() {
  vec3 color = vec3(vUv.x, vUv.y, 0.5)
  color += 0.1 * sin(uTime + undefinedHelper(color));
  fragColor = vec4(color, 1.0);
}