GAMES101 - 几何 Geometry

·  阅读 604
GAMES101 - 几何 Geometry

隐式几何

隐式几何 (Implicit Geometry) 就是能够定义一个的关系式f(x,y,z)=0f(x,y,z)=0,只要点 (x,y,z)(x,y,z) 满足该关系式,就可以确定点 (x,y,z)(x,y,z) 在该几何所描述的面上。

例如球体这种简单几何形体,就能以简单的公式:x2+y2+z2=1x^2+y^2+z^2=1 来进行表示。也就是说判断任何一个点在不在几何的面上是很容易的,只要将坐标代入公式进行判断,但是隐式几何判断一个点在哪,是非常困难的,只能依靠想象。

但隐式几何表示一个比球体略微复杂几何,例如心形体时,其公式就会变得非常复杂。因此隐式几何通常使用其他方法来表达较为复杂的几何形体。

(x2+9y24+z21)3=x2z3+9y2z380\begin{array}{r} \left(x^{2}+\frac{9 y^{2}}{4}+z^{2}-1\right)^{3}= x^{2} z^{3}+\frac{9 y^{2} z^{3}}{80} \end{array}

image.png

构造实体几何

构造实体几何 (Constructive solid Geometry) 简称为 CSG ,通过对隐式几何做布尔运算 (Boolean operations) 以构造出复杂几何形体。对几何形体做并 (Union)、交 (Intersection) 和差 (difference) 等布尔运算。因此我们可以使用简单的几何建立更加复杂的布尔表达式,从而得到意想不到的形体。

image.png

距离函数

距离函数 (Distance Functions) 是指,表达空间中任何一个点到你想要表述的几何形体上任意一个点的最小距离的函数。这个距离可以是正的也可以是负的。使用距离函数来描述几何,就是对于任何一个几何都不直接描述他的表面,而是任何一个点到这个表面的最近距离。

如果我认为一个点在表面外,这个点最小距离是正的,如果在内部,算出来最小距离就是个负的。也就是把空间中任何一个点都定义出一个值。当把两个距离函数都算出来后,就可以对两个距离函数做融合 (Blending) 操作

image.png

如果对图 AA 和图 BB 做一个线性的融合,就会得到一个三分之一黑色,三分之一灰色和三分之一白色区域的新图。如果对渐变的图 AA 和渐变的图 BB 做一个距离函数的融合,距离函数以黑白交接处为 00,黑色一侧为负值,白色一侧为正值。那么两图经过融合后,其交界处 00 就处于正中间,使得新图是从左到右由黑色渐变到白色的。

image.png

水平集

距离函数融合后再恢复成表面,只需要把距离函数对应是 00 的位置全找出来就行了。同样的原理,在距离函数不太容易写成解析式时,只要能够以某种表示方式表达出也能表达距离场。水平集 (Level Set) 就是讲函数的表述写在格子上,只需要找到函数值为 00 的曲线,就可以把几何表面提取出来。而这一概念在地理学上已经应用,例如等高线。我们只需要通过对格子进行双线性插值得到任意数值的函数曲线。

image.png

水平集也可以定义在三维空间中的格子上。例如我们可以让密度作为格子,找到所有的满足相同密度的位置,就可提取出三维物体的表面,这与三维纹理有很大的关联。因此水平集在三维空间中的物理模拟 (Physical Simulation) 上也有应用,例如对水滴和水滴进行融合,既可以使用距离函数的方式也可以使用水平集色方式提取融合后的表面。

image.png

分形

几何还有有一种特殊的描述方法被称为分形 (Fractals) 。分形是指自相似,即自己的一部分和整体相像,与递归概念较为相似。自然界中有很多的分形现象,如雪花、西兰花等等。虽然分形的应用不是很多,但是会引起很多复杂问题,在渲染时因为极高的频率变换导致强烈的走样。

image.png

显式几何

显式几何 (Explicit Geometry) 一般只有两种表现方式,一是直接使用多边形面构造出几何形体,一种是通过参数映射的方式,将一张二维的uv图上所有的点通过映射函数来映射到三维空间中的点再组成一个平面。

参数映射

通过参数映射 (Parameter Mapping) 的方式,定义一个函数,将uv的点映射到空间中某一个点。当uv上的点都映射到空间后,就得到了一个对应的几何。

f:R2R3;(u,v)(x,y,z)f: \mathbb{R}^{2} \rightarrow \mathbb{R}^{3} ;(u, v) \mapsto(x, y, z)

对于一个显式几何,我们想要判断一个点在不在表面上、表面内几何表面外是很困难的,除非你通过看出了这个几何是什么形体。因此有些情况适合隐式有些情况适合显式,目前没有完美的办法解决几何显示的问题,只能根据需要选择显式还是隐式。

image.png

多边形面

多边形面 (Polygon Mesh) 是使用的最多的一种显式几何。任何多边形面实际上都可以拆成小的三角形面。使用三角形面组成的模型在计算机中通常使用 OBJ (The Wavefront Object File Format) 格式的文件来表示。

OBJ 文件把空间中的顶点坐标、法向量和纹理坐标分开表示,再组合起来。对于自动建模导出的 OBJ 文件中实际上会有许多重复的、冗余的数据。OBJ 文件 通过使用 f(face)顶点/纹理坐标/法线 的形式来组合出三角形面的三个顶点,每三个顶点组成一个面。很显然,相邻的面往往会共用顶点,面与面直接就组合了起来形成模型。

image.png

点云

点云 (Point Cloud) 是指只有点组成的模型,因为只要点表示的足够细致,看不到点之间的缝隙,这样就可以形成一个表面。

随着点云密度降低,几何就会变得稀疏,无法形成一个清晰模型。因此点云通常是扫描出的原始数据,之后再将点云处理成面。

image.png

贝塞尔曲线

贝塞尔曲线 (Bezier Curves) 是用一系列的控制点定义的一条曲线。这些控制点定义的曲线需要满足一些性质,例如从 p0p_0 开始沿着 p0p_0p1p_1 的方向为切线向前走,一直到 p3p_3 结束,并且结束的时候一定是沿着 p2p_2p3p_3 的方向。

通过这四个点,可以定义这个曲线的起点和终点一定是在 p1p_1 和 **p3p_3 **上,并且起始和结束的切线方向一定是 p0p_0p1p_1 的方向和 p2p_2p3p_3 的方向。这样我们就可以得到一条唯一的曲线。

image.png

德卡斯特里奥算法

我们通常使用德卡斯特里奥算法 (de Casteljau Algorithm) 来精准地绘制贝塞尔曲线。若给定三个控制点绘制贝塞尔曲线 (Quadratic Bezier) 。假设如图所示的贝塞尔曲线其点 b0,b1b_0,b_1b1,b2b_1,b_2 之间的连线的绘制时间都为 (0,1)(0,1)

image.png

当任意时间为 t 时,确定 b0,b1b_0,b_1b1,b2b_1,b_2 上动点 b01b_{0}^{1}b12b_{1}^{2} 的位置。将两个动点连接,确定当任意时间为 t 时,动点 b02b_{0}^{2} 的位置。当找到所有的时间 t 时的动点 b02b_{0}^{2} 的位置,即可连成一条贝塞尔曲线。显然德卡斯特里奥算法是一个递归算法。即使由四个控制点绘制的贝塞尔曲线 (Cubic Bezier Curve) 的情况下,根据以上步骤就可以不断地算下去,直到只剩下一个动点,记录下这个点的移动轨迹就绘制出了贝塞尔曲线。

image.png

贝塞尔曲线的代数表示

利用德卡斯特里奥算法算出的贝塞尔曲线,显然是有代数表达式的。并且其一级一级不断分解递归最终只剩下一个点的特性,非常符合杨辉三角。

我们将点分为 n 阶,其上标表示在第几阶,下标表示是该阶的第几个点。例如当任意时间为 t 时,点 b01b_{0}^{1} 应为点 b0,b1b_0,b_1 的线性组合。以此类推可以推出每一阶的每一个动点在任意时间 t 时的坐标。而将前面所有阶的点的公式带入最后一阶的动点可以得出以下公式。

b01(t)=(1t)b0+tb1\begin{array}{l}\mathbf{b}_{0}^{1}(t)=(1-t) \mathbf{b}_{0}+t\mathbf{b}_{1} \end{array}
b11(t)=(1t)b1+tb2\begin{array}{l} \mathbf{b}_{1}^{1}(t)=(1-t) \mathbf{b}_{1}+t \mathbf{b}_{2} \end{array}
b02(t)=(1t)b01+tb11\begin{array}{l} \mathbf{b}_{0}^{2}(t)=(1-t) \mathbf{b}_{0}^{1}+t \mathbf{b}_{1}^{1} \end{array}
b02(t)=(1t)2b0+2t(1t)b1+t2b2\mathbf{b}_{0}^{2}(t)=(1-t)^{2} \mathbf{b}_{0}+2 t(1-t) \mathbf{b}_{1}+t^{2} \mathbf{b}_{2}

image.png

从以上给出的二次贝塞尔曲线的计算公式可以推出,当给定 n+1 个控制点时,可以得到一个 n 阶的贝塞尔曲线,曲线时由在任意时间 t 所给定的控制点的线性组合。而线性组合的系数显然是一个和时间相关的多项式。这个多项式又称为伯恩斯坦多项式 (Bernstein polynomial) 。那么任意阶数的贝塞尔曲线,在任意时间 t 时,任意控制点的位置由伯恩斯坦多项式作为系数对给定控制点的加权。

bn(t)=b0n(t)=j=0nbjBjn(t)\mathbf{b}^{n}(t)=\mathbf{b}_{0}^{n}(t)=\sum_{j=0}^{n} \mathbf{b}_{j} B_{j}^{n}(t)
Bin(t)=(ni)ti(1t)niB_{i}^{n}(t)=\left(\begin{array}{c} n \\ i \end{array}\right) t^{i}(1-t)^{n-i}

三维坐标中的代数表示

贝塞尔曲线同样可以在三维坐标中利用伯恩斯坦多项式进行插值计算。例如给定四个在三维空间中的控制点 b0=(0,2,3),b1=(2,3,5),b2=(6,7,9),b3=(3,4,5)\mathbf{b}_{0}=(0,2,3), \mathbf{b}_{1}=(2,3,5), \mathbf{b}_{2}=(6,7,9), \mathbf{b}_{3}=(3,4,5),其公式也依旧和二维坐标下的计算公式一样,四个点的系数符合杨辉三角,分别为 1,3,3,11,3,3,1

bn(t)=b0(1t)3+b13t(1t)2+b23t2(1t)+b3t3\mathbf{b}^{n}(t)=\mathbf{b}_{0}(1-t)^{3}+\mathbf{b}_{1} 3 t(1-t)^{2}+\mathbf{b}_{2} 3 t^{2}(1-t)+\mathbf{b}_{3} t^{3}

伯恩斯坦多项式实际上就是对 1 自己的 n 阶展开,可以看出同一阶上,将当 i=(0,1,2,3)i = (0,1,2,3) 时的多项式加起来一定等于 1,即给定任意时间 t ,四个控制点多项式值( y 坐标)加起来肯定等于 1

image.png

通过定义一系列和时间有关的多项式,来对不同的控制点进行插值来得到新的点,这个点就是我们定义的曲线的点,这一概念并不只用在贝塞尔曲线。

贝塞尔曲线的性质

  1. 贝塞尔曲线必须过起点和终点。
b(0)=b0;b(1)=b3\mathbf{b}(0)=\mathbf{b}_{0} ; \quad \mathbf{b}(1)=\mathbf{b}_{3}
  1. 三阶贝塞尔曲线的起始位置的切线和结束位置的切线一定是:
b(0)=3(b1b0)\mathbf{b}^{\prime}(0)=3\left(\mathbf{b}_{1}-\mathbf{b}_{0}\right)
b(1)=3(b3b2)\mathbf{b}^{\prime}(1)=3\left(\mathbf{b}_{3}-\mathbf{b}_{2}\right)
  1. 直接对不同的顶点做仿射变换,重新对变换后的顶点画贝塞尔曲线,一定和通过原始的控制点画出的贝塞尔曲线是一模一样的。也就是说对一个贝塞尔曲线进行仿射变换只需要对其控制点进行仿射变换即可,而不用对所有的点进行变换。相反的是,对贝塞尔曲线做投影变换是会导致贝塞尔曲线发生变换的。

  2. 凸包性质:任何一个贝塞尔曲线,任何一个点,在任意时间 t 一定都在给定的几个控制点所形成的凸包 (Convex Hull) 里。

image.png

分段贝塞尔曲线

当贝塞尔曲线的控制点过多时,实际上是不太好控制曲线的形状的。因此我们可以将多个控制点的贝塞尔曲线,以三阶贝塞尔曲线为基本单位,即每四个控制点形成一条贝塞尔曲线 (Piecewise Bezier Curves)

连续性

对于分段贝塞尔曲线,我们需要保证每段贝塞尔曲线连起来还是光滑的。首先,对光滑的最低要求是几何上的连续。即第一段的终点等于第二段的起点,又称为 C0C^0 连续。

image.png

如果分段贝塞尔曲线连接点处的切线也连续,即切线共线、方向相反、距离相同,称为 C1C^1 连续(一阶导数连续)。在实际应用中如果需要更高精度的连续,可以继续保证高阶导数的连续。

贝塞尔曲面

贝塞尔曲面的计算需要用到二次贝塞尔曲面插值 (Bicubic Bezier Surface Patch) 。而二次贝塞尔曲面插值在平面上定义了 4x4 的控制点。将十六个控制点分为四行控制点,则每一行有四个控制点。

我们如果把得到的四个不同的点,认为是另一个贝塞尔曲线的控制点,即水平方向分别做贝塞尔曲线,得到的四个控制点在竖直方向再做一次贝塞尔曲线,最终就得到一个贝塞尔曲面。

image.png

贝塞尔曲面坐标评估

如果想找到贝塞尔曲面上任意一点,我们需要两个时间 tt 。即在水平方向上找到时间 tt ,在竖直方向上找到另一个时间 tt ,可以称为 u,vu,v

  1. 使用 de Casteljau 算法 评估点在时间 uu 时,在四条贝塞尔曲线上的位置,即可得到新的四个控制点。

  2. 然后再使用一次 de Casteljau 算法 ,评估出点在时间 vv 时 所形成的贝塞尔曲线。

image.png

网格细分

网格细分 (Mesh Subdivision) 就好像把一个图像增大它的分辨率一样,让模型拥有更多的细节。因此网格细分需要用到各种算法以引入更多的三角形。但是如果只将一个大三角形拆分为更多的三角形,是不能让三角形的形状发生变化的。因此网格细分实际是两个操作:第一,使三角形的数量增多,即分出更多三角形;调整三角形的位置三角形位置发生变化,使得原来的模型变得更光滑。

image.png

卢氏细分

卢氏细分 (Loop Subdivison) 是针对三角形面的一种曲面细分算法,其命名并不是真的和 “循环” 有什么关系,而是发明这种算法的人的 Family Name 就是 Loop

任何细分都要分为两步,卢氏细分只针对三角形面进行细分和更新顶点位置的操作。而 Loop 细分 首先对一个三角形连接其三条边的中点,即可细分得到四个三角形。然后将三角形的顶点的种类区分开,即分为新的顶点和老的顶点。这是因为 Loop 细分 会对两种不同的顶点,使用不同的规则来改变他们的位置。

image.png

新顶点的更新

一般情况下一定存在两个三角形,共享一条边(边界处的三角形就是特殊情况)。则两三角形再共线上会有一个相交的点 PP 。假设共线的边设为 A,BA,B ,其他顶点设为 C,DC,D 。然后对四个顶点进行加权平均操作。例如取 A,BA,B 的坐标的 3/83/8C,DC,D 坐标的 1/81/8 ,相加后得到点 P 的新位置。因为我们认为距离点 P 近的顶点贡献大,距离远的贡献小。

P=3/8(A+B)+1/8(C+D)P = 3 / 8^{*}(A+B)+1 / 8^{\star}(C+D)

image.png

旧顶点的更新

一般情况下,一个旧顶点会连接很多三角形(如图所示六个三角形)。Loop 细分在更新旧顶点时,一部分由周围的旧顶点决定,一部分由自己决定。设顶点的度为 nn , 则 uu 为一个和 nn 相关的数值。通过一系列的加权计算出更新后的旧顶点。

(1nu)Po+uPsum(n)(1 - n · u ) · P_{o} +\mathrm{u} · P_{sum(n)}

Catmull-Clark 细分

如果模型的网格不是三角形而是四边形的网格,则可以使用 Catmull-Clark 细分。在 Catmull-Clark 细分 中我们定义四边形面 (Quad Face) 、非四边形面 (Non-Quad Face) 、度不为 4 的顶点为奇异点 (Extraordinary Vertex)

image.png

首先 Catmull-Clark 细分 对每条边都取其中点,每个面也要去一个点,这个点可以是重心或者是其他的店。然后将边上的点和面中心的点连起来。我们可以发现经过一次细分后两个度为 5 的奇异点没有变化,并且引入了两个新的度为 3 的奇异点。只要我们在一个非四边形内选择一个点,与其所有边上的点(中点)相连,那么一定会得到一个新的奇异点。而非四边形面经过一次细分操作后却都被我们消灭了,也就是说当我们继续进行细分后,奇异点也不会再增加了。

image.png

顶点的更新

Loop 细分 有所不同的是 Catmull-Clark 细分 将顶点分为三类进行更新。

  • 在面中心的点 (Face Point)
  • 在边中心的点 (Edge Point)
  • 旧顶点 (Old Vertex Point)

Catmull-Clark 细分 并按顺序对三类顶点进行加权平均计算,

image.png

image.png

网格简化

对于一个非常复杂的网格,当处于很远的地方时,实际上我们是不需要渲染这么精细的网格的。这时就需要对模型进行网格简化 (Mesh Simplification)

image.png

在不同的情况下,我们需要选取不同复杂度的模型,这一点有些类似 Mipmap 划分了很多层级的精细度,层次结构的几何和层次结构的图像在这方面来说含义是一样。但是几何的层次结构是很难做的,这是因为不同层级的几何在切换时没有像图像一样的三线性插值等方法可以实现平滑的层级过渡,因此需要考虑在切换时会不会影响观感。

image.png

边坍缩

边坍缩 (Edge Collapsing) 是网格简化的一种方法,正如其字面上的意义,减少网格的边或顶点,使得周围的面形成坍缩的现象,以实现网格简化,但边坍缩的实际应用没那么容易。

image.png

边坍缩需要判断哪些边是重要的哪些边是不重要的,因此需要引入二次误差度量 (Quadirc Error Metrics) 这一方法进行判断。

如下图所示的将面退化为点的一维情形,有一个五个顶点四条线段所构成的形状,我们需要将其简化成由三个顶点构成的蓝色三角形,并且保证蓝色三角形和原本的形状基本一致。

image.png

可以看出,三角形的上的新顶点和要减去的三个顶点有很大的关系,也就是说新顶点应该是旧顶点的某种加权平均的结果。但如果只是简单的加权平均,会发现新顶点无论如何都会比原来的突出部分小了一圈。

如何坍缩边

针对这种情况,人们引入了误差度量的概念,这里采用了二次误差。我们希望新顶点处在的位置,可以最小化误差度量,即新顶点到原本的几个点的距离的平方和达到最小。

如何选取边

回到实际的三维空间中,当要进行边坍缩时,我们可以假设每一条边都要进行坍缩,并计算出每一条边坍缩后的最小二次度量误差,然后从结果中选取误差最小的边开始坍缩。

但是当坍缩了一条边后,显然周围的边便也发生了变化,因此需要更新这个误差集合。也就是说我们需要一个数据结构对误差进行排序并动态更新,因此可以使用优先队列或堆来维护该误差集合。

image.png

我们需要的是一个在全局的物体简化轮廓表示,但现在的算法寻找的却是任意的局部最优解。显然这是典型的贪心算法,并不能保证能找到一个全局最优解。

网格规范化

对于三角形面的网格,其网格上的三角形有大有小,如细长型状的三角形,这实际上会对渲染造成一些不便。

对于这种情况,我们通常要对模型进行网格正规化 (Mesh Regularization) ,使三角形面更像正三角形。但是在改进三角形的质量的同时,不能丢失模型本身的质量。

image.png

分类:
代码人生
分类:
代码人生