【问题标题】:Screen Coordinates to World Coordinates屏幕坐标到世界坐标
【发布时间】:2018-01-29 11:31:12
【问题描述】:

我想将屏幕坐标转换为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) 中所说,结果以对象坐标返回。因此,为了将屏幕转换为世界坐标,我应该使用一个网格的变换矩阵,但是如果一个有很多网格并且我想从屏幕转换为世界坐标会发生什么?我应该用相机视图矩阵乘以什么模型矩阵来形成模型视图矩阵?

【问题讨论】:

    标签: c++ opengl matrix projection glm-math


    【解决方案1】:

    这个序列有几个问题:

           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 使用"virtual screen coordinates" 的概念,不一定映射到像素:

      像素和屏幕坐标可能会在您的机器上 1:1 映射,但它们 不会在其他所有机器上,例如在带有 Retina 的 Mac 上 展示。屏幕坐标和像素之间的比率也可以 根据窗口当前所在的监视器在运行时更改 被认为是开启的。

      GLFW 通常为一个窗口提供两种尺寸,glfwGetWindowSize 将返回虚拟屏幕坐标中的结果,而glfwGetFramebufferSize 将返回以像素为单位的实际尺寸,与 OpenGL 相关。所以基本上,您必须查询这两种尺寸,然后才能将鼠标坐标从屏幕坐标适当地缩放到您需要的实际像素。

    3. 子像素位置。虽然glReadPixels 使用整数坐标处理特定像素,但整个变换数学使用浮点数并可以表示任意子像素位置。 GL 的窗口空间被定义为整数坐标代表像素的,像素中心位于整数坐标的一半。您的win 变量将代表所述像素的左下角,但更有用的约定是使用像素中心,因此您最好将(0.5f, 0.5f, 0.0f) 的偏移量添加到win,假设您指向像素中心。 (如果虚拟屏幕坐标比我们的像素分辨率更高,我们可以做得更好,这意味着我们已经获得了鼠标光标的亚像素位置,但数学不会改变,因为我们仍然需要切换到GL 的修道院,其中整数表示边界而不是整数表示中心)。请注意,由于我们现在考虑从[0,w)x[0,h)y 的空间,这也会影响点1。如果单击像素(0,0),它将具有中心@987654342 @,而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 &amp; P^-1 * ndc_pos。如果你将结果从对象空间转换回世界空间,你再乘以M,得到M * M^-1 * V^-1 &amp; P^-1 * ndc_pos,当然这只是V^-1 &amp; P^-1 * ndc_pos,如果你没有输入@987654357,你会直接得到它@ 首先进入 unproject 。你只是增加了更多的计算工作,并为数值问题引入了更多的潜力......

    【讨论】:

    • 谢谢你,它的工作。在我的计算机中,WindowsSize 与 GLFW 中的 FramebufferSize 相同,所以问题是 glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &amp;zpos); 我必须反转 y 坐标。谢谢
    • 程序有一个小问题。读取的像素不是点击的像素,而是正下方的像素。问题是您添加半像素(pixel_pos = pixel_pos + glm::vec2(0.5f, 0.5f); // shift to GL's center convention),然后更改参考系统,最后转换为 int;这样,您最终会获取下面的像素。要解决这个问题,你应该只在 glReadPixels 之后,在 unproject 之前转换到 GL 的中心约定。
    • @Tarquiscani:感谢您发现这一点。我按照您的建议稍有不同地修复了这个问题,并更新了解释以适应:而不是 [0,h-1] 整数像素空间,我正在考虑 [0,h) 窗口空间作为我的坐标,所以正确的 y 翻转是 h-y,而不是h-1-y.
    猜你喜欢
    • 2017-12-11
    • 2017-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-25
    • 1970-01-01
    • 2021-10-22
    相关资源
    最近更新 更多