C++ 如何在给定视图空间深度值和ndc xy的情况下恢复视图空间位置
我正在写一个延迟着色器,并试图将我的gbuffer打包得更紧。然而,鉴于视图空间深度,我似乎无法正确计算视图位置C++ 如何在给定视图空间深度值和ndc xy的情况下恢复视图空间位置,c++,glsl,shader,coordinate-transformation,perspectivecamera,C++,Glsl,Shader,Coordinate Transformation,Perspectivecamera,我正在写一个延迟着色器,并试图将我的gbuffer打包得更紧。然而,鉴于视图空间深度,我似乎无法正确计算视图位置 //深度->(gl_ModelViewMatrix*vec4(pos.xyz,1)).z;其中pos是模型空间位置 //视野->以弧度表示的视野(0.62831855,0.47123888) //p->ndc位置,x,y[-1,1] vec3获取位置(浮动深度、vec2视野、vec2 p) { vec3位置; 位置x=-深度*tan(半圆周率-fov.x/2.0)*(p.x); 位置
//深度->(gl_ModelViewMatrix*vec4(pos.xyz,1)).z;其中pos是模型空间位置
//视野->以弧度表示的视野(0.62831855,0.47123888)
//p->ndc位置,x,y[-1,1]
vec3获取位置(浮动深度、vec2视野、vec2 p)
{
vec3位置;
位置x=-深度*tan(半圆周率-fov.x/2.0)*(p.x);
位置y=-深度*tan(半圆周率-fov.y/2.0)*(p.y);
位置z=深度;
返回pos;
}
计算出的位置是错误的。我知道这一点,因为我仍在gbuffer中存储正确的位置并使用它进行测试。我编写了一个延迟着色器,并使用此代码重新计算屏幕空间定位:
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
得到均匀坐标。乘二减一是将深度从[0,1](存储在纹理中)缩放到[-1,1]
此外,根据您使用的MRT类型,重新计算的结果将与存储的信息不完全相同,因为您失去了浮点精度。我最终设法使其工作,因为这是一种与上面不同的方法,我将详细说明,以便任何看到这一点的人都能找到解决方案
- 传递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;
摄影机平截头体(截断棱锥体)中的眼空间坐标映射到立方体(规范化设备坐标) 透视投影矩阵:
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.z
and)计算如下:
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));
A = prj_mat[2][2];
B = prj_mat[3][2];
z_ndc = 2.0 * depth - 1.0;
z_eye = B / (A + z_ndc);
通过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;
viewPos.x = z_eye * ndc_x / prjMat[0][0];
viewPos.y = z_eye * ndc_y / prjMat[1][1];
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距离:
z_ndc = 2.0 * depth - 1.0;
z_eye = 2.0 * n * f / (f + n - z_ndc * (f - n));
A = prj_mat[2][2];
B = prj_mat[3][2];
z_ndc = 2.0 * depth - 1.0;
z_eye = B / (A + z_ndc);
通过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;
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;
另见以下问题的答案:
2*p-1
将产生正确的值?你存储到depthTex中的深度是什么空间?我存储它,在我的GBuffer着色器中,如gl_FradCoord.z
。抱歉,只有sPos
的z
部分应该缩放-我编辑了我的原始帖子。是的,因为我非常确定gl_FragCoord
也在视图空间中。(对不起,我刚才拼错了。)