【问题标题】:What is the algorithm behind the gluTess functions?gluTess 函数背后的算法是什么?
【发布时间】:2012-10-02 20:29:16
【问题描述】:

我出于好奇提出这个问题,出于性能原因,在使用 GLU 之前首先尝试实现这样的算法。

我研究过常用算法(经常提到 Delaunay、Ear Clipping),但我似乎无法理解 GLU 是如何一直如此出色地完成其工作的。

你们有没有关于该主题的有趣论文或文章?

【问题讨论】:

  • GLUT 没有曲面细分功能。 GLU 可以。它在 GLU 的 Tess 函数中称为 gluTess,而不是 glutEss。
  • 我的错,我以为他们是一回事。 :)
  • 实际上只是编辑了问题以反映这一点。

标签: opengl glu tesselation


【解决方案1】:

some notes旁边还有the source:

这只是一个非常简短的概述。有相当多的 源代码本身的附加文档。

稳健细分的目标

镶嵌算法本质上是一种二维算法。我们 最初将所有数据投影到一个平面中;我们的目标是稳健地 镶嵌投影数据。相同的拓扑镶嵌是 然后应用于输入数据。

从拓扑上讲,输出应该始终是镶嵌。如果 输入甚至稍微不平面,那么一些三角形会 从某些角度看,一定是背对着的,但目标 就是尽量减少这种影响。

该算法需要一些清理输入数据的能力 以及其自身计算中的数值误差。一种方法 这是为了指定上面定义的容差,并清理 行扫描过程中的输入和输出。至少, 该算法必须处理重合顶点,顶点与 边和重合边。

算法的阶段

  1. 求多边形法线 N。
  2. 将顶点数据投影到平面上。它不需要垂直于法线,例如。我们可以投影到飞机上
    垂直于与 N 的点积为的坐标轴 最大。
  3. 使用线扫描算法,将平面划分为 x 单调区域。任何垂直线与 x 单调区域相交 最多一个间隔。
  4. 对 x 单调区域进行三角测量。
  5. 将三角形组合成条形和扇形。

寻找法线向量

求多边形法线的常用方法是计算有符号面积 当多边形沿三个坐标轴投影时。我们 不能这样做,因为轮廓可以有零面积而不是 退化(例如领结)。

我们为顶点数据拟合一个平面,忽略它们是如何连接的 成轮廓。理想情况下,这将是最小二乘拟合;然而对于 我们的目的正常的准确性并不重要。相反,我们 找到三个相距很远的顶点,并计算法线 到它们形成的三角形。顶点的选择使得 三角形的面积至少是任何三角形的最大面积的 1/sqrt(3) 倍 使用输入顶点形成的三角形。

轮廓确实会影响法线的方向;计算后 正常情况下,我们检查有符号轮廓区域的总和是 非负数,必要时反转正态。

投影顶点

我们将顶点投影到垂直于三个顶点之一的平面上 坐标轴。这通过删除 原始输入数据和数据之间的转换步骤 由算法处理。投影还压缩了输入 数据;投影后顶点之间的二维距离可能更小 比原来的二维距离。但是通过选择坐标 与法线的点积最大的轴,压缩 系数最多为 1/sqrt(3)。

即使法线的准确度并不重要(因为 无论如何,我们都垂直于坐标轴投影), 计算的鲁棒性很重要。例如,如果有许多顶点几乎沿着一条直线,而一个顶点 V 它与线很好分开,然后我们的正常计算 应该涉及V,否则结果将是垃圾。

垂直于多边形法线投影的优点是 计算出的交点将尽可能接近 他们的理想地点。要获得此行为,请定义 TRUE_PROJECT。

线扫

共有三种数据结构:网格、事件队列和 边缘字典。

网格是一种“四边形”数据结构,它记录了网络的拓扑结构 当前分解;有关详细信息,请参阅包含文件“mesh.h”。

事件队列只保存所有顶点(原始的和计算的) 个),组织起来,以便我们可以快速提取顶点 最小 x 坐标(以及其中具有最小 y 坐标的那个)。

边字典描​​述了当前扫过的交点 与多边形的区域对齐。这只是一个排序 与扫描线相交的边,按其当前顺序排序 路口。对于每一对边,我们存储一些关于 它们之间的单调区域——这些被称为“活动区域” (因为它们被当前扫描线穿过)。

基本算法是从左到右扫一扫,处理每一个 顶点。网格的处理部分(扫描线左侧)是 平面分解。当我们穿过每个顶点时,我们更新网格 和边缘字典,然后我们检查任何新的相邻对 边看它们是否相交。

一个顶点可以有任意数量的边。有很多边的顶点可以 当顶点被合并并且交点被创建时被创建 计算。对于未处理的顶点(扫描线的右侧),这些 边缘在顶点周围没有特定的顺序;用于加工 顶点,拓扑排序应该匹配几何 订购。

顶点处理分为两个阶段:首先我们处理的是 向左边缘(所有这些边缘当前都在边缘 字典)。这涉及:

  • 从字典中删除左行边;
  • 如有必要,重新链接网格,以便事件顶点周围的这些边的顺序与字典中的顺序相匹配;
  • 根据 他们的绕组编号。

当没有左走边,并且事件顶点在一个 “内部”区域,我们需要添加一条边(将区域分割成 单调件)。为此,我们只需将事件顶点连接到 包含的上边缘或下边缘的最右端左端点 地区。

然后我们处理向右的边缘。这涉及:

  • 在边缘字典中插入边缘;
  • 计算任何新创建的活动区域的绕组数。我们可以使用每条边的绕组增量计算这个
    我们在浏览字典时会越过它。
  • 如有必要,重新链接网格,以便事件顶点周围的这些边的顺序与字典中的顺序相匹配;
  • 检查任何新相邻的边是否相交和/或合并。

如果没有右向边,我们需要再次添加一个来分割 将包含区域分成单调块。在我们的例子中,它是最 方便将边缘添加到任何一个的最左端点 包含边缘;但是我们可能需要稍后更改它(请参阅 代码了解详情)。

不变量

这些是在扫描期间保持的最重要的不变量。 我们定义了一个函数 VertLeq(v1,v2),它定义了 顶点穿过扫描线,以及一个函数 EdgeLeq(e1,e2; loc) 它表示在扫描事件位置“loc”处 e1 是否低于 e2。 此功能仅在扫描事件位置定义,该位置位于 在 {e1,e2} 的最右端和最左端之间 {e1,e2} 的端点。

边缘字典的不变量。

  • 每对相邻边 e2=Succ(e1) 在扫描事件的任何有效位置都满足 EdgeLeq(e1,e2)。
  • 如果 EdgeLeq(e2,e1) 也是如此(在任何有效的扫描事件中),则 e1 和 e2 共享一个公共端点。
  • 对于字典中的每个 e,e->Dst 已处理,但 e->Org 未处理。
  • 每个边 e 满足 VertLeq(e->Dst,event) && VertLeq(event,e->Org) 其中“event”是当前扫描线 事件。
  • 没有边 e 的长度为零。
  • 没有两条边具有相同的左右端点。网格的不变量(已处理部分)。

  • 扫描线左侧的网格部分是平面图,即。有一些方法可以将其嵌入平面中。

  • 没有处理过的边的长度为零。
  • 没有两个已处理的顶点具有相同的坐标。
  • 每个“内部”区域都是单调的,即。根据 VertLeq(v1,v2) 可以分成两条单调递增的顶点链
    • 非不变式:这些链可能会(稍微)相交,因为 数值错误,但这不会影响算法的运行。

扫描的不变量。

  • 如果顶点有任何向左的边,那么在处理顶点时这些边必须在边字典中。
  • 如果一条边被标记为“fixUpperEdge”(它是由 ConnectRightVertex 引入的临时边),那么它是唯一的右向 来自其关联顶点的边。 (这表示这些边缘存在 仅在必要时。)

稳健性

算法鲁棒性的关键是保持 上面的不变量,尤其是边缘的正确排序 字典。我们通过以下方式实现这一目标:

  1. 编写数值计算以获得最大精度,而不是 超过最大速度。

  2. 对边缘的结果完全不做任何假设 交集计算——对于足够退化的输入, 计算出的位置并不比随机数好多少。

  3. 当数值错误违反不变量时,恢复它们 通过在必要时进行拓扑更改(即重新链接 网状结构)。

三角剖分和分组

我们在进行任何三角测量之前完成线扫描。这是 因为即使在一个单调区域完成之后,还有更多 由于进一步的顶点合并,其顶点数据发生变化。

在对所有单调区域进行三角剖分后,我们要将 三角形成扇形和条形。我们使用贪婪的方法来做到这一点。 三角剖分本身并未优化以减少 原语;我们只是试图得到一个合理的分解 计算三角剖分。

【讨论】:

  • 哇,非常感谢。在 Google 上快速搜索并没有返回任何有趣的结果,但我并没有一路回溯到原始源代码。
  • @genpfault 这个算法的实现在哪里运行?是 CPU 还是 GPU?
  • @warunapww:CPU。
猜你喜欢
  • 1970-01-01
  • 2010-09-15
  • 2018-03-19
  • 1970-01-01
  • 2020-09-22
  • 1970-01-01
  • 1970-01-01
  • 2020-10-09
相关资源
最近更新 更多