启用 msaa 时 gl_FragDepth 是不是等于 gl_FragCoord.z?

Posted

技术标签:

【中文标题】启用 msaa 时 gl_FragDepth 是不是等于 gl_FragCoord.z?【英文标题】:Is gl_FragDepth equal gl_FragCoord.z when msaa enable?启用 msaa 时 gl_FragDepth 是否等于 gl_FragCoord.z? 【发布时间】:2021-08-18 00:37:54 【问题描述】:

我知道gl_FragDepth 将从 opengl wiki 获取 gl_FragCoord.z 的值。 https://www.khronos.org/opengl/wiki/Fragment_Shader/Defined_Outputs

但是我有一个问题。如果我启用 MSAA 并在片段着色器中写入gl_FragDepth = gl_FragCoord.z,则显示将无法正常工作。您可以在白色三角形上看到一条黑线,如下所示:

如果我不在片段着色器中写gl_FragDepth,它会正常工作。

如果我禁用 MSAA,无论我写gl_FragDepth,它都可以正常工作。

正确的显示图像没有黑线:

渲染场景很简单,我只画了两个白色三角形,它们相交在一条边上。

我在顶点着色器中添加了一个简单的灯光。代码如下:

const char *vertexShaderSource[] = 
    "#version 120\n",
    "varying vec4 lightColor;\n",
    "void main()\n",
    "\n",
    "   vec3 n = normalize(gl_NormalMatrix * gl_Normal);\n",
    "   vec3 l = normalize(vec3(0.0, 1.0, 1.0));\n",
    "   float NdotL = clamp(dot(n, l), 0.001, 1.0);\n",
    "   lightColor = vec4(1.0)*(NdotL + 0.2);\n",
    "   gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n",
    "\n"
;

const char *fragmentShaderSource[] = 
    "#version 120\n",
    "varying vec4 lightColor;\n",
    "void main(void)\n",
    "\n",
    "   gl_FragColor = vec4(lightColor.rgb, 1.0);\n",
    "   gl_FragDepth = gl_FragCoord.z;\n"
    "\n"
;

2个三角形的6个顶点的位置是(-5,-5,0),(5,-5,0),(-5,5,0),(-5,0,0),( 5,0,0),(-5,0,-10)。 法线垂直于三角形。

如果我在片段着色器中写gl_FragDepth,我想知道为什么显示图像不同?

【问题讨论】:

【参考方案1】:

你的两个三角形相交。具体来说,灰色三角形的边缘可以生成与白色三角形的深度值相等的深度值。因此,灰色三角形中的特定样本完全有可能在该交点处生成与白色三角形的深度值相等的深度值。

所以你永远不能保证不会在那里看到一行;你只是碰巧的情况不多。

然而,这一切都假设:

    灰色三角形在白色三角形之后渲染。

    您的深度测试将通过相同的值。

即使在这两个条件之外,您在这里得到的结果也可能发生。原因很复杂。

看,whole point of multisampling 是光栅化器生成的深度值的数量和片段着色器执行的数量不一样。因此,单个 FS 调用会映射到多个深度值。

但是,单个 FS 调用仍然可以写入 gl_FragDepth。如果这样做,则映射到该 FS 调用的 所有样本 将接收相同的深度。此深度会覆盖多重采样生成的深度值。

另外,interpolation at the edges of a primitive is weird under multisampling。在该像素的三角形边界内的每个样本都将导致写入一个样本值(除非有其他东西将其剔除)。但像素的中心点不必是这些样本位置之一。因此,一个不通过像素中心的三角形仍然可以为该像素贡献一些样本,只要该三角形通过该像素中的至少一个样本。

片段着色器根据像素内的某个位置获取插值。使用多重采样,此位置可能不在三角形的内部。例如,如果实现为像素内的 FS 插值选择的位置位于三角形的中心,并且三角形没有通过该像素的中心,那么只要它通过,您仍然会得到 FS 调用通过一些样本。

但这意味着插值可以表示三角形区域外部的位置。插值数学可以为不在三角形内的区域生成值;他们只是没有意义。

gl_FragCoord 是一个插值,因此可以生成三角形之外的值。由于灰色三角形面向观察者,因此来自灰色三角形迎面而来的边缘“上方”位置的值将比它们应有的更近。并且由于灰色三角形的边缘与白色三角形相交,因此比其实际边缘值更接近的值将被认为比白色三角形更接近

解决此问题的正常方法是使用centroid 插值限定符。但是,标准并没有真正允许这样做。即使您使用centroid 限定符重新声明gl_FragCoord,it won't have any effect:

centroid 的使用并未进一步限制此值在当前原语内。

此外,如前所述,常规多重采样渲染中的深度替换无论如何都会破坏所有每个样本的深度信息。如果您的 FS 写入深度,则像素中的每个样本都将获得相同的深度值。这并不是您真正想要的,即使您可以对 gl_FragCoord 进行 centroid 插值(这可能是他们不允许这样做的原因)。

因此,如果在用于多重采样的着色器中进行深度替换是绝对必要的(并且您应该尽可能避免这种情况),您将需要使用per-sample shading。您可以使用sample 重新声明gl_FragCoord 来实现此目的。

【讨论】:

非常感谢您的回答。实际上,当我使用对数深度来解决 webgl 的 z-fighting 时,我发现了这个问题。由于 webgl 不支持 glClipControl,我不能使用 reverse-z。看来我不能再使用对数深度了。

以上是关于启用 msaa 时 gl_FragDepth 是不是等于 gl_FragCoord.z?的主要内容,如果未能解决你的问题,请参考以下文章

当我使用 glBlitFramebuffer 实现 MSAA 时收到 INVALID OPERATION

在encodeui中,MSAA跟随技术不支持过滤器属性吗?

可以从 MSAA FBO 读取单个样本吗?

WebGPU学习:MSAA

FXAA,FSAA与MSAA有什么区别?效果和性能上哪个好

渲染到目标。有些有 MSAA,有些没有