用鼠标拾取拖动3D点的最佳方法是什么。问题不在于拾取,而在于在三维空间中拖动。
我有两种想法,一种是使用gluUnProject获得"视图到世界"坐标,并平移三维点。这种情况下的问题是,它只在具有"深度"值(使用glReadPixels)的曲面上创建世界,如果鼠标离开曲面,它将根据gluUnProject的winZ组件给出最大或最小深度值。而且在某些情况下它不起作用。
第二种方法是使用GL_MODELVIEW_MIX沿XY、XZ和YZ平面拖动。但在这种情况下,问题是,我们如何知道我们在XY、XZ或YZ平面上?我们怎么知道轨迹球的前视图在XY平面上,如果我们想在侧平面而不是前平面上拖动呢?
那么,有没有什么方法可以给我准确的二维到三维坐标,这样我就可以在不考虑平面情况的情况下轻松地在各个方向拖动三维点?一定有一些方法,我见过3D软件,它们有完美的拖动功能。
我习惯于在某种程度上天真地解决这些用户交互问题(也许不是以数学上最优的方式),但考虑到它们不是非常关键的性能(用户交互部分,不一定是对场景的最终修改),我已经"足够好"了。
对于对象的无约束自由拖动,您描述的使用unproject的方法往往效果很好,只需轻轻调整即可实现接近像素的完美拖动:
当用户正在拾取和/或已经选择时,您需要几何对象/网格的概念,而不是使用glReadPixels
来尝试提取屏幕深度。现在只需投影该对象的中心点即可获得屏幕深度。然后,您可以在屏幕X/Y中拖动,保持从该投影获得的Z不变,然后取消投影以获得从上一个中心到新中心的平移增量,从而变换对象。这也让它"感觉"像是从对象的中心拖动,这往往是非常直观的。
对于自动约束拖动,一种快速检测方法是首先获取"视图平面法线"。使用你习惯的投影/取消投影函数的一个快速方法(可能会让数学家皱眉头)是取消投影屏幕空间中视口中心的两个点(一个具有近z值,一个具有远z值),并在这两个点之间获得一个单位向量。现在,您可以使用点积找到最接近法线的世界轴。另外两个世界轴定义了我们要拖动的世界平面。
然后,再次使用那些方便的取消投影功能来沿着鼠标光标获取光线就变得很简单了。之后,可以在拖动光标时重复进行光线/平面相交,以根据增量计算平移向量。
对于更灵活的约束,gizmo(也称为操纵器,基本上是一个3D小部件)可以派上用场,这样用户就可以根据拾取/拖动gizmo的哪些部分来指示他想要什么样的拖动约束(平面、轴、无约束等)。对于轴约束,光线/直线或直线/直线相交非常方便。
根据注释中的要求,要从视口中检索光线(C++伪代码):
// 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沿受约束的轴平移对象。
请注意,轴/平面拖动约束涉及一些棘手的边情况。例如,如果一个平面垂直于观察平面(或闭合),它可以将对象射到无穷远。轴沿垂直线拖动也存在同样的情况,就像试图从前视口(X/Y观察平面)沿Z轴拖动一样。因此,有必要检测直线/平面垂直(或闭合)的情况,并在这种情况下防止拖动,但这可以在基本概念工作后完成。
在某些情况下,另一个值得注意的技巧是隐藏鼠标光标,以改善事物的"感觉"。例如,使用轴约束时,鼠标光标可能会与轴本身相距甚远,并且看起来/感觉可能很奇怪。因此,我看到许多商业软件包在这种情况下只是隐藏鼠标光标,以避免暴露鼠标和小控件/手柄之间的差异,因此感觉更自然。当用户释放鼠标按钮时,鼠标光标将移动到控制柄的视觉中心。请注意,您不应该对平板电脑进行这种隐藏的光标拖动(它们有点例外)。
这种拾取/拖动/交叉点的东西可能很难调试,所以值得在初级阶段解决。为自己设定小目标,比如只需在视口中的某个位置单击鼠标按钮即可创建光线。然后可以环绕并确保光线是在正确的位置创建的。接下来,您可以尝试一个简单的测试,看看该光线是否与世界平面(例如X/Y)相交,并创建/可视化光线和平面之间的交点,并确保这是正确的。慢慢来,耐心地调整自己的节奏,你会有一个平稳、自信的进步。试着一次做太多事情,你可能会在试图找出错误的地方时取得非常令人沮丧的不和谐的进展。
这是一个非常有趣的toic。除了您描述的颜色标记着色方法外,z缓冲区也是一种方法。这里也有关于同一主题的类似讨论:
基于顶点着色器的几何变形应用的3D场景通用拾取解决方案
OpenGL-拾取(最快方式)如何从屏幕坐标中获取对象坐标?
这也类似于已经充分讨论过的对象选择,包括它们的优缺点:http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/
事实上,我的答案是没有唯一的最佳方法来做到这一点。正如你也提到的,它们有优点也有缺点。我个人喜欢gluunproject,因为它很容易,结果也不错。此外,不能使用屏幕空间在任何方向移动顶点,因为屏幕空间是二维的,并且没有唯一的方法将其映射回三维几何空间。希望能有所帮助:)。