第 12 课:跨平台与跨设备兼容

返回教程目录 · 打开本课练习 · 复制兼容性清单

同一份 Shader 为什么会不同

Shader 的可见结果由输入数据、GLSL 版本、浮点精度、纹理状态、GPU/驱动、浏览器以及输出颜色空间共同决定。排查时先记录可重复的差异:设备与浏览器、WebGL 版本、画布实际像素、输入 uniform、纹理格式、具体截图和控制台日志,再逐项缩小范围。将“某台机器看起来不同”直接归因于驱动,会跳过更常见的未定义数学、坐标和颜色空间错误。

本课练习仍以 Three.js 的 ShaderMaterial 预览为主。JS 把 uTime、模式开关、纹理传给 GPU;顶点 uv 经顶点 Shader 输出 varying,光栅化后在片元 Shader 中插值并计算颜色。第 1、6 题额外使用独立的原生 WebGL 探针,实际创建 WebGL1 和 WebGL2 context、编译、链接、绘制三角形并读取中心像素。Three.js r185 的主画布使用 WebGL2;它本身不能证明 WebGL1 兼容。

WebGL1、WebGL2 与源码版本

WebGL1 对应 GLSL ES 1.00,使用 attribute、varying、texture2D、gl_FragColor。WebGL2 可使用 GLSL ES 3.00:源码首行 #version 300 es,输入输出改用 in/out,纹理采样用 texture(),片元输出自己声明 out vec4。在 Three.js 中使用 GLSL3 风格时应设置 material.glslVersion = THREE.GLSL3 并匹配内建注入的声明。只有把两份源码分别放进两种 context 运行,才能验证真正的双版本兼容;单纯让 GLSL1 风格代码在 WebGL2 context 中通过不等于 WebGL1 测试。

第 6 题把纯颜色函数放进 shared-color.glsl,由 JS 分别嵌入两个版本的包装器。共用的应是与版本无关的数学逻辑;版本关键字、输出变量、内建采样函数和扩展声明由包装器承担。这样维护一份核心公式,但仍要分别编译与绘制两个版本。

精度与长时间运行

GLSL 的 lowp、mediump、highp 是精度要求;硬件实际实现可不同。位置、世界坐标、大范围 UV、时间累加和高频相位优先用 highp,低范围的颜色权重可考虑 mediump。第 2 题把同一正弦条纹放在两种变量精度下比较,结果只说明这台设备、这组输入是否出现差异;没有差异也不能证明所有移动 GPU 安全。用 getShaderPrecisionFormat 记录支持范围,再在目标设备观察画面。

连续增长的秒数传入 32 位 float 时,可表示的小数精度逐渐变差,sin(time) 和滚动 UV 可能卡顿。mod(time, 1000.0) 可限制后续相位范围,但如果 time 进入 GPU 时小数已经丢失,Shader 内的 mod 无法找回它。第 5、8 题把 JavaScript 的时间在写入 uniform 前折返,再与大时间输入并排比较。折返点若不与周期对齐,动画会跳变;周期动画更稳妥的做法是按相位周期折返或维护单独的短周期相位。

未定义数学与纹理边界

pow(负数,非整数)、sqrt(负数)、除零、数组越界和未初始化变量会产生不可依赖的结果。第 3、4 题默认显示安全写法,只有切换实验模式才执行非法输入;不同设备的黑色、杂色、编译处理甚至无明显变化都不应作为业务逻辑。常用修复是对有定义域的输入先 max/clamp,对除数设置有意义的最小值,并检查索引范围。

纹理坐标超出 [0,1] 时,结果取决于 wrapS/wrapT:重复、镜像或边缘夹取。第 7 题比较重复采样与显式 clamp;夹取阻止重复,却仍会把最外侧纹素拉伸。需要透明、纯色或渐隐边框时还要边界遮罩,不能把 clamp 当成万能边缘修复。颜色纹理标记 sRGB,法线、遮罩等数据纹理保持无颜色空间;最终显示还要检查 renderer 输出颜色空间与 tone mapping。

驱动、浏览器与系统层

WebGL1 循环限制、扩展可用性和移动 GPU 的分支代价都需要按目标设备验证。第 10 题的 if 与 mix 可产生同一画面,源代码没有分支不代表 GPU 一定更快;两边表达式都计算时甚至可能更贵。discard 可能影响早期深度优化,使用前应测量。Linux Mesa、Windows 厂商驱动、Safari/Chrome/Firefox,以及高 DPI 的实际绘制分辨率都可能使问题显现,但需要用同一输入与截图复现后再归因。第 9 题提供设备能力报告,建议连同浏览器版本和操作系统手工记录。

发布前检查

直接复制 兼容性检查清单 到项目。若某个环境表现不同,先确定该环境是否成功创建 WebGL context,随后看编译日志、精度信息、uniform 值、资源状态、纹理颜色空间、实际画布尺寸,再比较驱动与系统。先修复不合法输入和明确的状态错误,最后才考虑设备专属降级。

练习

难度 题目 可观察验收
⭐ WebGL1 到 WebGL2 的语法迁移 两种 context 编译并绘制
⭐ highp 与 mediump 的可见范围 条纹对照与设备记录
⭐⭐ 受控观察 pow 的非法输入 安全与实验模式可切换
⭐⭐ 受控观察 sqrt 的非法输入 修复路径可复现
⭐⭐ 在 JS 端折返长时间 uniform 长时间对照
⭐⭐⭐ 一段颜色函数,两种 GLSL 包装 共用函数两端通过
⭐⭐⭐ 越界 UV:重复、夹取与边界淡出 三种边缘行为可见
⭐⭐⭐ 大时间导致滚动 UV 精度崩塌 原始与折返时间对照
⭐⭐⭐⭐ 记录设备与浏览器能力 可复制差异报告
⭐⭐⭐⭐ if 与 mix 的同画面对照 固定条件记录帧间隔