OpenGL:将视图空间坐标投影到NDC,结果似乎超出了[-1,1]范围

5

我一直在尝试按照这个教程的指导实现屏幕空间环境光遮蔽。我在解决问题时会遇到一些困难,但目前有一个问题让我卡住了。

我对该方法的理解如下。环境遮蔽因子是通过从与给定片段法线对齐的半球中取样来确定的。要确定样本是否对环境遮蔽因子有贡献,必须将样本在视图空间中的深度与视图空间深度纹理(包含在本文底部左下角的图像中)进行比较。因此,我必须将样本的坐标从视图空间转换为归一化设备坐标(范围为[-1,1]),然后再转换为范围为[0,1]的坐标,以便深度纹理有效地“映射”到视口。

以下图像显示了我的环境遮蔽覆盖在场景上的效果。我知道环境遮蔽本身存在相当明显的问题(我认为半球的方向不正确),但我会处理它,目前让我好奇的是遮蔽的出现被“缩小”了,这表明我的操作从视图空间样本坐标移动到纹理坐标是不正确的。

enter image description here

由于我没有稳定的着色器调试器,因此我可以进行的调试受限于我可以渲染到屏幕上的内容。下一个图像是使用以下代码创建的,其中ndcs是给定样本的归一化设备坐标:

if (ndcs.x > 1.0f || ndcs.y > 1.0f || ndcs.x < -1.0f || ndcs.y < -1.0f)
{
  gl_FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
}
else
{
  gl_FragColor = vec4(vec3(1.0f), 1.0f);
}

enter image description here

我期望这张图片完全是白色的(或者说,我使用这个着色器的部分是白色的),然而它似乎表明我创建的 NDC 超出了 [-1,1] 的范围,我相信这一定是不正确的。屏幕上也不是一致的区域,就像你在下面这张图中看到的,当相机非常靠近一个表面时:

enter image description here

我以前从未使用过此过程来获取 NDC,所以我肯定我的逻辑有哪里出错了。我下载了教程提供的演示代码,但我看不出我的代码有什么不同。我还在网上搜索过(包括本网站),但好像没有人像我这样的症状。

这是我的着色器中相关的代码:

顶点着色器:

v_eye_space_position = u_mvpMatrix * a_position;
v_world_space_normal = normalize(u_rotationMatrix * a_normal);
v_eye_space_normal = normalize(u_mvpMatrix * a_normal);
gl_Position = v_eye_space_position;

片段着色器:


// --- SSAO Testing ---
// Convert from the noise texture back to [-1,1] range
// We want the noise texture to tile across the screen.
vec3 kernel_rotation = (texture2D(s_noise, gl_FragCoord.xy * u_noise_scale) * 2.0f - 1.0f).xyz;
vec3 eye_space_tangent = normalize(kernel_rotation - v_eye_space_normal.xyz * dot(kernel_rotation, v_eye_space_normal.xyz));
vec3 eye_space_bitangent = cross(v_eye_space_normal.xyz, eye_space_tangent);
mat3 tbn = mat3(eye_space_tangent, eye_space_bitangent, v_eye_space_normal);

float ambient_occlusion = 0.0f;
const float hemisphere_radius = 0.05f;

for (int i=0; i<16; i++)
{
  vec3 kernel_sample = tbn * u_ssao_kernel[i];
  kernel_sample = kernel_sample * hemisphere_radius + v_eye_space_position.xyz;

  // Project the sample position into screen space.
  vec4 offset = vec4(kernel_sample, 1.0f);
  offset = u_projection_matrix * offset;
  offset.xy /= offset.w;
  vec4 ndcs = offset;
  offset.xy = 1.0f - (offset.xy * 0.5f + 0.5f);

  // Find the depth at this sample point.
  float sample_depth = texture2D(s_camera_depth, offset.xy).z;

  // Check if the sample point is occluded.
  float range_check = 0.0f;

  float linear_eye_space_position = (v_eye_space_position.z - u_near_plane)/(u_far_plane - u_near_plane);

  // Range check.
  if (abs(linear_eye_space_position - sample_depth) < hemisphere_radius)
  {
    range_check = 1.0f;
  }

  float linear_kernel_sample_depth = (kernel_sample.z - u_near_plane)/(u_far_plane - u_near_plane);
  if (sample_depth <= linear_kernel_sample_depth)
  {
    ambient_occlusion += 1.0f * range_check;
  }
}

// Average and invert the ambient occlusion.
ambient_occlusion = 1.0f - (ambient_occlusion/16.0f);

我已经单独检查了每个元素,但是我无法发现问题所在。

  • 我已经将碎片的视图空间位置替换到投影中(而不是样本的视图空间位置),并且得到了相同的结果。
  • 投影矩阵是我用来转换模型顶点的透视矩阵(我在顶点着色器中构建了MVP矩阵,以确保投影矩阵完好无损地传递到着色器程序中)。
  • 投影操作本身-这不是我以前做过的事情,但我已经阅读了在线文章和有关投影问题的问题,并且我看不出我错在哪里。

因此,我只能得出结论,我的透视投影理解中必定有一些基本问题,但我就是想不明白到底是什么。如果你们中的任何人能够为我解决问题或提供更多检查的方法,我将不胜感激。如果我遗漏了任何有用的信息或需要澄清的内容,请告诉我。

1个回答

2

从您的顶点着色器:

v_eye_space_position = u_mvpMatrix * a_position;
[...]

pMatrix * a_normal); gl_Position = v_eye_space_position;

从这里我们可以看出,v_eye_space_position并不是顶点的眼睛空间位置,而是需要分配给gl_Position的裁剪空间位置。您的矩阵统一变量名称也表明这是ModelViewProjection-Matrix。
在片段着色器中,您基本上再次通过投影矩阵进行乘法运算(因为您似乎假设它在眼睛空间中)。
因此,正确的代码应该是:
v_eye_space_position = u_mvMatrix * a_position;
[...]
gl_Position = u_projection_matrix * v_eye_space_position;

现在,你可以在片段着色器中再次将投影应用到 v_eye_space_position。但是我的问题是:为什么要再做一次呢?如果你想在屏幕空间工作,gl_FragCoord 已经在窗口空间中了。你只需要通过反转视口(和深度范围)变换来进行从窗口空间到 NDC 的倍增加即可。


非常感谢您对此的帮助。在实施了您的建议之后,结果正是我所期望的。有趣的是,我竟然忽略了这么简单的错误。教训是:始终检查“简单”的事情(特别是坐标空间)。再次非常感谢您 - 您为我减轻了巨大的压力。祝您愉快! :) - Richard Williams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接