c++ - 在OpenGL中用鼠标拾取在3D空间中拖动3D点的最佳方法?

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

用鼠标拖动拖动3D点的最佳方法是什么。问题不在于挑选,而在于在3D空间中拖动。

我在想两种方法,一种是使用gluUnProject获取“查看世界”坐标并转换3D点。在这种情况下的问题是,它仅在具有深度值的表面(使用glReadPixels)上是世界,如果鼠标离开表面,它将基于gluUnProject的winZ分量给出最大或最小深度值。在某些情况下,它不起作用。

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

那么,有什么方法可以使我获得准确的2D到3D坐标,从而无需考虑平面情况就可以轻松地在每个方向上拖动3D点?一定有一些方法,我看过3D软件,它们具有完善的拖动功能。

最佳答案

我习惯于天真地解决这些用户交互问题(也许不是以数学上的最佳方式),但考虑到它们对性能的要求并不严格(用户交互部分,不一定是对用户交互的最终修改),因此“足够好”现场)。

为了不受约束地自由拖动对象,您使用unproject描述的方法往往效果很好,通常会稍作调整即可提供接近像素的完美拖动:

...而不是使用glReadPixels尝试提取屏幕深度,而是希望在用户进行选择和/或选择时使用几何对象/网格的概念。现在,只需投影该对象的中心点即可获得屏幕深度。然后,您可以在屏幕X / Y中四处拖动,保持从该投影获得的Z,然后取消投影以将生成的平移增量从先前的中心转换到新的中心以转换对象。这也使您感觉像从对象的中心拖动一样,这很直观。

对于自动约束拖动,一种检测到此问题的快速方法是首先捕获“视平面法线”。使用您惯用的那些投影/非投影功能的一种快速方法(可能使数学家皱眉)是在屏幕空间中的视口(viewport)中心解投影两个点(一个具有接近z的值和一个具有远z的值)并获得在这两点之间的单位 vector 。现在,您可以使用点积找到最接近该法线的世界轴。另外两个世界轴定义了我们要拖动的世界平面。

然后,再次使用这些方便的非投影函数以使光线沿着鼠标光标变为一个简单的问题。之后,您可以在反复拖动光标时进行重复的射线/平面相交,以根据增量计算平移 vector 。

为了获得更灵活的约束,可以使用Gizmo(又称操纵器,基本上是3D小部件),以便用户可以根据他要在Gizmo的哪些部分来指示他想要哪种拖动约束(平面,轴,无约束等)。选择/拖动。对于轴约束,光线/线或线/线相交非常方便。

根据注释中的要求,从视口(viewport)中检索射线(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点,我们可以从中使用delta沿约束轴平移对象。

请注意,轴/平面拖动约束涉及一些棘手的边缘情况。例如,如果一个平面垂直于观察平面(或靠近观察平面),则它可以将物体射向无穷远。轴沿垂直线拖动时也存在这种情况,就像试图从前视口(viewport)(X / Y视平面)沿Z轴拖动一样。因此,值得检测直线/平面垂直(或闭合)的情况,并在这种情况下防止拖动,但这可以在基本概念起作用后完成。

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

这些拾取/拖动/交叉点的东西可能很难调试,因此值得一试解决。为自己设置一些小目标,例如只需在视口(viewport)中的某个位置单击鼠标以创建射线。然后,您可以绕轨道旋转,并确保将射线创建在正确的位置。接下来,您可以尝试进行简单的测试,以查看该射线是否与世界平面(例如X / Y)相交,并创建/可视化射线与平面之间的交点,并确保正确。循序渐进地,耐心地步调轻快地步调自己,您将获得平稳,自信的进步。尝试一次做太多事情,您可能会遇到令人沮丧的令人讨厌的进度,试图找出您出了问题的地方。

关于c++ - 在OpenGL中用鼠标拾取在3D空间中拖动3D点的最佳方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30519214/

相关文章:

c - 如何使用带有 OpenGL 的 Nurbs 知道特定值的位置?

jquery - 使 3D CSS3 元素连续旋转

c++ - 无法渲染到 GtkGLArea

C++ 3D 游戏引擎

Java、LWJGL、OpenGL 1.1、将BufferedImage解码为Bytebuffer并跨类绑定(bind)到OpenGL

c++ - 使用 Xcode 和 Eclipse 在 Mac Mojave 上编译着色器问题

c++ - 使用 C++14 的自动函数类型返回推导代替 std::common_type 总是安全的吗?

c++ - 为什么析构函数会无休止地调用自身(导致堆栈溢出)?

3d - JavaFX 3D 图形 : Mouse click position on 3D object

javascript - 将鼠标坐标转换为 3D 平面图