第 11 课:性能基础与测量
先定义要测什么
Shader 性能不由代码行数决定。一个像素经历多少纹理读取、循环迭代和分支,屏幕有多少像素需要着色,网格有多少顶点,都会影响总成本;浏览器的 JS、布局、显示同步也会影响帧率。本课各题固定平面、相机和材质,只改变片元 Shader 的一处写法,并在页面展示平均帧间隔、估算 FPS、draw call、画布实际像素数与设备的片元 highp 精度信息。
页面的“平均帧间隔”来自连续 requestAnimationFrame 的 dt,它包含浏览器调度与垂直同步,不是 GPU 执行时间。稳定 60 FPS 时,即使 Shader 更快,两种写法也可能都显示约 16.7 ms。正式判断 GPU 瓶颈可用 Spector.js、浏览器性能面板和可用时的 GPU timer query,仍应在目标设备复测。
固定测试条件
比较 A/B 时固定设备、浏览器、视口、像素比、画布尺寸、场景和其他标签页负载。先预热几秒再各记录至少十秒,多次交替测量,并记录中位数或范围。画布宽高翻倍会让片元数约变成四倍;因此把窗口或设备像素比改了以后,前后 FPS 不能直接归因于某个公式。renderer.info.render.calls 是 draw call 数,不代表每次绘制有多少片元,也不代表耗时。
第 1 题用恒等式证明嵌套 mix(0,mix(0,mix(0,1,x),x),x) 与 pow(x,3) 都产生 x³。pow 写法更短,但某些 GPU 上多项式乘法可能更快。第 2、4 题用 mix、step 替换简单条件输出;它们减少源代码中的分支,却不保证机器码更快。若分支在整组片元里高度一致,原写法也可能高效;若分支两侧都要昂贵地计算,mix 甚至可能让成本增加。
精度与循环
mediump 通常精度和范围低于 highp,某些移动 GPU 可以从中受益,但现代桌面驱动可能按相同精度执行。第 3 题只把小范围相位变量改为 mediump,故意不把大世界坐标或不断增长的时间放进去。画面相同只能证明当前设备和输入范围下没有可见差异;不能推出所有平台安全。
FBM 把不同频率、不同幅度的噪声相加。第 5 题的 3 层保留大轮廓,6 层增加细节,同时增加噪声求值次数。两个版本通过编译时常量控制循环次数;修改层数后需要重新编译材质。第 7 题让 4 次和 16 次采样计算同一类型的图案,训练在固定条件下记录画面与时间的取舍。60 FPS 是某些设备、分辨率和场景的目标,不是教程对所有机器的保证。
数据流与常规实践
JS 创建两个 ShaderMaterial 版本并切换它们,复用同一个几何体与 uniform。顶点 position、uv 进入顶点 Shader,UV 在三角形内部插值,片元 Shader 计算图案。JS 每帧更新 uTime 并统计帧间隔,公共运行时负责渲染和窗口缩放。切换材质会触发首次编译,所以切换后的第一段读数不应纳入稳态比较。
第 6 题把噪声值和阈值遮罩直接画成灰度,检查中间变量的范围;详细的 Shader 编译报错、全黑、闪烁和颜色空间排查将在第 13 课展开。日常优化先找到瓶颈:顶点数高时考虑 LOD 或实例化,片元成本高时降低 overdraw、采样数或分辨率,纹理占用高时选合适尺寸与压缩格式。应记录优化前后的目标设备数据,并接受必要时的画质取舍。
练习
| 难度 | 题目 | 可观察验收 |
|---|---|---|
| ⭐ | pow 与嵌套 mix 的同形曲线 | 两版本曲线一致,记录帧间隔 |
| ⭐ | mix 与 if/else 颜色选择 | 左右分色一致 |
| ⭐⭐ | mediump 与 highp 的安全范围 | 当前设备差异可记录 |
| ⭐⭐ | step 与 if 的二值遮罩 | 圆盘边界一致 |
| ⭐⭐⭐ | FBM 三层与六层的取舍 | 细节差异和帧间隔记录 |
| ⭐⭐⭐ | 把中间变量画成灰度 | 三种输出模式可切换 |
| ⭐⭐⭐⭐ | 固定场景下的帧时间对比 | 报告条件、画面与测量值 |