附录 A:Shader 常见错误与排查顺序
本附录是第 1~23 章的随手排查表。先确认错误发生在编译、几何、坐标、函数、法线、颜色中的哪一层,再改动对应代码;同一时间只改一个变量,观察画面是否按预期变化。示例使用 Three.js r174 的 ShaderMaterial,完整运行方式见教程首页。
先按现象缩小范围
- **完全没有画面:**先看浏览器控制台中的 JavaScript、GLSL 编译和 WebGL 错误;确认静态服务器、模块路径、
scene/camera/renderer、gl_Position与片元输出。不要先调颜色参数。 - **画面有,但形状不对:**暂时输出 UV、局部坐标或距离遮罩,确认是取样位置还是函数本身。再检查比例、中心、旋转顺序和参数范围。
- **形状对,明暗不对:**输出法线 RGB,检查法线、光线和视线属于同一坐标空间;位移后确认法线也重新计算。
- **只有细节、轮廓不变:**确认效果运行在顶点还是片元阶段,以及几何体分段是否足够。
- **静态正常,动画或设备上异常:**检查时间单位、采样频率、精度、循环次数与色彩空间,再分辨视觉差异是否来自性能或算法。
1. 编译与 Three.js 输入
| 现象 | 常见原因 | 第一项检查 | 对应章节 |
|---|---|---|---|
| GLSL 报类型不匹配 | 把 1 当 float 使用,或 vec2/vec3 分量数不对 |
用 1.0、vec2(1.0) 等明确类型;检查函数参数签名 |
2 |
| 编译通过但位置消失 | 顶点未正确写 gl_Position,或模型在相机可视范围外 |
暂用最小顶点变换与纯色片元输出 | 1、3 |
uv 在片元中不可用 |
uv 是顶点属性,未通过自定义 varying 传递 |
顶点写 vUv=uv,两阶段声明匹配 |
1 |
| 自定义几何上图案或光照异常 | 几何体缺 uv 或 normal 属性 |
检查 geometry.attributes,不要假设每个几何体都有这些属性 |
1 |
| 改 JS 滑块无效 | shader 与 JavaScript 的 uniform 名称或类型不一致,或忘了更新值 | 同时检查声明、uniforms 对象和事件处理 |
1 |
RawShaderMaterial 一换就报未声明变量 |
原本依赖 ShaderMaterial 的内置声明 |
明确自行声明需要的属性与矩阵,核对实际 GLSL 版本 | 1 |
本教程的普通 ShaderMaterial 示例源码使用 attribute、varying 和 gl_FragColor 的入门写法;Three.js r174 在编译时处理兼容定义。直接把片段搬进别的材质或独立 GLSL 环境,必须重新核对版本、输入输出声明及内置变量。控制台给出的错误行号可能指向 Three.js 展开后的 shader,而不一定与示例源码一一对应。
排查坐标时可暂时把片元结果改为下面的颜色。它只验证 vUv,不验证你的纹理、距离场或光照:
// 片元阶段;vUv 来自顶点阶段的 UV 插值。
gl_FragColor = vec4( vUv, 0.0, 1.0 );
#include <colorspace_fragment>
若看到左下暗、右上偏黄的渐变,UV 通路基本成立;若是纯色或全黑,先检查几何属性、varying 与实际被渲染的材质。
2. 坐标、比例与基础函数
| 现象 | 常见原因 | 第一项检查 | 对应章节 |
|---|---|---|---|
| 圆变成椭圆 | 在非正方形平面或画布上直接用未修正的 UV 距离 | 分清画布、平面、图片三个比例;看距离在哪套单位中计算 | 7 |
| 图片被拉伸 | 纹理本身的比例与显示框不同 | 分别检查图片尺寸、目标平面尺寸与裁剪/适配规则 | 7、11 |
step 边界反了 |
参数顺序弄反,或把“内侧为 1”当成“外侧为 1” | 把关键输入在阈值两侧各代入一个数 | 4 |
smoothstep 出现意外过渡 |
两个边界顺序错误或相等;过渡宽度和当前坐标单位不匹配 | 保证 edge0<edge1,先用常数测试遮罩 |
5 |
| 重复图案边缘断裂 | 在单格边界直接对 fract 后坐标求导,或格子宽高不是同一物理长度 |
先输出 fract(uv*tiles),再单独检查抗锯齿与格数 |
6、23 |
| 负坐标处重复错位 | 误把 fract(x) 当成“保留整数后的余数”,或混用 mod 的参数/符号规则 |
用 x=-0.2 手算:fract(x)=0.8 |
6 |
| 旋转中心跑偏 | 先旋转全局坐标再减中心,或混淆矩阵列向量约定 | 先减目标中心,再乘逆向旋转矩阵 | 9 |
| 波浪方向或速度与预想相反 | sin(kx-ωt) 与 sin(kx+ωt) 的传播方向不同;角度未转弧度 |
暂时固定空间位置看时间变化,再固定时间看空间变化 | 10 |
比例问题不能只靠“看起来差不多”解决。先标记取样坐标的单位:UV 无单位;(uv-0.5)*vec2(1.5,1.0) 以 3:2 平面的高度为长度单位;纹理采样坐标还要考虑图片自身宽高。改变 CSS 的画布宽度,并不自动改变平面几何或图片比例。
3. Noise、分段与动画
| 现象 | 常见原因 | 第一项检查 | 对应章节 |
|---|---|---|---|
| 顶点 noise 位移没有形状 | 几何分段太少,只有几个顶点被取样 | 查看 PlaneGeometry 的 widthSegments/heightSegments |
15、23 |
| 多层 noise 整体越来越亮 | 累加层数后忘记除以权重和 | 单独输出每层和归一化后的值 | 14 |
| 高频 noise 闪烁或格子状 | 频率超过顶点或像素采样能力,或差分间距与频率不匹配 | 降低最高频/层数,再提高必要的几何或像素采样密度 | 14、17 |
| 不同设备的 noise 细节不同 | 浮点精度、三角函数 hash 或高频累积误差 | 确认输入/输出范围与精度,避免以逐像素完全一致作为正确标准 | 13、17 |
| 暂停后画面还动 | JS 仍在增加 uTime,或另一个公式直接使用真实时钟 |
统一通过一个模拟时间更新所有动画 uniform | 10、23 |
片元图案只决定颜色时,低分段平面也可以有细线;顶点位移要靠足够密的网格才能改变轮廓。把分段调高会增加顶点成本,却不会自动让片元 noise 的跨设备精度变好。性能问题先确认瓶颈在顶点数量、每片元的 noise 层数,还是连续重绘。
4. 法线、光照与色彩
| 现象 | 常见原因 | 第一项检查 | 对应章节 |
|---|---|---|---|
| 表面起伏了,明暗仍像平面 | 位移后继续使用原 normal |
输出修正法线 RGB,与原法线对照 | 15 |
| 法线方向随相机或模型转动错乱 | 局部、观察、世界空间混用 | 标注每个向量的空间;用 normalMatrix 或对应变换统一空间 |
15、16 |
| 细节明暗有,轮廓仍平 | 只在片元中扰动法线,没有顶点位移 | 看网格轮廓和深度,决定是否需要真实几何 | 16 |
| 自定义颜色偏灰或过亮 | 线性 RGB 与 sRGB 输出/纹理设置不一致 | 检查 renderer.outputColorSpace、颜色纹理设置和最终颜色转换 |
3、11 |
| 高光位置不跟波峰走 | 高度函数与法线斜率公式不是同一表面,或法线插值过粗 | 用同一个高度函数求斜率,再查看法线 RGB | 15、23 |
normalMatrix 把局部法线转换到观察空间。如果你的光方向在世界空间,先把它也转换到观察空间,或者统一把法线转到世界空间;不能只把数值代入 dot 就期待方向正确。法线 RGB 是方向调试图,不是物体表面真实颜色。
5. SDF、光晕与 Ray Marching
| 现象 | 常见原因 | 第一项检查 | 对应章节 |
|---|---|---|---|
| SDF 内外反了 | 混淆负内正外约定,或差集少了负号 | 在形状中心、边界、外部各代入一点 | 18、20 |
| 矩形角外距离太短 | 把两个正分量只取 max,未算角点的欧氏距离 |
看角外等距线是否为圆弧 | 19 |
| 并集填充对、等距线怪 | 硬组合在隐藏边界附近未保持精确内部距离 | 同时画原形状和组合零边界,不把 min/max 当处处精确距离 |
20 |
| 缩放后光晕宽度异常 | 查询点除以比例后,距离没乘回正的等比系数 | 用等距线比较缩放前后距离单位 | 20、23 |
| 阴影反向移动或盖住本体 | 查询点偏移号反了,或合成顺序不对 | 查 p-offset 和“背景→阴影→本体”的顺序 |
21 |
| Ray Marching 缺面或提前命中 | 场值高估安全距离、步数不足、命中阈值过大 | 从单个精确球开始,分别看步数与阈值 | 22 |
| 虚拟 SDF 物体不能遮挡 Three.js 网格 | 全屏片元颜色没有自动写入虚拟表面深度 | 区分平面网格的深度与 ray march 命中距离 | 22 |
fwidth(d) 只根据邻近片元估计屏幕上一像素内的距离变化,用它画平滑边缘不会修复错误的 SDF 数值。SDF 的符号、零边界、距离幅值应分别验证。Ray Marching 还必须确认距离可以安全作为步长;一个只保证零边界正确的函数不一定适合直接向前跳同样长度。
最后核对输出与资源
gl_FragColor.a=1.0表示不透明。SDF 中画出的“孔”如果仍显示背景色,可能只是 shader 自己画的背景;真正透明还涉及材质transparent、混合和深度设置。见第 21 章。- 本教程的网页示例从仓库根目录的静态服务器加载
build/three.module.js。直接双击本地 HTML 用file://打开时,ES 模块加载可能失败。见运行说明。 - 若画面缩放后模糊,分别检查 CSS 显示尺寸、
renderer.setSize、设备像素比和图案自身的抗锯齿。提高像素比会增加每帧片元数,不会增加几何分段。 - 若想把示例截图或录制成可分享素材,先固定输出画布尺寸、时间点和参数,再检查目标设备的颜色表现与性能。不同 GPU 对程序化 noise 的逐像素结果可能略有差异。