使用选择框选择 3D 表面(以两种不同的方式)

Selecting 3D surfaces with a selection box (in two different ways)

我正在创建一个建模软件。我的模型全部由平面多边形组成,它们只是我用 OpenGL 显示的一组有序顶点。我进行了相当多的搜索,但令我惊讶的是,我没有找到有关我正在寻找的应用程序的太多信息。

我正在尝试使用矩形框 select 表面。这听起来很简单,但我希望它的工作方式与此方法在许多程序中的工作方式相同。这些是我正在寻找的要求:

  1. 我想要一个从左边开始向右延伸到 select 完全包含在框内的对象的矩形。
  2. 从右边开始向左移动的矩形应该 select 接触到的任何表面(它不必完全封闭。
  3. 所有 个对象 in/touching 应该 select 编辑矩形。换句话说,我想要 select 对象,无论它们是否可见。装在盒子里的所有东西,即使被另一个表面覆盖,仍然应该 selected。

列表中的第 3 项最重要。同时拥有选项 1 和 2 是首选,但如果实施起来太困难,我可以只接受其中一个。

我看过其他各种关于 3D 拾取的帖子,似乎大多数建议使用颜色拾取或光线投射。我对正常点击 selection 使用颜色拾取,但是因为我希望盒子 selection 包含不可见的表面,所以这不是一个选项。光线投射似乎也只适用于单击点而不是盒子。那么还有其他方法可以相当直接地实现我的目标吗?我认为这将是一个相当常见的任务,因为它似乎存在于许多建模软件中,但不幸的是我一直无法找到适合我需要的方法。

算法的伪代码将不胜感激,但不是必需的。至少我正在寻找一种方法,我可以自己研究并找到一些例子;我只是不知道该去哪里看。

我可以直截了当地告诉你,颜色选择是行不通的;您必须对每个对象进行一次传递才能满足要求 (3),因为只有一个像素会进入帧缓冲区。

关于光线投射,它确实只测试一个点,但实际上您可以通过取消投影选择矩形的四个角来创建测试体积。您会在近平面 (Win_Z = 0) 和远平面 (Win_Z = 1) 中找到 world-space 中的坐标,并使用它从您的 2D 选择区域构建 3D 体积。

这样做得到的体积称为平截头体(假设透视投影),它看起来像一个顶部被切掉的金字塔。视锥体相交测试有很好的记录,任何讨论 "frustum culling" 的内容都应该为您提供足够的背景来实现它。如果您可以使用轴对齐的边界框 and/or 球体来近似这些对象的边界,您的生活将会轻松很多。

following diagram很好地说明了这个测试卷:

在 CPU 上执行您自己的交集计算当然是一种选择。但根据我对你的要求的理解,我认为你也可以让 OpenGL 来完成这项工作,这应该会更容易、更高效。

方法概述

想到的机制是遮挡查询。它们允许您计算已渲染的像素。如果将此与使用剪刀测试结合使用,则可以计算选择矩形内呈现的像素。

应用到用例

用例 2 是此方法中较简单的一个。您将选择矩形设置为剪刀矩形,并使用每个表面的遮挡查询渲染所有表面。然后查看查询结果,所有查询结果大于0的surface都有像素在选择矩形内。

用例 1 稍微复杂一些。要知道曲面是否完全包含在矩形内,您需要两遍。如上所述,您通过遮挡查询进行一次渲染,并启用剪刀测试。然后你第二次做同样的事情,禁用剪刀测试。如果一个曲面两次pass的查询结果相同,则它完全在矩形内部。

实施

我不会为此提供完整的代码。这一切都应该非常简单。但这里有一些指针和代码片段。这些调用显示为 C 绑定。我希望使用 Python 绑定时相同的东西看起来很明显。

首先,由于要在选区中包含隐藏表面,因此需要禁用深度测试:

glDisable(GL_DEPTH_TEST);

由于您并不真正需要生成输出,并且可能不想打扰视觉渲染输出,您可能还想禁用颜色输出:

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

如果您启用了背面剔除,您可能还想禁用它:

glDisable(GL_CULL_FACE);

然后,对于上面提到的你只想计算选择矩形内像素的pass,设置剪刀矩形,并启用剪刀测试:

glScissor(selectionLeft, selectionBottom, selectionWidth, selectionHeight);
glEnable(GL_SCISSOR_TEST);

对于带有遮挡查询的渲染,每个表面都需要一个查询对象:

GLuint queryIds[surfaceCount];
glGenQueries(surfaceCount, queryIds);

然后对于每个表面,使用k作为循环索引:

glBeginQuery(GL_SAMPLES_PASSED, queryIds[k]);
// render surface k
glEndQuery(GL_SAMPLES_PASSED);

所有surface渲染完成后,可以得到查询结果:

GLint pixelCounts[surfaceCount];
// for all surfaces k
glGetQueryObjectiv(queryIds[k], GL_QUERY_RESULT, &pixelCounts[k]);

然后评估像素数以决定应按照上一节中所述为每个用例选择哪些表面。

完成后不要忘记重置所有状态以准备再次渲染。深度测试、颜色遮罩、剪刀测试等

我知道我来晚了,但实际上颜色选择可以用于 selecting 深层对象,同时通过解决方法提高性能:在按下 shift+鼠标滚动时执行 select离子剥离。

或者换句话说,当你第一次做矩形测试时,记住矩形位置,然后让用户按下 shift + 鼠标滚动,当用户滚动时,暂时从渲染中移除已经 selected 的对象(因此剥离)(仅在渲染时到 selection 缓冲区)并将任何新的 selected 对象添加到 selected 数组。继续,直到用户停止滚动或按下 shift 键。

需要注意的是,一旦您移动或旋转相机,此方法将无法很好地工作,但是当用户这样做时,您可以使最后一个矩形无效,或者当用户执行 shift + 鼠标滚动时临时将相机的增量矩阵应用于整个世界用户没有使最后一个 selection 无效,但出于这个原因,它可能仍然最好坚持遮挡查询,除非你卡在非常旧的硬件或移动设备上(opengl es 2 没有扩展名)

另一个是对于小于 1 像素的对象,颜色拾取失败,但这很少出现问题(尽管对于矩形拾取,根据用例,select“不可见”对象可能有意义)