第九章:几何图元

发布时间 2023-10-29 12:01:49作者: OwlCat

第九章:几何图元

几何图元,就是构成几何物体的最小单元。这章节我们将对它们进行讨论。

1.表示技术

如何用数学的方式来描绘物体?是的,用函数。
我们可以用一个布尔函数\(f(x,y,z)\)隐含形式进行描绘,当传入空间中的一点的坐标时,只有当这点属于那个物体时才会返回真;
还有一种叫描述方式是参数形式,我们传入一个参数\(t\),在\(t\)的范围内,随着\(t\)值的变化,会返回不同的坐标,它们就构成了物体的形状;
最后还有一种是直接形式,也是最常见的。就是以描述对象的主要信息为核心来描绘。比如,用两个点描述线段、用半径和球心描述球体等。

2.直线和射线

我们小学就知道3种基本的线性段类型:

  1. 直线,可在两个方向上无限延伸;
  2. 射线,是具有原点并在一个方向上可以无限延伸;
  3. 线段,是具有两个端点的直线的有限部分;

在计算机科学中,射线的定义有了些变化——射线是有向的线段。也就是说它也有一个起点和一个终点。也因此,它可以定义位置、有限长度和方向,也成为了计数几何和图形中的重要组成部分。

image

根据新的射线定义,我们可以这么来表示射线:

\[p(t)=\vec{p_0}+t\vec{d} \]

其中 \(\vec{p_0}\) 是矢量表示的射线原点的坐标,\(\vec{d}\) 是射线延伸的方向和长度,而 \(t\) 限制在\([0,1]\)内。当然,也可以稍微变形成下面这种形式:

\[p(t)=\vec{p_0}+t\hat{d} \]

这时,\(t\)的范围就可以变为\([0,l]\)并且用来表示射线长度。


至于直线(介绍的内容仅用于二维),则可以用一阶线性函数的方式表示:

\[y = kx + y_0 \]

这是熟悉的斜截式表达,但不能表示直线垂直时的情况。所以我们将它以隐含形式改写:$$ ax + by = d$$如果让矢量\(\vec{p}=[x,y]\)\(\vec{n}=[a,b]\),就可以得到用矢量表示的直线定义:

\[\vec{p}·\vec{n}=d \]

什么意思呢?说的就是从 \(p\) 点到原点的距离在 \(\vec{n}\) 方向的投影长度为 \(d\) 的所有点的集合。

image

3.球体和圆形

球体(圆形同理)的表示很简单,只需要描述中心 \(\vec{c}\) 和半径 \(r\)

\[\begin{Vmatrix} \vec{p} - \vec{c}\end{Vmatrix} = r \]

\(\vec{p}\) 表示球面任意一点,将它化为坐标形式就得到了隐式定义:$$(p_x-c_x)^2 + (p_y-c_x)^2 + (p_z - c_Z)^2= r^2$$

4.包围盒

「包围体」是用于近似复杂几何对象的大小的简单几何体, 球体因其简单的表示和旋转不变的特点,被应用于简单的包围体,称为 「包围球」,而另一种同样有此应用的是 「包围盒」,它是一个简单的长方体。

image

包围盒有两种:一种是与体坐标轴轴向对齐的包围盒(英文缩写AABB),另一种是与世界坐标轴对齐的定向的包围盒(英文缩写OBB)。我们将围绕前者进行讨论,因为后者与前者的差异仅在与包围盒对齐的坐标空间,完全可以将它看成是特殊的AABB,并且前者更易于创建和使用。


接下来介绍一下AABB的表示方法。我们只需记录两个具有特殊意义的顶点:

\[\vec{p}_{min}=[x_{min},y_{min},z_{min}], \vec{p}_{max}=[x_{max},y_{max},z_{max}] \]

包围盒的任意一点坐标都不会超出二者,它们实际上就是包围盒的一条体对角线(具体哪条要看坐标空间)的两端。
根据这两点,我们就可以直接获得其它的6个包围盒顶点,从而定义整个包围盒,而且还可以利用它们计算包围盒的中心点、包围盒的长宽高等等。


相比包围球,AABB主要有以下优点:

  1. 计算一个物体的包围盒比计算包围球更简单(可自行搜索计算包围球的算法哦)。
  2. AABB可以提供更紧密、贴合物体的包围体。毕竟包围盒拥有长宽高三个可分配的自由度,比起包围球单单只有半径,确实更好适配物体。

当然,具体情况还要具体分析,并没有哪种绝对好的说法(否则早就被淘汰了)。


有时我们需要获取一个物体在另一个坐标空间下的AABB,这一般不会选择在新坐标空间下重新计算对象的AABB,而是对已有的AABB进行变换。(应该能听懂吧

image

经过变换后的包围盒通常会比原本的大(因为需要对齐的轴向不同了,适配的尺寸就会相应调整),我们可以利用原本的AABB快速生成新的AABB。只需要抓住原本的 \(\vec{p}_{min}\)\(\vec{p}_{max}\),通过以下伪代码的逻辑,我们即可根据变换矩阵 \(m\) 的各个元素直接算出新的 \(\vec{p}_{min}\)\(\vec{p}_{max}\),从而得到新的AABB:

////min和max初始化变换矩阵的平移值,也就是4x4矩阵中的最后一行
min = max = getTransition(m);
//接下来的变换只需考虑3x3部分即可
for(i = 1; i <= 3; ++i)
{
    if(m.mi1 > 0.0f)//mij表示变换矩阵第i行第1列的元素,它们用于变换x
    {
        min.x += m.mi1 * box.min.x;
        max.x += m.mi1 * box.max.x; 
    }
    else
    {
        min.x += m.mi1 * box.max.x;
        max.x += m.mi1 * box.min.x;
    }
    if(m.mi2 > 0.0f)//mij表示变换矩阵第i行第1列的元素,它们用于变换x
    {
        min.y += m.mi2 * box.min.y;
        max.y += m.mi2 * box.max.y;
    }
    else
    {
        min.y += m.mi2 * box.max.y;
        max.y += m.mi2 * box.min.y;
    }
    if(m.mi3 > 0.0f)//mij表示变换矩阵第i行第1列的元素,它们用于变换x
    {
        min.z += m.mi3 * box.min.z;
        max.z += m.mi3 * box.max.z;
    }
    else
    {
        min.z += m.mi3 * box.max.z;
        max.z += m.mi3 * box.min.z;
    }
}

5.平面

模仿直线的隐含形式定义的逻辑,将它扩展到三维,我们就得到了以隐含形式表达的平面:

\[ax + by + cz = d(标量表达法) \]

\[\vec{p} · \vec{n} = d(矢量表达法) \]

我们同样也可以(通常也确实这么做)将 \(\vec{n}\) 设置成单位矢量,这可以便利于一些相关计算。


还有一个我们初中就学过的定义平面的方法:3个非共线点(两条不平行的直线)就可以指定一个平面。

image

但偶尔我们会想要计算一组超过3个点的平面方程,而这些点,可能并没有那么规矩,这时我们就需要 “最佳拟合”平面 的方法。
假设有这么一组点 \(\vec{p}_1\)\(\vec{p}_2\)……\(\vec{p}_n\)
首先计算最佳拟合平面的法线 \(\vec{n}\)(这里利用到了叉积,所以不要对为什么既有加号又有减号感到奇怪啊):

\[n_x = \sum_{i=1}^n(z_i+z_{i+1})(y_i-y_{i+1}), \]

\[n_y = \sum_{i=1}^n(x_i+x_{i+1})(z_i-z_{i+1}), \]

\[n_z = \sum_{i=1}^n(y_i+y_{i+1})(x_i-x_{i+1}) \]

然后计算最佳拟合 \(d\) 值:

\[d = \frac{1}{n}\sum_{i=1}^n(\vec{p}_i·\vec{n}) = \frac{1}{n}(\sum_{i=1}^n\vec{p})·\vec{n} \]

6.三角形

三角形在建模和图形中具有基础意义上的重要性,复杂三维对象的表面都是由多个三角形近似而来的。


要定义一个三角形,只需要记录3个顶点就可以。但这些点的顺序是很重要的,它们会决定这个三角形的正反面(就是涉及法线的计算)。

image

三角形的周长和面积我们小学就会计算了,但这里要再聊聊面积的计算。由于我们记录的是三角形的3个顶点的坐标,所以并不能直接获取三角形的高,这时我们可以看看另一种单独通过顶点计算思路(仅用于2维的三角形,3维有其它方法):

image

如上图,你应该能发现,无论什么形状的三角形,将3个顶点向下沿至x轴,都能得到一大两小,共三个直角梯形,在上图就是s3大,s1、s2小。那用s3-(s1+s2)就可以得到三角形的面积了。考虑到正负号的问题,我们可以用下式进行计算:

image

垂直平移三角形并不会影响它的面积,并且可以简化计算,比如让三角形的一个顶点的 \(y\) 移至\(y = 0\)的位置就可以少算一个式子,所以,最终我们可以得到计算公式:

image

而在三维中,利用叉积(小复习:叉积结果的大小 = 两矢量构成的平行四边形面积)就可以得到三角形。给定来自三角形的两个边矢量 \(\vec{e}_1\)\(\vec{e}_2\),它的面积:

\[A = \frac{\begin{Vmatrix} \vec{e}_1 \times \vec{e}_2 \end{Vmatrix}}{2}\]


为了更好求得三角形所在平面的任意点,我们需要一个三角形表面相关但独立于三角形“存在”的坐标空间,重心空间就是这样一个坐标空间,它在图形学中应用也十分广泛。
三角形平面中的任意点都可以表示为顶点的加权平均值,这些加权称为重心坐标。下面看看将标准笛卡尔坐标转换为重心坐标的方法。

image

在二维中,可以通过一组方程来解决:

image

而它最终可转化为子三角形面积比率的问题:

\[b_1 = \frac{A(T_1)}{A(T)},b_2 = \frac{A(T_2)}{A(T)},b_3 = \frac{A(T_3)}{A(T)} \]

在三维中,会有3个未知数和4个方程,将不能直接求解。一种办法是将三角形投影到3个基本平面之一,转化为2维计算。但不能随意投影,要考虑三角形可能出现在某平面的投影为0的情况,所以可以选择投影到具有最大绝对值的坐标所在基本平面,以保证投影面积最大化,;另一种是直接通过叉积来计算出子三角形的面积,它的计算量比前者稍大,但不需要if判断。


将重心坐标\((b_1,b_2,b_3)\)与三个顶点相乘求和,就可以转换回标准笛卡尔坐标:

image

我们可以看看具体的例子:

image

不难发现,在重心空间下,三个顶点具有很简单的形式:\(\vec{v_1} = (1,0,0)\)\(\vec{v_2} = (0,1,0)\)\(\vec{v_3} = (0,0,1)\);而在三角形之内的点坐标都在 \([0,1]\) 范围内,三角形外的任何点都至少有一个坐标。重心坐标可以将平面网格化为与原始三角形大小相同的三角形:

image

7.多边形

多边形,由顶点和边组成的平面对象。但实际的模样可能比说的更复杂。

image

通过添加成对的接缝可以将复杂的多边形转换为简单的多边形:

image

多边形还分为自相交与非自相交,不过一般都会避免出现自相交多边形,估不再谈。


简单多边性还可以进一步分为凸面和凹面:

image
对人而言,这很好区分,就看有没有“凹陷”就可以了,但要计算机让如何识别一个多边形是凹还是凸呢?

一种方法是检查顶点的角度之和。首先需要点积测量外角和内角中较小的一个,将它当做内角,那么凸多边形的“内角”和都为 \((n-2)180^\circ\),而凹多边形的总和将小于\((n-2)180^\circ\)

image

另一种方法是逐点搜索是否有作为凹陷点的顶点。基本思路是,每个顶点都应朝同一个方向转动,任何沿相反方向转动的顶点都是凹陷点。这要用到叉积,且最好已知该多边形的法线。

总之,任何确定多边形凹凸的方法都存在细微的困难,这里就不再展开了。


我们曾提到过,任何复杂的几何图形都可以由三角形表示,多边形便是所指的复杂几何图形。但将多边形划分为三角形并不容易,至少凹多边形是如此。
不过好在,凸多变形的三角剖分还是比较容易的。只需要选择一个顶点,再围绕该顶点进行扇形分割即可。

image