屏幕坐标转世界坐标

9
我希望将屏幕坐标转换为OpenGL中的世界坐标。我使用glm(同时我也在使用glfw)来实现此目的。
这是我的代码:
static void mouse_callback(GLFWwindow* window, int button, int action, int mods)
{
    if (button == GLFW_MOUSE_BUTTON_LEFT) {
        if(GLFW_PRESS == action){
            int height = 768, width =1024; 
            double xpos,ypos,zpos;
            glfwGetCursorPos(window, &xpos, &ypos);

            glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zpos);

            glm::mat4 m_projection = glm::perspective(glm::radians(45.0f), (float)(1024/768), 0.1f, 1000.0f);

            glm::vec3 win(xpos,height - ypos, zpos);
            glm::vec4 viewport(0.0f,0.0f,(float)width, (float)height);
            glm::vec3 world = glm::unProject(win, mesh.getView() * mesh.getTransform(),m_projection,viewport);

            std::cout << "screen " << xpos << " " << ypos << " " << zpos << std::endl;
            std::cout << "world " << world.x << " " << world.y << " " << world.z << std::endl;
        }
    }
}

现在,我有两个问题。第一个问题是,从glm::unProject获取的世界向量的x、y和z非常小。如果我使用这些值来平移网格,则网格会发生微小的平移,并且不会跟随鼠标指针移动。
第二个问题是,如在glm文档中所述(https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3),结果以对象坐标返回。因此,为了将屏幕坐标转换为世界坐标,我应该使用来自一个网格的变换矩阵,但是如果我有许多网格并且想要从屏幕坐标转换为世界坐标怎么办?我应该将哪个模型矩阵乘以相机视图矩阵形成ModelView矩阵?
1个回答

11

这个序列存在一些问题:

       glfwGetCursorPos(window, &xpos, &ypos);
       glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zpos);
       [...]
       glm::vec3 win(xpos,height - ypos, zpos);

  1. 窗口空间原点。 glReadPixels是一个GL函数,因此遵循GL的约定,原点为左下角像素。虽然您将win变量翻转到该约定,但仍然使用错误的起点读取深度缓冲区。

    此外,您的翻转是错误的。由于ypos应在[0,height-1]中,因此正确的公式是height-1-ypos,因此您也偏离了一步。 (稍后我们将看到这并不完全正确。)

  2. “屏幕坐标”与“像素坐标”。您的代码假设从GLFW返回的坐标是以像素为单位的。事实并非如此。GLFW使用所谓的"虚拟屏幕坐标"的概念,这些坐标不一定映射到像素:

    像素和屏幕坐标可能在您的计算机上按1:1映射,但在其他计算机上可能不会,例如具有Retina显示屏的Mac。屏幕坐标和像素之间的比率也可能在运行时更改,具体取决于当前被视为窗口的监视器。

    GLFW通常为窗口提供两个大小,glfwGetWindowSize将在所述虚拟屏幕坐标中返回结果,而glfwGetFramebufferSize将返回OpenGL相关的实际像素大小。因此,您必须查询两个大小,然后可以将鼠标坐标从屏幕坐标适当地缩放到所需的实际像素。

  3. 子像素位置。虽然glReadPixels使用整数坐标指定特定像素,但整个转换数学运算使用浮点数并且可以表示任意子像素位置。 GL的窗口空间定义为整数坐标表示像素的角落,像素中心位于半整数坐标处。您的win变量将表示该像素的左下角,但更有用的约定是使用像素中心,因此如果您指向像素中心,则最好将win添加偏移量(0.5f,0.5f,0.0f)。 (如果虚拟屏幕坐标比我们的像素分辨率更高,这意味着我们已经获得了鼠标光标的子像素位置,但数学运算不会改变,因为我们仍然必须切换到GL的约定,其中整数表示边框而不是中心)。请注意,由于我们现在考虑的空间从[0,w)x[0,h)y,因此这也会影响点1。如果您单击像素(0,0),它将具有中心(0.5,0.5),并且y翻转应为h-y,因此h-0.5(访问帧缓冲像素时应向下舍入至h-1)。

把所有东西放在一起,你可以这样做(概念上):

glfwGetWindowSize(win, &screen_w, &screen_h); // better use the callback and cache the values 
glfwGetFramebufferSize(win, &pixel_w, &pixel_h); // better use the callback and cache the values 
glfwGetCursorPos(window, &xpos, &ypos);
glm::vec2 screen_pos=glm::vec2(xpos, ypos);
glm::vec2 pixel_pos=screen_pos * glm::vec2(pixel_w, pixel_h) / glm::vec2(screen_w, screen_h); // note: not necessarily integer
pixel_pos = pixel_pos + glm::vec2(0.5f, 0.5f); // shift to GL's center convention
glm::vec3 win=glm::vec3(pixel_pos., pixel_h-pixel_pos.y, 0.0f);
glReadPixels( (GLint)win.x, (GLint)win.y, ..., &win.z)
// ... unproject win

我应该用什么模型矩阵乘以相机视图矩阵来形成模型视图矩阵?

没有。基本的坐标转换流程是

object space -> {MODEL} -> World Space -> {VIEW} -> Eye Space -> {PROJ} -> Clip Space -> {perspective divide} -> NDC -> {Viewport/DepthRange} -> Window Space

没有模型矩阵影响从世界空间到窗口空间的方式,因此反转也不会依赖于任何模型矩阵。

如glm文档所述(https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3),结果以对象坐标返回。

数学不关心你在哪些空间之间进行变换。文档提到了对象空间,并且函数使用名为modelView的参数,但是你放置哪个矩阵完全无关紧要。只需将view放在那里即可。

因此,为了将屏幕坐标转换为世界坐标,我应该使用来自一个网格的变换矩阵。

好的,你甚至可以这样做。只要矩阵不是奇异的,并且在unproject和从物体空间到世界空间转换时使用相同的矩阵,就可以使用任何对象的任何模型矩阵。你甚至可以编写一个随机矩阵,只要确保它是常规的即可。(如果矩阵条件不良可能会出现数值问题)。关键在于当你指定(V*M)和P作为glm::unproject的矩阵时,它将内部计算(V*M)^-1 * P^-1 * ndc_pos,即M^-1 * V^-1 & P^-1 * ndc_pos。如果你将结果从物体空间转换回世界空间,再次乘以M,就会得到M * M^-1 * V^-1 & P^-1 * ndc_pos,这当然只是直接得到的V^-1 & P^-1 * ndc_pos,如果你一开始没有将M放入unproject中,你就直接得到了这个结果。你只是增加了更多的计算工作,并引入了更多的潜在数值问题...


谢谢,它起作用了。在我的电脑上,WindowsSize和GLFW中的FramebufferSize相同,因此问题是“glReadPixels(xpos,ypos,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&zpos);” 我必须反转y坐标。谢谢。 - RdlP
1
这个过程有一个小问题。像素读取的不是点击的那个,而是下面的那个。问题在于添加了半像素 (pixel_pos = pixel_pos + glm::vec2(0.5f, 0.5f); // shift to GL's center convention),然后改变参考系统,最后强制类型转换为整型;这样会导致选取下面的像素。要解决这个问题,应该在 glReadPixels之后,在unproject之前才进行GL中心约定的偏移量移动。 - Tarquiscani
1
@Tarquiscani:感谢您发现这个问题。我按照您的建议稍微进行了修复,并更新了解释以适应:我考虑的是我的坐标在[0,h)窗口空间而不是[0,h-1]整数像素空间,因此正确的y轴翻转是h-y而不是h-1-y - derhass

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