OpenGL:如何在GLSL中从窗口空间坐标计算眼睛空间坐标?

3

请问如何在GLSL中从窗口坐标(帧缓冲中的像素)和像素深度值计算出眼空间坐标(可以理解为相机坐标)?类似于GLU库中的gluUnproject函数。

5个回答

4

看起来是GLSL将gl_FragCoord.z转换为眼空间z的重复问题

编辑(完整答案):

// input: x_coord, y_coord, samplerDepth
vec2 xy = vec2(x_coord,y_coord); //in [0,1] range
vec4 v_screen = vec4(xy, texture(samplerDepth,xy), 1.0 );
vec4 v_homo = inverse(gl_ProjectionMatrix) * 2.0*(v_screen-vec4(0.5));
vec3 v_eye = v_homo.xyz / v_homo.w; //transfer from homogeneous coordinates

我已经阅读了您对那个问题的回复。首先,我并不只是想重建z。而且您的回复中包含了看似完全无关的内容,也就是说,在一行代码中使用了一些变量,但在接下来的代码中从未引用过这些变量。当我读到那段话时,我不禁想问您在回复时抽了什么样的烟(开玩笑);-) - Razzupaltuff
@karx11erx:答案并不像你说的那么糟糕,但我已经在这里放了一个修复版本供你参考。 - kvark
啊,太棒了。谢谢。我认为还不太对。你从深度缓冲区中获取了v_screen,在下一行中你又使用了read_depth。我觉得它们是相同的。问题是,我没有原始投影矩阵。我尝试做的是在整个帧被渲染后应用阴影贴图的后处理。所以我有一个带有颜色和深度缓冲区以及阴影贴图的FBO。但这并不完全有效。请参见此处以获取详细信息:http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=295642#Post295642 - Razzupaltuff
啊,我看到你在这里编辑了你的代码。我指的是你链接的另一个问题。 - Razzupaltuff

2
假设您坚持使用固定管线式的模型、视图和投影,那么您可以直接实现gluUnProject手册页面中给出的公式。
GLSL中没有内置矩阵求逆功能,所以最好在CPU上进行操作。因此,您需要提供一个由组合的modelViewProjection矩阵求逆后的uniform变量。gl_FragCoord处于窗口坐标系中,因此您还需要提供视图尺寸。
因此,您可能最终会得到类似以下的代码(即兴编写):
vec4 unProjectedPosition = invertedModelViewProjection * vec4( 
               2.0 * (gl_FragCoord.x - view[0]) / view[2] - 1.0, 
               2.0 * (gl_FragCoord.y - view[1]) / view[3] - 1.0,
               2.0 * gl_FragCoord.z - 1.0,
               1.0);

如果您已经实现了自己的旧矩阵堆栈的类似物,那么您可能可以很好地反转矩阵。否则,这可能是一个比您预期更令人生畏的话题,您最好使用MESA的开源实现(请参见该文件中的第三个函数invert_matrix),因为它经过了充分的测试,即使没有其他原因也是如此。

这是由glGetIntegerv返回的GL_VIEWPORT相同的值。您还应根据kvark的建议进行修改:在GLSL规范的较新版本中,invert出现了。但是,您可能不想每个片段都这样做,因为它会非常昂贵。 - Tommy
谢谢。所以视图 [0 .. 3] = 左、右、下、上?我有一个矩阵反演函数,没问题。实际上我已经在使用它了(详见我对 kvark 评论的回复)。 - Razzupaltuff
是的,听起来没错-所以你将x和y转换为范围在[-1, 1]的数字,以覆盖整个坐标轴。我实际上是照字面地转录了链接的手册的内容,所以那里有详细的说明,对于在我的答案中留下'view'意义模糊一点的问题,我向您道歉。 - Tommy

0

嗯,opengl.org上的一个人指出,投影产生的剪裁空间坐标被clipPos.w除以来计算归一化设备坐标。当从片段到NDC再到剪裁空间坐标反转步骤时,需要重构w(这恰好是与之对应的视图空间(相机)坐标的-z),并将ndc坐标乘以该值来计算正确的剪裁空间坐标(通过将其乘以逆投影矩阵可以将其转换为视图空间坐标)。

以下代码假定您正在后处理中处理帧缓冲区。在呈现几何体时进行处理时,您可以使用gl_FragCoord.z代替texture2D(sceneDepth, ndcPos.xy).r。

以下是代码:

uniform sampler2D sceneDepth;
uniform mat4 projectionInverse;
uniform vec2 clipPlanes; // zNear, zFar
uniform vec2 windowSize; // window width, height

#define ZNEAR clipPlanes.x
#define ZFAR clipPlanes.y

#define A (ZNEAR + ZFAR)
#define B (ZNEAR - ZFAR)
#define C (2.0 * ZNEAR * ZFAR)
#define D (ndcPos.z * B)
#define ZEYE -(C / (A + D))

void main() 
{
vec3 ndcPos;
ndcPos.xy = gl_FragCoord.xy / windowSize;
ndcPos.z = texture2D (sceneDepth, ndcPos.xy).r; // or gl_FragCoord.z
ndcPos -= 0.5;
ndcPos *= 2.0;
vec4 clipPos;
clipPos.w = -ZEYE;
clipPos.xyz = ndcPos * clipPos.w;
vec4 eyePos = projectionInverse * clipPos;
}

基本上这是 gluUnproject 的 GLSL 版本。


0

我刚刚意识到,在片段着色器中进行这些计算是不必要的。你可以通过在CPU上执行这些操作并将其与MVP逆矩阵相乘来节省一些运算(假设glDepthRange(0, 1),请随意编辑):

glm::vec4 vp(left, right, width, height);
glm::mat4 viewportMat = glm::translate(
    vec3(-2.0 * vp.x / vp.z - 1.0, -2.0 * vp.y / vp.w - 1.0, -1.0))
  * glm::scale(glm::vec3(2.0 / vp.z, 2.0 / vp.w, 2.0));
glm::mat4 mvpInv = inverse(mvp);
glm::mat4 vmvpInv = mvpInv * viewportMat;
shader->uniform("vmvpInv", vmvpInv);

在着色器中:

vec4 eyePos = vmvpInv * vec4(gl_FragCoord.xyz, 1);
vec3 pos = eyePos.xyz / eyePos.w;

0
我认为所有可用的答案都从一个方面触及了这个问题,而且 khronos.org 有一个 Wiki 页面,列出了几种不同的情况,并用着色器代码进行了解释,因此值得在这里发布。 从窗口空间计算眼睛空间

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