部分Intel集成显卡WebGL着色器输出异常问题排查
WebGL着色器跨GPU平台输出不一致问题
问题现象
同一段WebGL着色器在Apple M1设备(iMac/iPhone)和Intel Haswell-ULT集成显卡设备(Thinkpad 440、Thinkpad 13 Chromebook)上输出完全不同:
- 同一设备上不同浏览器输出一致:M1设备上Chrome/Safari输出相同,Intel设备上Chrome/GNOME Web输出相同
- 多数测试者看到的是Apple版本输出,仅少数Intel设备显示差异版本
原始着色器代码
JS逻辑
const gl = document.querySelector("canvas").getContext("webgl"); gl.canvas.width = gl.canvas.height = 512 gl.viewport(0, 0, 512, 512); const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]); const bufferInfo = twgl.createBufferInfoFromArrays(gl, { a_Position: { numComponents: 2, data: [-1, -1, -1, 3, 3, -1] }, }); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); twgl.drawBufferInfo(gl, bufferInfo);
顶点着色器
attribute vec4 a_Position; varying vec2 v_Position; varying vec2 v_TexCoord; void main() { v_Position = a_Position.xy; v_TexCoord = a_Position.xy / 2.0 + 0.5; gl_Position = a_Position; }
片段着色器
precision highp float; varying vec2 v_Position; varying vec2 v_TexCoord; vec2 compMult(vec2 u, vec2 v) { return vec2(u.x * v.x - u.y * v.y, u.x * v.y + u.y * v.x); } vec2 G(vec3 x) { float lenx = length(x); float ang = 350.0 * lenx; return vec2(cos(ang), sin(ang)) / lenx; } void main() { gl_FragColor.a = 1.0; vec3 x0 = vec3(v_TexCoord, 1); vec2 amplitude; for(int i = 0; i < 10; i++) { for(int j = 0; j < 10; j++) { vec2 pos = 0.05 + vec2(i, j) / 10.0; vec3 x = vec3(0.4 + 0.2 * pos, 0); float disx0x = distance(x0, x); amplitude += compMult( G(x - vec3(-0.4765625, -0.671875, 300)), compMult( vec2(-1.0 / disx0x, 350.0), G(x - x0) / disx0x ) ); } } gl_FragColor.r = length(amplitude) / 10.0; }
设备输出对比
- Apple设备输出:

- Intel设备输出:

简化测试代码(无循环版本)
JS逻辑(与原始一致)
const gl = document.querySelector("canvas").getContext("webgl"); gl.canvas.width = gl.canvas.height = 512 gl.viewport(0, 0, 512, 512); const programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]); const bufferInfo = twgl.createBufferInfoFromArrays(gl, { a_Position: { numComponents: 2, data: [-1, -1, -1, 3, 3, -1] }, }); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); twgl.drawBufferInfo(gl, bufferInfo);
顶点着色器(与原始一致)
attribute vec4 a_Position; varying vec2 v_Position; varying vec2 v_TexCoord; void main() { v_Position = a_Position.xy; v_TexCoord = a_Position.xy / 2.0 + 0.5; gl_Position = a_Position; }
片段着色器
precision highp float; varying vec2 v_Position; varying vec2 v_TexCoord; vec2 compMult(vec2 u, vec2 v) { return vec2(u.x * v.x - u.y * v.y, u.x * v.y + u.y * v.x); } vec2 G(vec3 x) { float lenx = length(x); float ang = 350.0 * lenx; return vec2(cos(ang), sin(ang)) / lenx; } void main() { gl_FragColor.a = 1.0; vec3 x0 = vec3(v_TexCoord, 1); vec2 pos = floor(8.0 * v_TexCoord) / 8.0; vec3 x = vec3(0.375 + 0.25 * pos, 0); float disx0x = distance(x0, x); vec2 amplitude = compMult( G(x - vec3(-0.46875, -0.5, 300.0)), compMult( vec2(-1.0 / disx0x, 350.0), G(x - x0) ) ); gl_FragColor.rgb = vec3(0.5 + amplitude, 0.5); }
简化代码的异常表现
Intel设备上出现不符合预期的灰色区块:
从算法逻辑看,灰色区域应与其他区块表现一致,且已确认:
- 未使用小数值除法导致精度问题
distance(x, x0)与distance(x, vec3(-0.46875, -0.5, 300))数值均不会过小- 常量已改为精确二进制表示
问题原因与解决建议
核心原因
这种跨GPU平台的输出差异,本质是不同GPU硬件对浮点数运算的精度实现存在差异,尤其是在涉及三角函数和复杂向量运算的场景:
- 三角函数精度放大偏差:
cos(350.0 * lenx)和sin(350.0 * lenx)中,350是大系数,微小的lenx计算差异会被放大,导致角度计算出现明显偏差,最终影响G函数的输出向量。 - 运算顺序优化差异:不同GPU的编译器会对运算顺序进行不同的优化(比如乘法结合律的应用),在多次复数乘法(
compMult)的累积计算中,微小的精度误差会被叠加放大,最终导致输出结果完全不同。 - Intel Haswell架构浮点特性:Haswell集成显卡在高精度浮点运算的一致性上,与Apple M1 GPU存在差异,尤其是处理大系数缩放的三角函数时,误差累积更明显。
解决建议
- 对三角函数的输入角度进行模
2π处理,避免角度过大导致的精度丢失:float ang = mod(350.0 * lenx, 2.0 * 3.14159265); - 调整运算顺序,减少误差累积,比如将除法运算提前或拆分复杂的复合乘法
- 添加微小偏移量避免极端情况:
lenx = max(length(x), 1e-6);,防止除零或精度突变
内容的提问来源于stack exchange,提问作者fweth
相关产品推荐
相关产品推荐

