You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何gl_FragCoord.z与((pos.z/pos.w)+1.0)*0.5存在差异?深度精度疑问

顶点着色器计算的depth与gl_FragCoord.z差异的原因及解决方法

这个问题的核心原因在于透视校正插值的机制,以及GPU处理顶点数据和片段数据的顺序差异,咱们一步步拆解来看:

1. 两种深度计算方式的本质区别

  • 你在顶点着色器里计算的depth,是先对每个顶点的裁剪空间坐标执行pos.z/pos.w(得到NDC标准化设备坐标的z值),转成[0,1]范围后输出。GPU会对这个depth值做线性插值,将结果传递给每个片段。
  • 而gl_FragCoord.z是GPU先对顶点的完整裁剪空间坐标pos(x/y/z/w四个分量)做透视校正插值,再在每个片段上执行透视除法(pos.z/pos.w)得到NDC的z值,最后转换为窗口空间的深度值(对应深度缓冲区[0,1]的范围)。

2. 为何z值小(离相机远)时差异更大?

在透视投影下,深度缓冲区的精度分布是非线性的:

  • 靠近相机的区域(z值大,对应NDC z更接近近平面),深度值的变化更敏感,精度更高;
  • 远离相机的区域(z值小,对应NDC z更接近远平面),深度值的变化非常平缓,精度极低。

线性插值无法模拟透视下的非线性深度变化,在远平面区域,这种插值误差会被放大,导致你看到的差异变得更明显;而在近平面区域,因为深度精度高,线性插值和透视校正插值的结果差异很小,几乎可以忽略。

3. 如何让两者计算结果一致?

不要在顶点着色器里提前计算好depth,而是输出裁剪空间的z和w分量,让GPU对这两个分量做透视校正插值,再在片段着色器里完成深度计算:

// 顶点着色器
out float clipZ;
out float clipW;
void main(void) {
    vec4 pos = mvpMatrix * vertex;
    clipZ = pos.z;
    clipW = pos.w;
    gl_Position = pos;
}

// 片段着色器
in float clipZ;
in float clipW;
void main(void) {
    float ndcZ = clipZ / clipW;
    float depth = ((ndcZ + 1.0) * 0.5);
    gl_FragDepth = depth; // 此时结果与gl_FragCoord.z完全一致
}

这样做的原因是,GPU对clipZ和clipW的透视校正插值会自动适配透视投影的非线性特性,在片段着色器里做除法后得到的深度值,和硬件原生生成的gl_FragCoord.z完全匹配。

内容的提问来源于stack exchange,提问作者PowerNow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:42:34