2

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

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

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

那么,有什么方法可以为我提供准确的 2D 到 3D 坐标,以便我可以在不考虑平面情况的情况下轻松地在各个方向拖动 3D 点?一定有一些方法,我看过3D软件,它们有完美的拖动功能。

4

2 回答 2

4

我习惯于有些天真地解决这些用户交互问题(也许不是以数学上最优的方式),但考虑到它们对性能不是很关键(用户交互部分,不一定是对场景)。

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

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

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

然后,再次使用那些方便的非投影函数沿着鼠标光标获得一条光线就变得很简单了。之后,您可以在四处拖动光标以计算增量的平移向量时进行重复的射线/平面相交。

对于更灵活的约束,小工具(又名操纵器,基本上是 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 轴拖动。因此,值得检测线/平面垂直(或接近)的情况并防止在这种情况下拖动,但这可以在基本概念工作后完成。

在某些情况下,另一个值得注意的改善“感觉”方式的技巧是隐藏鼠标光标。例如,在轴约束下,鼠标光标最终可能会变得离轴本身很远,并且看起来/感觉很奇怪。所以我看到一些商业软件包在这种情况下只是简单地隐藏了鼠标光标,以避免显示鼠标和小工具/手柄之间的差异,结果往往感觉更自然一些。当用户释放鼠标按钮时,鼠标光标移动到手柄的视觉中心。请注意,您不应该为平板电脑执行此隐藏光标拖动(它们有点例外)。

这种拾取/拖动/相交的东西可能很难调试,因此值得一开始就解决它。为自己设定小目标,例如在某处的视口中单击鼠标按钮来创建射线。然后,您可以绕行并确保在正确的位置创建射线。接下来,您可以尝试一个简单的测试来查看该射线是否与世界平面(例如 X/Y)平面中的一个平面相交,并创建/可视化射线和平面之间的交点,并确保这是正确的。慢慢来,耐心的小步,调整自己的步调,你就会有平稳、自信的进步。尝试一次做太多事情,你可能会有非常令人沮丧的不和谐的进展,试图找出你哪里出错了。

于 2015-05-29T02:28:24.850 回答
2

这是一个非常有趣的主题。除了您描述的颜色标记方法之外,为 z 缓冲区着色也是一种方法。这里有关于同一主题的类似讨论:

应用了基于顶点着色器的几何变形的 3D 场景的通用拾取解决方案

OpenGL - Picking (fastest way) 如何从屏幕坐标中获取对象坐标?

这也类似于已经充分讨论过的对象拾取,包括它们的优缺点: http ://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/

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

于 2015-05-29T04:24:09.527 回答