7

我正在编写一个延迟着色器,并试图更紧密地打包我的 gbuffer。但是,给定视图空间深度,我似乎无法正确计算视图位置

// depth -> (gl_ModelViewMatrix * vec4(pos.xyz, 1)).z; where pos is the model space position
// fov -> field of view in radians (0.62831855, 0.47123888)
// p -> ndc position, x, y [-1, 1]
vec3 getPosition(float depth, vec2 fov, vec2 p)
{
    vec3 pos;
    pos.x = -depth * tan( HALF_PI - fov.x/2.0 ) * (p.x);
    pos.y = -depth * tan( HALF_PI - fov.y/2.0 ) * (p.y);
    pos.z = depth;
    return pos;
}

计算的位置是错误的。我知道这一点,因为我仍在 gbuffer 中存储正确的位置并使用它进行测试。

4

3 回答 3

7

3 透视投影中恢复视空间位置的解决方案

投影矩阵描述了从场景的 3D 点到视口的 2D 点的映射。它从视图(眼睛)空间转换到剪辑空间,剪辑空间中的坐标通过除以剪辑坐标的w分量转换为归一化设备坐标(NDC)。NDC 在 (-1,-1,-1) 到 (1,1,1) 范围内。

在透视投影中,投影矩阵描述了从针孔相机看到的世界中的 3D 点到视口的 2D 点的映射。
相机平截头体(截断的金字塔)中的眼睛空间坐标映射到立方体(标准化设备坐标)。

透视投影矩阵:

r = right, l = left, b = bottom, t = top, n = near, f = far

2*n/(r-l)      0              0               0
0              2*n/(t-b)      0               0
(r+l)/(r-l)    (t+b)/(t-b)    -(f+n)/(f-n)    -1    
0              0              -2*f*n/(f-n)    0

它遵循:

aspect = w / h
tanFov = tan( fov_y * 0.5 );

prjMat[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect)
prjMat[1][1] = 2*n/(t-b) = 1.0 / tanFov

在透视投影中,Z 分量由有理函数计算:

z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye

深度 (gl_FragCoord.zgl_FragDepth) 计算如下:

z_ndc = clip_space_pos.z / clip_space_pos.w;
depth = (((farZ-nearZ) * z_ndc) + nearZ + farZ) / 2.0;


1. 视野和纵横比

由于投影矩阵是由视野和纵横比定义的,因此可以利用视野和纵横比来恢复视口位置。假设它是一个对称的透视投影和归一化的设备坐标,深度和近远平面是已知的。

恢复视图空间中的 Z 距离:

z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));

通过 XY 归一化设备坐标恢复视图空间位置:

ndc_x, ndc_y = xy normalized device coordinates in range from (-1, -1) to (1, 1):

viewPos.x = z_eye * ndc_x * aspect * tanFov;
viewPos.y = z_eye * ndc_y * tanFov;
viewPos.z = -z_eye; 


2. 投影矩阵

由视场和纵横比定义的投影参数存储在投影矩阵中。因此,视口位置可以通过投影矩阵中的值从对称透视投影中恢复。

注意投影矩阵、视野和纵横比之间的关系:

prjMat[0][0] = 2*n/(r-l) = 1.0 / (tanFov * aspect);
prjMat[1][1] = 2*n/(t-b) = 1.0 / tanFov;

prjMat[2][2] = -(f+n)/(f-n)
prjMat[3][2] = -2*f*n/(f-n)

恢复视图空间中的 Z 距离:

A     = prj_mat[2][2];
B     = prj_mat[3][2];
z_ndc = 2.0 * depth - 1.0;
z_eye = B / (A + z_ndc);

通过 XY 归一化设备坐标恢复视图空间位置:

viewPos.x = z_eye * ndc_x / prjMat[0][0];
viewPos.y = z_eye * ndc_y / prjMat[1][1];
viewPos.z = -z_eye; 


3. 逆投影矩阵

当然可以通过逆投影矩阵恢复视口位置。

mat4 inversePrjMat = inverse( prjMat );
vec4 viewPosH      = inversePrjMat * vec3( ndc_x, ndc_y, 2.0 * depth - 1.0, 1.0 )
vec3 viewPos       = viewPos.xyz / viewPos.w;


另请参阅以下问题的答案:

于 2017-09-08T14:36:30.630 回答
4

我最终设法使它工作,由于它与上面的方法不同,我将详细说明它,以便任何看到这个的人都有解决方案。

  • Pass 1:将视图空间中的深度值存储到 gbuffer
  • 在第二遍中重新创建 (x, y, z) 位置:
  • 将弧度的水平和垂直视野传递到着色器中。
  • 将近平面距离(near)传递给着色器。(从相机位置到近平面的距离)
  • 想象一下从相机到片段位置的光线。这条射线在某个位置 P 与近平面相交。我们在 ndc 空间中有这个位置,并且想要在视图空间中计算这个位置。
  • 现在,我们在视图空间中拥有了我们需要的所有值。我们可以利用相似三角形定律找到实际的片段位置 P'

    P = P_ndc * near * tan(fov/2.0f) // computation is the same for x, y
    // Note that by law of similar triangles, P'.x / depth = P/near  
    P'.xy = P/near * -depth; // -depth because in opengl the camera is staring down the -z axis
    P'.z = depth;
    
于 2012-07-09T10:15:01.030 回答
3

我写了一个延迟着色器,并使用这段代码重新计算屏幕空间定位:

vec3 getFragmentPosition()
{
     vec4 sPos = vec4(gl_TexCoord[0].x, gl_TexCoord[0].y, texture2D(depthTex, gl_TexCoord[0].xy).x, 1.0);
     sPos.z = 2.0 * sPos.z - 1.0;
     sPos = invPersp * sPos;

     return sPos.xyz / sPos.w;
}

其中depthTex是保存深度信息的纹理,并且invPersp是预先计算的逆透视矩阵。您获取屏幕的片段位置,并将其乘以逆透视矩阵以获得模型视图坐标。然后除以w得到齐次坐标。乘以 2 和减 1 将深度从 [0, 1](因为它存储在纹理中)缩放到 [-1, 1]。

此外,根据您使用的 MRT 类型,重新计算的结果不会完全等于存储的信息,因为您失去了浮点精度。

于 2012-06-30T21:31:51.660 回答