WebGL使用EQUAL深度测试时的不一致行为及规范问询
问题核心
当使用EQUAL作为深度测试函数,且深度清除值与图元的裁剪空间深度值均为精确的1.0时,不同GPU设备出现了深度测试结果不一致的情况,需要明确该场景的规范定义,判断是实现Bug还是未定义行为。
复现场景
WebGL程序配置如下:
gl.enable(gl.DEPTH_TEST); gl.clearDepth(1.0); gl.depthFunc(gl.EQUAL);
顶点着色器中固定裁剪空间深度:
gl_Position.z = 1.0;
设备表现差异
- 测试通过设备:M3芯片Mac、Intel芯片Mac、Mali-G52 GPU,图元正常渲染
- 测试失败设备:Snapdragon 685,图元无法渲染
进一步测试发现,将gl.clearDepth(1.0)改为gl.clearDepth(0.9999828338623046875)后,Snapdragon 685可以正常渲染。这说明该设备上裁剪空间深度值在转换为深度缓冲区存储值时,被缩放后略小于1.0——尽管片段着色器中读取到的片段深度仍为1.0。
精度差异分析
这个特殊值0.9999828338623046875需要18位尾数来存储,精度高于depth16unorm(16位归一化整数,等效11位尾数精度)或半精度浮点数(10位尾数精度)。WebGL中JS的双精度数值转换为深度缓冲区的存储格式时,会受硬件支持的深度缓冲区精度限制,出现精度丢失。
在Snapdragon 685的实现中,裁剪空间z=1.0转换为深度缓冲区存储值时,因精度限制无法精确存储为1.0,而gl.clearDepth(1.0)被精确存储为对应格式的最大值,导致两者在EQUAL测试时不匹配;其他设备的深度缓冲区精度更高,或转换算法使得两者存储值一致,因此测试通过。
结论与规范说明
这种情况不属于严格意义上的Bug,而是硬件实现的精度差异。WebGL规范允许深度缓冲区的精度由硬件决定,并未强制要求所有设备在边缘深度值(如1.0)的转换和存储上完全一致。深度测试的EQUAL模式本身对精度非常敏感,尤其是在深度缓冲区精度有限的设备上,边缘值的严格匹配容易出现不一致。
建议避免依赖EQUAL测试与深度缓冲区边缘值的严格匹配,改用LEQUAL等更宽松的测试函数,或者调整图元的深度值,避开精度敏感的边缘区域(如将gl_Position.z设置为略小于1.0的值)。
内容的提问来源于stack exchange,提问作者Andrew

