在OpenGL中使用鼠标拾取在3D空间中拖动3D点的最佳方法是什么?

时间:2015-05-29 00:23:54

标签: c++ opengl 3d drag-and-drop mouse-picking

使用鼠标拾取拖动3D点的最佳方法是什么。问题不是挑选,而是拖入3D空间。

我有两种方法,一种是使用gluUnProject获取View to World坐标并翻译3D点。在这种情况下的问题是,它只有具有深度值(使用glReadPixels)的表面上的世界,如果鼠标离开表面,它将根据gluUnProject的winZ组件给出最大或最小深度值。在某些情况下它不起作用。

第二种方法是使用GL_MODELVIEW_MATRIX沿XY,XZ,YZ平面拖动。 但在这种情况下的问题是,我们怎么知道我们在XY或XZ或YZ平面上?我们怎么知道轨迹球的前视图在XY平面上,如果我们想在侧面上拖动而不是在前平面上呢?

那么,有没有什么方法可以让我获得准确的2D到3D坐标,这样我就可以在不考虑平面情况的情况下轻松地在各个方向上拖动3D点?必须有一些方法,我见过3D软件,它们具有完美的拖动功能。

2 个答案:

答案 0 :(得分:4)

我曾经有点天真地解决这些用户交互问题(也许不是以数学上最优的方式),但是#34;足够好"考虑到他们不是非常关键的性能(用户交互部分,不一定是对场景的结果修改)。

对于无约束的自由拖动对象,使用unproject描述的方法往往能够很好地工作,通常只需略微调整即可实现近像素完美拖动:

...而不是使用glReadPixels来尝试提取屏幕深度,您需要在用户选择和/或选择时使用几何对象/网格的概念。现在只需投影该对象的中心点即可获得屏幕深度。然后,您可以在屏幕X / Y中拖动,保持与此投影相同的Z,并取消项目以获得从前一个中心到新中心的结果平移增量来转换对象。这也使它感觉"像你一样从物体的中心拖动,这往往非常直观。

对于自动约束拖动,一种快速检测方法是首先抓取正常的' viewplane。使用你曾经习惯的那些投影/非投影函数的快速方法(可能让数学家皱眉)是在屏幕空间中取消投影视口中心的两个点(一个具有接近z的值,一个具有远z值)并获得这两点之间的单位向量。现在,您可以使用点积找到最接近法线的世界轴。另外两个世界轴定义了我们想要拖动的世界平面。

然后再次使用那些方便的非投影函数来获取沿着鼠标光标的光线变得很简单。之后,您可以在重复拖动光标以从三角形计算平移向量时执行重复的光线/平面交叉。

对于更灵活的约束,Gizmo(也就是操纵器,基本上是3D小部件)可以派上用场,以便用户可以根据哪些部分来指示他想要的拖拽约束(平面,轴,无约束等)。他挑选/拖动的小发明。对于轴约束,光线/线或线/线交叉很方便。

根据评论中的要求,从视口中检索光线(C ++ - ish伪代码):

// Get a ray from the current cursor position (screen_x and screen_y).
const float near = 0.0f;
const float far = 1.0f;
Vec3 ray_org = unproject(Vec3(screen_x, screen_y, near));
Vec3 ray_dir = unproject(Vec3(screen_x, screen_y, far));
ray_dir -= ray_org;

// Normalize ray_dir (rsqrt should handle zero cases to avoid divide by zero).
const float rlen = rsqrt(ray_dir[0]*ray_dir[0] + 
                         ray_dir[1]*ray_dir[1] + 
                         ray_dir[2]*ray_dir[2]);
ray_dir[0] *= rlen;
ray_dir[1] *= rlen;
ray_dir[2] *= rlen;

然后我们与从鼠标光标获得的光线进行光线/平面交叉,以确定当用户开始拖动时光线与平面相交的位置(交点将为我们提供3D点)。在那之后,它只是通过在用户拖动鼠标时反复执行此操作所收集的点之间的增量来翻译对象。在沿着平面约束移动时,对象应该直观地跟随鼠标。

轴拖动基本上是相同的想法,但是我们将光线转换为直线并进行线/线交叉(对于轴约束线的鼠标线,给我们一个最近的点,因为线通常赢得'完美交叉),给我们一个3D点,我们可以使用这些点来沿着约束轴平移物体。

请注意,轴/平面拖动约束涉及棘手的边缘情况。例如,如果一个平面垂直于观察平面(或接近),它可以将物体射入无限远。轴沿着垂直线拖动时存在同样的情况,例如尝试从前视口(X / Y视图平面)沿Z轴拖动。因此,值得检测线/平面垂直(或接近)并在这种情况下阻止拖动的情况,但这可以在您使基本概念起作用后完成。

另一个值得注意的改进方式"感觉"对于某些情况是隐藏鼠标光标。例如,对于轴约束,鼠标光标可能最终变得与轴本身非常远,并且它可能看起来/感觉很奇怪。因此,我已经看到许多商业软件包只是在这种情况下隐藏鼠标光标,以避免暴露鼠标和Gizmo /手柄之间的差异,结果往往会感觉更自然。当用户释放鼠标按钮时,鼠标光标移动到手柄的可视中心。请注意,您不应该为平板电脑执行此隐藏光标拖动(它们有点例外)。

这种拾取/拖动/交叉的东西可能很难调试,因此值得在babysteps中处理它。为自己设置小目标,比如在某个视口中单击鼠标按钮来创建光线。然后你可以围绕轨道运行并确保光线是在正确的位置创建的。接下来,您可以尝试一个简单的测试,以查看该光线是否与世界(例如X / Y)平面中的平面相交,并创建/可视化光线和平面之间的交点,并确保其正确。把它放在小的,耐心的babysteps,自己踱步,你将有顺利,自信的进步。试着一次做太多,你可能会有非常令人沮丧的震动进展,试图找出你出错的地方。

答案 1 :(得分:2)

这是一个非常有趣的toic。除了您描述的颜色标记着色z-buffer的方法也是一种方法。这里有关于同一主题的类似讨论:

Generic picking solution for 3D scenes with vertex-shader-based geometry deformation applied

OpenGL - Picking (fastest way) How to get object coordinates from screen coordinates?

这也与已经完全讨论过的对象选择相似,包括它们的优点和缺点: http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/

实际上我的答案是没有独特的最佳方法来做到这一点。正如你所提到的,它们有利有弊。我个人喜欢gluunproject,因为它很简单,结果还可以。此外,您无法使用屏幕空间在任何方向上移动顶点,因为屏幕空间为2D,并且没有将其映射回3D几何空间的独特方式。希望它有所帮助:)。