屏幕坐标到世界坐标
Screen Coordinates to World Coordinates
我想在 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 文档 (https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3) 中所述,结果以对象坐标返回。因此,为了将屏幕转换为世界坐标,我应该使用一个网格的变换矩阵,但是如果有很多网格并且我想从屏幕转换为世界坐标会怎样?我应该将什么模型矩阵与相机视图矩阵相乘以形成 ModelView 矩阵?
这个序列有几个问题:
glfwGetCursorPos(window, &xpos, &ypos);
glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zpos);
[...]
glm::vec3 win(xpos,height - ypos, zpos);
Windowspace出身。 glReadPixels
是一个 GL 函数,因此遵守 GL 的约定,原点为左下角像素。当您为 win
变量采用该约定时,您仍然使用错误的原点来读取深度缓冲区。
另外,你的翻转是错误的。由于 ypos
应该在 [0,height-1]
中,正确的公式是 height-1 - ypos
,所以你在这里也 差一个 。 (稍后我们会发现这也不完全正确。)
"Screen Coordinates" 与像素坐标。您的代码假定您从 GLFW 返回的坐标以像素为单位。不是这种情况。 GLFW 使用 "virtual screen coordinates" 的概念,它不一定映射到像素:
Pixels and screen coordinates may map 1:1 on your machine, but they
won't on every other machine, for example on a Mac with a Retina
display. The ratio between screen coordinates and pixels may also
change at run-time depending on which monitor the window is currently
considered to be on.
GLFW 通常为 window 提供两种尺寸,glfwGetWindowSize
will return the result in said virtual screen coordinates, while glfwGetFramebufferSize
将 return 以像素为单位的实际尺寸,与 OpenGL 相关。所以基本上,您必须查询两种尺寸,然后才能适当地将鼠标坐标从屏幕坐标缩放到您需要的实际像素。
次像素位置。虽然 glReadPixels
使用整数坐标寻址特定像素,但整个转换数学运算使用浮点并且可以表示任意子像素位置。 GL的window space定义为整数坐标表示像素的角,像素中心位于半整数坐标。您的 win
变量将代表所述像素的左下角,但更有用的约定是使用像素中心,因此您最好将 (0.5f, 0.5f, 0.0f)
的偏移量添加到 win
,假设您指向像素中心。 (如果虚拟屏幕坐标比我们的像素分辨率更高,我们可以做得更好,这意味着我们已经获得了鼠标光标的子像素位置,但数学不会改变,因为我们仍然必须切换到GL 的修道院,其中整数表示边界而不是整数表示中心)。请注意,由于我们现在考虑从 x
中的 [0,w)
和 y
中的 [0,h)
出发的 space,这也会影响点 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
what model matrix should I multuply by camera view matrix to form ModelView matrix?
None。基本的坐标变换管道是
object space -> {MODEL} -> World Space -> {VIEW} -> Eye Space -> {PROJ} -> Clip Space -> {perspective divide} -> NDC -> {Viewport/DepthRange} -> Window Space
没有模型矩阵影响从世界到window space的方式,因此反转它也不会依赖于任何模型矩阵。
that as said in the glm docs (https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3) the result is returned in object coordinates.
数学并不关心您在哪些 space 之间进行转换。文档提到了对象 space,函数使用了一个名为 modelView
的参数,但是你放在那里的矩阵是完全不相关的。放view
就可以了。
So in order to convert screen to world coordinates I should use a transform matrix from one mesh.
好吧,你甚至可以这样做。您可以使用任何对象的任何模型矩阵,只要该矩阵不是单一的,并且只要您对非项目使用相同的矩阵,就像您稍后用于从对象 space 到世界 space.如果你确定它是规则的,你甚至可以组成一个随机矩阵。 (好吧,如果矩阵是病态的,可能会出现数值问题)。这里的关键是,当你指定 (V*M) 和 P 作为 glm::unproject
的矩阵时,它会在内部计算 (V*M)^-1 * P^-1 * ndc_pos
即 M^-1 * V^-1 & P^-1 * ndc_pos
。如果将结果从对象 space 转换回世界 space,则再次将其乘以 M
,结果是 M * M^-1 * V^-1 & P^-1 * ndc_pos
,这当然只是 V^-1 & P^-1 * ndc_pos
如果您一开始没有将 M
放入 unproject 中,您将直接得到它。您刚刚添加了更多的计算工作,并引入了更多潜在的数值问题...
我想在 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 文档 (https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3) 中所述,结果以对象坐标返回。因此,为了将屏幕转换为世界坐标,我应该使用一个网格的变换矩阵,但是如果有很多网格并且我想从屏幕转换为世界坐标会怎样?我应该将什么模型矩阵与相机视图矩阵相乘以形成 ModelView 矩阵?
这个序列有几个问题:
glfwGetCursorPos(window, &xpos, &ypos); glReadPixels(xpos, ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zpos); [...] glm::vec3 win(xpos,height - ypos, zpos);
Windowspace出身。
glReadPixels
是一个 GL 函数,因此遵守 GL 的约定,原点为左下角像素。当您为win
变量采用该约定时,您仍然使用错误的原点来读取深度缓冲区。另外,你的翻转是错误的。由于
ypos
应该在[0,height-1]
中,正确的公式是height-1 - ypos
,所以你在这里也 差一个 。 (稍后我们会发现这也不完全正确。)"Screen Coordinates" 与像素坐标。您的代码假定您从 GLFW 返回的坐标以像素为单位。不是这种情况。 GLFW 使用 "virtual screen coordinates" 的概念,它不一定映射到像素:
Pixels and screen coordinates may map 1:1 on your machine, but they won't on every other machine, for example on a Mac with a Retina display. The ratio between screen coordinates and pixels may also change at run-time depending on which monitor the window is currently considered to be on.
GLFW 通常为 window 提供两种尺寸,
glfwGetWindowSize
will return the result in said virtual screen coordinates, whileglfwGetFramebufferSize
将 return 以像素为单位的实际尺寸,与 OpenGL 相关。所以基本上,您必须查询两种尺寸,然后才能适当地将鼠标坐标从屏幕坐标缩放到您需要的实际像素。次像素位置。虽然
glReadPixels
使用整数坐标寻址特定像素,但整个转换数学运算使用浮点并且可以表示任意子像素位置。 GL的window space定义为整数坐标表示像素的角,像素中心位于半整数坐标。您的win
变量将代表所述像素的左下角,但更有用的约定是使用像素中心,因此您最好将(0.5f, 0.5f, 0.0f)
的偏移量添加到win
,假设您指向像素中心。 (如果虚拟屏幕坐标比我们的像素分辨率更高,我们可以做得更好,这意味着我们已经获得了鼠标光标的子像素位置,但数学不会改变,因为我们仍然必须切换到GL 的修道院,其中整数表示边界而不是整数表示中心)。请注意,由于我们现在考虑从x
中的[0,w)
和y
中的[0,h)
出发的 space,这也会影响点 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
what model matrix should I multuply by camera view matrix to form ModelView matrix?
None。基本的坐标变换管道是
object space -> {MODEL} -> World Space -> {VIEW} -> Eye Space -> {PROJ} -> Clip Space -> {perspective divide} -> NDC -> {Viewport/DepthRange} -> Window Space
没有模型矩阵影响从世界到window space的方式,因此反转它也不会依赖于任何模型矩阵。
that as said in the glm docs (https://glm.g-truc.net/0.9.8/api/a00169.html#ga82a558de3ce42cbeed0f6ec292a4e1b3) the result is returned in object coordinates.
数学并不关心您在哪些 space 之间进行转换。文档提到了对象 space,函数使用了一个名为 modelView
的参数,但是你放在那里的矩阵是完全不相关的。放view
就可以了。
So in order to convert screen to world coordinates I should use a transform matrix from one mesh.
好吧,你甚至可以这样做。您可以使用任何对象的任何模型矩阵,只要该矩阵不是单一的,并且只要您对非项目使用相同的矩阵,就像您稍后用于从对象 space 到世界 space.如果你确定它是规则的,你甚至可以组成一个随机矩阵。 (好吧,如果矩阵是病态的,可能会出现数值问题)。这里的关键是,当你指定 (V*M) 和 P 作为 glm::unproject
的矩阵时,它会在内部计算 (V*M)^-1 * P^-1 * ndc_pos
即 M^-1 * V^-1 & P^-1 * ndc_pos
。如果将结果从对象 space 转换回世界 space,则再次将其乘以 M
,结果是 M * M^-1 * V^-1 & P^-1 * ndc_pos
,这当然只是 V^-1 & P^-1 * ndc_pos
如果您一开始没有将 M
放入 unproject 中,您将直接得到它。您刚刚添加了更多的计算工作,并引入了更多潜在的数值问题...