我想在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;
}
}
}
现在,我有2个问题,第一个是我从glm::unProject
得到的世界向量有一个非常小的x,y和z。如果我使用此值来转换网格,则网格会遭受小的翻译,并且不会跟随鼠标指针。
第二个问题是,如glm docs(https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3)中所述,结果以对象坐标返回。因此,为了将屏幕转换为世界坐标,我应该使用来自一个网格的变换矩阵,但是如果有多个网格并且我想从屏幕转换为世界坐标会发生什么?我应该通过相机视图矩阵多维模型矩阵来形成ModelView矩阵?
答案 0 :(得分:9)
这个序列有几个问题:
glfwGetCursorPos(window, &xpos, &ypos); glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zpos); [...] glm::vec3 win(xpos,height - ypos, zpos);
窗口空间来源。 glReadPixels
是一个GL函数,因此遵循GL的惯例,其原点是左下角的像素。当您转到win
变量的约定时,仍然使用错误的原点来读取深度缓冲区。
此外,你的翻转是错误的。由于ypos
应位于[0,height-1]
,因此正确的公式为height-1 - ypos
,因此您也可以关闭一个。 (我们稍后会看到,这也不完全正确。)
&#34;屏幕坐标&#34;与像素坐标。您的代码假定您从GLFW返回的坐标以像素为单位。不是这种情况。 GLFW使用"virtual screen coordinates"的概念,它不一定映射到像素:
像素和屏幕坐标可以在您的机器上以1:1映射,但它们 在其他所有机器上都没有,例如在带有Retina的Mac上 显示。屏幕坐标和像素之间的比率也可以 在运行时更改,具体取决于窗口当前的监视器 被认为是。
GLFW通常为窗口提供两种尺寸,glfwGetWindowSize
将在所述虚拟屏幕坐标中返回结果,而glfwGetFramebufferSize
将返回与OpenGL相关的实际尺寸(以像素为单位)。所以基本上,你必须查询两种尺寸,并且可以适当地将鼠标坐标从屏幕坐标缩放到你需要的实际像素。
亚像素位置。虽然glReadPixels
使用整数坐标来处理特定像素,但整个变换数学运算与浮点一起使用并且可以表示任意子像素位置。定义GL的窗口空间,使得整数坐标表示像素的角,像素中心位于半整数坐标处。您的win
变量将代表所述像素的左下角,但更有用的惯例是使用像素中心,因此您最好将(0.5f, 0.5f, 0.0f)
的偏移量添加到{{1}假设你指向像素中心。 (如果虚拟屏幕坐标的分辨率高于我们的像素,我们可以做得更好一些,这意味着我们已经获得了鼠标光标的子像素位置,但数学不会改变,因为我们仍然需要切换到GL的修道院,其中整数表示边界而不是整数表示中心)。
总而言之,你可以(概念上):
win
我应该通过相机视图矩阵多维模型矩阵来形成ModelView矩阵?
无。基本坐标转换管道是
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-1-pixel_pos.y, 0.0f);
glReadPixels( (GLint)win.x, (GLint)win.y, ..., &win.z)
// ... unproject win
没有模型矩阵影响从世界到窗口空间的方式,因此反转它也不依赖于任何模型矩阵。
如glm docs(https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3)中所述,结果以对象坐标返回。
数学并不关心你在哪些空间之间进行转换。文档提到了对象空间,函数使用了一个名为object space -> {MODEL} -> World Space -> {VIEW} -> Eye Space -> {PROJ} -> Clip Space -> {perspective divide} -> NDC -> {Viewport/DepthRange} -> Window Space
的参数,但是你放在那里的矩阵是完全不相关的。只需modelView
即可。
嗯,你甚至可以这样做。您可以使用任何对象的任何模型矩阵,只要矩阵不是单数,并且只要您使用相同的矩阵作为非项目,您将在以后用于从对象空间到世界空间。如果你确定它是常规的,你甚至可以组成一个随机矩阵。 (好吧,如果矩阵病态,可能存在数值问题)。这里的关键是,当您指定(V * M)和P作为因此,为了将屏幕转换为世界坐标,我应该使用一个网格的变换矩阵。
view
的矩阵时,它将在内部计算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
放入非项目中。您刚刚添加了更多计算工作,并为数值问题引入了更多潜力...