我正在写一个延迟着色器,我正试图更紧密地收拾我的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中存储正确的位置并使用它进行测试。
答案 0 :(得分:7)
投影矩阵描述了从场景的3D点到视口的2D点的映射。它从视图(眼睛)空间转换到剪辑空间,并且通过用剪辑坐标的 w 分量进行划分,将剪辑空间中的坐标转换为规范化设备坐标(NDC)。 NDC的范围是(-1,-1,-1)到(1,1,1)。
在透视投影中,投影矩阵描述了从针孔相机到视口的2D点看世界中3D点的映射。
相机平截头体(截头金字塔)中的眼睛空间坐标被映射到立方体(标准化设备坐标)。
透视投影矩阵:
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
在Perspective Projection中,Z分量由有理函数:
计算z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye
深度(gl_FragCoord.z
和gl_FragDepth
)的计算方法如下:
z_ndc = clip_space_pos.z / clip_space_pos.w;
depth = (((farZ-nearZ) * z_ndc) + nearZ + farZ) / 2.0;
由于投影矩阵由视场和纵横比定义,因此可以使用视场和纵横比恢复视口位置。如果它是对称透视投影和标准化设备坐标,则深度和近远平面是已知的。
恢复视图空间中的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;
由视场和纵横比定义的投影参数存储在投影矩阵中。因此,视口位置可以通过投影矩阵中的值从对称透视投影中恢复。
注意投影矩阵,视野和纵横比之间的关系:
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;
当然,视口位置可以通过反投影矩阵来恢复。
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;
另见以下问题的答案:
答案 1 :(得分:3)
我设法最终使它工作,因为它是一个与上面不同的方法,我将详细说明所以任何看到这个的人都会有一个解决方案。
现在,我们在视图空间中拥有了所需的所有值。我们可以使用类似三角形的定律来找到实际的片段位置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;
答案 2 :(得分:2)
我写了一个延迟着色器,并使用此代码重新计算屏幕空间定位:
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类型,重新计算的结果将不会完全等于存储的信息,因为您将失去浮点精度。