OpenGL:投影视图空间与NDC的协调,结果似乎超出[-1,1]范围

Ric*_*ams 5 opengl glsl perspectivecamera projection-matrix ssao

我一直在尝试按照教程的指示实现屏幕空间环境光遮挡.我一直在解决我的实施问题,因为我遇到过它们,但这一次让我感到难过.

我对该方法的理解如下.环境遮挡因子由从与给定片段的法线对齐的半球内取得的样本确定.为了确定样本是否有助于环境遮挡因子,我必须根据视图空间深度纹理(包含在此帖子图像的左下角)检查视图空间中的样本深度.所以我知道从深度纹理中取出哪些坐标,我必须将样本的坐标从视图空间转换为规范化设备坐标(在[-1,1]范围内),然后转换到范围[0 ,1],因此深度纹理​​有效地"映射"到视口.

下面的图像是我的场景中的环境遮挡.我知道环境遮挡本身有一个相当明显的问题(我假设半球的方向不正确),我会及时处理,但是目前我的好奇心是"闭塞"的外观是什么',表明我从视空间样本坐标移动到纹理坐标的操作是不正确的.

在此输入图像描述

由于我缺少一个稳定的着色器调试器,我可以做的调试仅限于我可以渲染到屏幕上的内容.使用以下代码创建下一个图像,其中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);
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我希望图像完全是白色的(或者更确切地说,我正在使用这个着色器的位),但是它似乎表明我正在创建的NDC在[-1,1]范围之外,我相信一定是不正确的.它也不是屏幕的一致区域,如下图所示,相机非常接近表面:

在此输入图像描述

我以前从未使用过这个程序来获取NDC,所以我确信我的逻辑在某处肯定是错的.我已经下载了教程提供的演示代码,但我无法看到代码的不同之处.我也在网上搜索过(包括在这个网站上)我似乎无法找到任何与我自己症状相同的人.

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

Vert Shader:

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;
Run Code Online (Sandbox Code Playgroud)

Frag Shader:

// --- 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);
Run Code Online (Sandbox Code Playgroud)

我已经孤立地看了每个元素,我看不出它们的问题.

  • 我已经将片段自己的视图空间位置固定到投影中(而不是样本的视图空间位置),我得到了相同的结果.
  • 投影矩阵是我用来变换模型顶点的透视矩阵(我在我的顶点着色器中构造了MVP矩阵,以确保投影矩阵能够直接到达着色器程序).
  • 投影操作本身 - 这不是我以前做过的事情,但我在线阅读文章和有投影问题的人的问题,我看不出我做错了什么.

因此,我只能得出结论,我对透视投影的理解必须有一些基础,这是有缺陷的,但我无法弄清楚是什么.如果你们中的任何一个人能够对这个问题有所了解或者让我有更多的方法来检查,我将非常感激.如果我遗漏了任何有用的信息或我可以澄清的任何内容,请告诉我.

der*_*ass 2

从你的顶点着色器:

v_eye_space_position = u_mvpMatrix * a_position;
[...]
Run Code Online (Sandbox Code Playgroud)

pMatrix * a_normal); gl_Position = v_eye_space_position;

由此可见,v_eye_space_position不是顶点的眼睛空间位置,而是裁剪空间位置,也需要赋值给gl_Position。矩阵制服的名称也表明这是 ModelView Projection -Matrix。

在片段着色器中,您基本上再次乘以投影矩阵的结果(因为您似乎假设它位于眼睛空间中)。

所以正确的代码是:

v_eye_space_position = u_mvMatrix * a_position;
[...]
gl_Position = u_projection_matrix * v_eye_space_position;
Run Code Online (Sandbox Code Playgroud)

v_eye_space_position现在,您可以在片段着色器中再次应用投影。但我的问题是:为什么还要这样做?如果您想在屏幕空间中工作,gl_FragCoord则已经在窗口空间中了。您只需要一个乘加即可从窗口空间到 NDC,只需反转视口(和深度范围)变换即可。