跳到主要内容
3D Playground
WebGL 与着色器第 12 / 12 节

着色器调试:用颜色“打印”数值

进阶WebGL 2

把数值画出来

着色器同时在数百万个像素上运行,没法打断点,也没法打印。最可靠的调试手段,是把你怀疑的那个值直接输出成颜色。关键在于先把它映射到 0~1:

glsl
outColor = vec4(N * 0.5 + 0.5, 1.0);   // 法线从 [-1,1] 映射到 [0,1]
  • 法线视图:红 = +x,绿 = +y,蓝 = +z。朝上的地面是绿色。如果出现颜色突变或全黑,说明法线没有归一化、没有变换,或者方向反了。
  • UV 视图:红 = u,绿 = v,再叠一层棋盘格。拉伸、接缝、UV 翻转一眼就能看出。
  • 线性深度:把视空间深度按 near/far 归一化,近白远黑。注意 gl_FragCoord.z 是非线性的,直接显示几乎是一片白。
  • 热力图:把标量映射成色带,并用醒目的颜色标出越界的值。本例中蓝色表示 < 0,红色表示 > 1。N·L 不做 clamp 时,背光面会立刻变蓝。

NaN 与 Inf

sqrt(-1.0)、0.0 / 0.0、对零向量做 normalize、pow(负数, 非整数) 都会产生 NaN。NaN 会“传染”:任何与它运算的结果都是 NaN,最后表现为黑块、白块或闪烁的像素,而且不同 GPU 的表现还不一样。用 isnan() / isinf() 把它们涂成品红色:

glsl
float risky = sqrt(vWorld.y - 0.3);
bool bad = isnan(risky) || isinf(risky);
c = bad ? vec3(1.0, 0.0, 1.0) : vec3(risky);

修复方法通常是先 clamp 输入:sqrt(max(x, 0.0))、normalize(v + 1e-6)。

精度

precision mediump float 在许多移动 GPU 上只有约 16 位:大约 3 位有效数字,最大值约 65504。症状是带状色阶、远处坐标抖动、时间 uniform 运行久了动画开始卡顿。片元着色器里涉及位置和时间的计算,优先使用 highp。

读懂编译错误

信息日志的格式通常是 ERROR: 0:行号: 描述。注意行号是着色器源码中的行号,而且 three.js 会在前面插入大量前缀代码,所以要以日志里的描述为准。最常见的错误有:漏写分号、float a = 1;(GLSL 不做 int 到 float 的隐式转换)、向量维度不匹配,以及拼错变量名。用面板里的“Compile errors”逐个试一遍,看看控制台怎么报。

核心 API

isnanisinfgl.getShaderInfoLogprecision highpgl_FragCoordTHREE.ShaderMaterialrenderer.getContext

动手试试

  1. 1切到 NaN finder,把 sqrt(vWorld.y - 0.3) 改成 sqrt(max(vWorld.y - 0.3, 0.0)),品红色就消失了。
  2. 2在 depth 视图里调整 near 和 far,看看深度精度如何分布。
  3. 3在顶点着色器里把 vNormal 改成 normal(不乘 modelMatrix),观察旋转时法线视图哪里出错。
运行中