一、引言
在计算机图形学中,我们经常需要用数学方法描述一个物体的形状。例如,一个球体、一张曲面,或者一个复杂的三维模型,都可以通过不同的数学方式进行表示。
那么,如何用数学语言描述一个物体?
一种直观的想法是,直接记录物体表面上所有点的坐标。但对于复杂的三维物体来说,表面上的点可能非常多,直接存储所有点不仅占用空间,而且难以精确描述连续的曲面。
另一种思路是,不直接记录表面上的所有点,而是定义一个数学公式,通过这个公式来描述物体的形状。
根据描述物体的方式不同,我们可以将几何表示大致分为三类:
-
隐式表示(Implicit Representation):通过数学方程或函数描述物体。
-
参数化表示(Parametric Representation):通过参数和函数,将参数空间中的点映射到物体表面。
-
显式表示(Explicit Representation):直接存储或构造物体的几何元素,例如点、曲线和网格。
这三种方法各有优缺点,适用于不同的场景。
二、隐式表示:用公式定义物体
2.1 什么是隐式表示?
隐式表示的核心思想是:不直接描述物体表面上的每一个点,而是定义一个函数,让满足特定条件的空间点构成物体表面。
以三维空间中的球体为例。
假设一个球体的球心位于原点,半径为 (r),那么它的表面可以用以下方程描述:
x²+y²+z²=r²
这个方程并没有直接列出球面上有哪些点,而是规定了球面上所有点必须满足的条件。
例如,当 (r=1) 时:
x²+y²+z²=1
所有满足该方程的三维空间点连在一起,就构成了一个单位球的表面。
我们还可以进一步定义一个隐式函数:
f(x,y,z)= x²+y²+z²
通过判断函数值,可以区分点与球体之间的关系:
-
(f(x,y,z)=0):点位于球体表面。
-
(f(x,y,z)<0):点位于球体内部。
-
(f(x,y,z)>0):点位于球体外部。
这就是隐式表示的一个重要优势:判断一个点在物体内部、外部还是表面上,往往非常方便。
如果需要知道物体的点的具体位置,就会变的很麻烦了。
2.2 隐式表示的典型例子
1. CSG:构造实体几何
CSG(Constructive Solid Geometry,构造实体几何)是一种通过组合基本几何体来构造复杂物体的方法。
例如,我们可以使用一个立方体和一个球体构造新的物体,常见操作包括:
-
并集:将两个物体合并。
-
交集:保留两个物体共同占据的部分。
-
差集:从一个物体中减去另一个物体。

通过并集,交集,差集也就可以使用规律的几何物体来表示出不规则的物体。
2 SDF:有符号距离函数
SDF 的全称是 Signed Distance Function,即有符号距离函数。
它不仅可以判断点在物体内部还是外部,还可以描述点到物体表面的距离。
对于一个球体,可以定义:
d(x,y,z)=sqrt{x^2+y^2+z^2}-r
其中:
-
d = 0:点在球面上。
-
d < 0:点在球体内部。
-
d > 0:点在球体外部。
对于球体而言,这个函数的绝对值就是点到球面的最短距离。
SDF 常用于实时渲染、光线步进、三维建模和几何融合等领域。
在几何融合中,SDF是一个非常重要的函数。

几何融合步骤:
第1步:定义两个物体的符号距离函数(SDF)
首先,分别定义物体 A 和物体 B 的符号距离函数。
-
dA(p):表示空间中的任意点 p 到物体 A 表面的符号距离。
-
dB(p):表示空间中的任意点 p 到物体 B 表面的符号距离。
这里的 SDF 是 Signed Distance Function 的缩写,中文称为符号距离函数。
通常情况下:
-
距离值小于 0,表示点在物体内部。
-
距离值等于 0,表示点位于物体表面。
-
距离值大于 0,表示点在物体外部。
例如,假设物体 A 是左侧的球体,物体 B 是右侧的球体。对于空间中的任意一个点 p,我们可以分别计算它相对于两个球体表面的符号距离,得到 dA(p) 和 dB(p)。
第2步:使用平滑融合函数(Smooth Union)
得到两个物体的 SDF 后,就可以通过布尔运算将它们合并。
普通并集:直接取最小值
计算公式:
dHard = min(dA, dB)
其中,min 表示取两个数中较小的值。
这种方法可以将两个物体合并成一个整体,但连接区域可能出现明显的拼接痕迹,缺少平滑过渡效果。
平滑并集:使用 Smooth Min 函数
计算公式:
dSmooth = smooth_min(dA, dB, k)
其中:
-
dA:当前点到物体 A 的符号距离。
-
dB:当前点到物体 B 的符号距离。
-
k:平滑融合参数,用来控制融合区域的宽度。
-
dSmooth:融合之后得到的符号距离值。
这里的 smooth_min 表示平滑最小值函数。与直接取最小值不同,它会在两个物体距离接近的区域,对距离值进行平滑混合,从而形成自然的过渡曲面。
参数 k 控制融合效果。通常来说,k 越大,平滑过渡区域越宽,两个物体看起来就越像橡皮泥一样自然地连接在一起。
需要注意,具体的融合效果还取决于所使用的 smooth_min 函数实现。
第3步:得到融合之后的整体 SDF 场
经过平滑并集运算后,整个三维空间中的每一个点都可以获得一个新的符号距离值(SDF),记作 dSmooth。
这个距离场描述了融合后物体的整体形状。
判断点的位置时,可以根据距离值的正负进行区分:
-
dSmooth 小于 0:点位于融合物体内部。
-
dSmooth 等于 0:点位于融合物体的表面,也就是需要渲染的曲面。
-
dSmooth 大于 0:点位于融合物体外部。
因此,只需要找到整个三维空间中距离值等于 0 的位置,就可以提取融合后物体的表面,并进一步进行三维可视化或渲染。
三、参数化表示(Parametric Representation)
3.1 基本思想
参数化表示的核心思想是:通过参数和函数,直接生成物体表面上的点。
对于三维曲面,可以使用两个参数 u 和 v,定义三个坐标函数:
x=x(u,v)
y=y(u,v)
z=z(u,v)
因此,曲面可以表示为:
P(u,v)=(x(u,v),y(u,v),z(u,v))
通过函数将(u,v)和坐标(x,y,z)建立联系,只要给定参数 u 和 v,就可以计算出曲面上对应点的三维坐标。
当 u 和 v 在指定范围内变化时,生成的所有点就构成了一个曲面。
3.2 示例:马鞍面
例如,定义一个参数化曲面:
x=u
y=v
z=u^2-v^2
合起来就是:
P(u,v)=(u,v,u^2-v^2)
当 u 和 v 变化时,就会生成一个马鞍形曲面,也就是双曲抛物面。
这种曲面在一个方向向上弯曲,在另一个方向向下弯曲。
参数化表示的优势在于,我们可以通过调整参数范围、改变映射函数或采样参数空间,来生成和控制曲面的形状。

四、显式表示(Explicit Representation)
显式表示是指直接存储或构造几何元素来描述物体。
常见的例子包括点云、三角形网格、OBJ 文件和贝塞尔曲线。
4.1 点云(Point Cloud)
点云由大量离散的三维点构成。
每个点通常包含三个坐标,分别表示它在三维空间中的 x、y、z 位置。
例如,第 i 个点可以表示为:
pi = (xi, yi, zi)
整个点云可以表示为:
P = {p1, p2, ..., pn}
其中,n 表示点云中包含的点的总数,pi 表示第 i 个三维空间点。
例如,通过三维扫描设备扫描一个物体,就可以得到大量分布在物体表面附近的点。
这些点共同描述了物体的形状。
点云的优点是结构简单,能够直接记录采样数据。
但是,点云通常不包含明确的点与点之间的连接关系,因此它本身不一定构成完整的连续曲面。
4.2 OBJ 文件
OBJ 是一种常见的三维模型文件格式。
它可以存储:
-
顶点坐标。
-
纹理坐标。
-
顶点法线。
-
多边形面的顶点索引。
点云只记录点的位置,而三角形网格还会记录顶点之间的连接关系。
因此,网格能够更明确地描述物体表面的结构,方便渲染、纹理映射和几何编辑。

需要注意,OBJ 是一种文件格式,而不是一种独立的数学表示理论。
五、贝塞尔曲线(Bézier Curve)
5.1 基本思想
贝塞尔曲线是一种通过少量控制点来控制曲线形状的数学方法。
它广泛应用于计算机图形学、字体设计、矢量绘图和动画等领域。
贝塞尔曲线不需要直接存储曲线上的所有点,而是通过控制点和参数计算生成曲线。
5.2、贝塞尔曲线的基本性质
贝塞尔曲线不仅可以通过控制点灵活地控制曲线形状,还具有一些重要的数学性质。这些性质使它在计算机图形学、字体设计、矢量绘图和动画轨迹生成等领域得到了广泛应用。
1、端点插值性质
贝塞尔曲线一定经过起点和终点。
对于三次贝塞尔曲线,其起点和终点分别为:
起点:B(0) = P0
终点:B(1) = P3
其中,P0、P1、P2、P3 是三次贝塞尔曲线的四个控制点。
这说明,贝塞尔曲线的第一个控制点决定曲线的起点,最后一个控制点决定曲线的终点。
需要注意的是,中间的控制点 P1 和 P2 通常不在曲线上,而是用于控制曲线的形状和弯曲方向。
2、切线性质
贝塞尔曲线在起点和终点处的切线方向,可以通过相邻控制点的位置关系确定。
对于三次贝塞尔曲线:
起点处的导数:
B'(0) = 3 × (P1 - P0)
终点处的导数:
B'(1) = 3 × (P3 - P2)
从这两个公式可以看出:
-
起点处的切线方向由 P0 指向 P1 的方向决定。
-
终点处的切线方向由 P2 指向 P3 的方向决定。
因此,如果希望改变曲线起点附近的方向,可以调整 P1 的位置;如果希望改变曲线终点附近的方向,可以调整 P2 的位置。
这也是三次贝塞尔曲线能够灵活控制曲线形状的重要原因。
3、仿射变换性质
贝塞尔曲线具有仿射变换性质。
简单来说,如果对贝塞尔曲线的所有控制点进行相同的仿射变换,那么得到的新曲线,就等于对原来的曲线进行相同的变换。
常见的变换包括:
-
平移:改变曲线在空间中的位置。
-
旋转:改变曲线的朝向。
-
缩放:改变曲线的大小。
-
剪切:改变曲线的倾斜形状。
例如,将三次贝塞尔曲线的四个控制点全部向右移动一定距离,生成的曲线也会整体向右移动相同的距离。
因此,在图形变换过程中,我们不需要重新计算曲线的每一个点,只需要对控制点进行相应的变换即可。
5.3、二次贝塞尔曲线
二次贝塞尔曲线由三个控制点构成:
-
P(0):起点。
-
P(1):中间控制点。
-
P(2):终点。
-
其计算公式可以用普通文本表示为:

需要取一个时间 t ,这个 t 就表示曲线的 "进度条" ,可以通过进度条,用来取出曲线上面的一个点。
对于二次贝塞尔曲线来说,根据 t 在线段P0,P1上面进行插值求出插值点,在P1,P2同理求出插值点,在两个求出插值点上面再次进行插值,求出在 t 时刻的贝塞尔曲线的点。
最终可以得出下面公式:
B(t) = (1 - t)² × P(0) + 2 × (1 - t) × t × P(1) + t² × P(2)
其中,P(1) 用来控制曲线的弯曲程度和方向。
当中间控制点的位置发生变化时,曲线的形状也会随之改变。
二次贝塞尔曲线结构简单,适合描述较为简单的弧形。
5.3 三次贝塞尔曲线
三次贝塞尔曲线由四个控制点构成:
-
P(0):起点。
-
P(1):控制起点附近的曲线方向。
-
P(2):控制终点附近的曲线方向。
-
P(3):终点。
其计算公式可以用普通文本表示为:

B(t) = (1 - t)³ × P(0) + 3 × (1 - t)² × t × P(1) + 3 × (1 - t) × t² × P(2) + t³ × P(3)
与二次贝塞尔曲线相比,三次贝塞尔曲线多了一个控制点,因此可以更加灵活地调整曲线的形状。
在计算机图形学中,三次贝塞尔曲线应用非常广泛,例如字体轮廓、矢量图形、路径绘制和动画运动轨迹等。
5.4 为什么要使用分段贝塞尔曲线?
假设我们有很多数据点,希望曲线经过这些点。
如果直接把这些点当作一条贝塞尔曲线的控制点,曲线通常不会经过所有中间控制点。
而且,当控制点数量过多时,曲线的整体形状也会变得难以控制。

因此,实际应用中经常采用分段贝塞尔曲线。
例如,使用多段三次贝塞尔曲线连接一系列指定点,每段曲线都使用四个控制点。
这样既可以让曲线经过指定的连接点,又可以分别调整不同曲线段的形状。
但是,仅仅让相邻曲线段的端点重合,并不意味着曲线一定平滑。我们还需要考虑曲线的连续性。
六、贝塞尔曲线的连续性
当多段贝塞尔曲线连接在一起时,需要考虑连接处是否平滑。常见的连续性条件包括 C0 连续、C1 连续和 G1 连续。
它们的区别主要在于:连接处的位置是否重合,以及两侧曲线的切线方向和变化速度是否一致。
6.1 C0 连续:位置连续
C0 连续要求相邻曲线段的连接点重合。
假设第一段曲线为 B1,第二段曲线为 B2,那么需要满足:
B1(1) = B2(0)
其中,B1(1) 表示第一段曲线的终点,B2(0) 表示第二段曲线的起点。
这个条件说明,第一段曲线的终点必须与第二段曲线的起点处于同一个位置。

C0 连续只能保证曲线不会断开,但不能保证连接处没有尖角。因此,两段曲线虽然连接在一起,仍然可能看起来不够平滑。
6.2 C1 连续:一阶导数连续
C1 连续不仅要求相邻曲线段的位置连续,还要求连接处的一阶导数(切线的方向和大小)相同。
需要满足以下两个条件:
位置连续:
B1(1) = B2(0)
一阶导数连续:
B1'(1) = B2'(0)
这里的 B1'(1) 和 B2'(0) 分别表示第一段曲线终点处、第二段曲线起点处的导数。
可以将导数理解为曲线在某个位置的切向量,它能够反映曲线在该位置的切线方向和变化速度。

为了使两段曲线满足 C1 连续,需要满足以下条件:
第一,连接点重合:
P3 = Q0
第二,连接处的切向量(切线的方向和大小)相同:
P3 - P2 = Q1 - Q0
这意味着连接处两侧的切线方向相同,并且切向量的大小也相同。
因此,C1 连续能够让曲线在连接处具有更加平滑的过渡效果。
七、曲面细分
1、什么是曲面细分?
在计算机图形学中,曲面细分(Subdivision Surface)是一种通过不断细分多边形网格,并按照特定规则调整顶点位置,使模型逐渐变得平滑的技术。
它的基本思想是:
-
拓扑细分:在原有网格的边或面上生成新顶点,并建立新的边和面。
-
几何调整:根据细分规则计算新顶点的位置,同时更新旧顶点的位置。
-
重复迭代:对细分后的网格再次执行上述过程,使模型逐渐逼近平滑曲面。
因此,曲面细分并不只是简单地增加三角形数量。若只把每个三角形切成四个更小的三角形,而不调整顶点位置,模型的表面通常仍然保持原来的形状。
真正让模型变得平滑的是顶点位置的重新计算。
2、Loop 细分:针对三角形网格
Loop Subdivision 是一种经典的三角形网格细分方法。
它的特点是:每个原始三角形被拆分成四个新的三角形,同时重新计算边上的新顶点和原始顶点的位置。
对于生成的点来说:

内部边的两个端点为 A、B,相邻三角形的另外两个顶点为 C、D:
vec3 edgePoint = 3.0 / 8.0 * (A + B)
+ 1.0 / 8.0 * (C + D);
生成边上新顶点之后,原来1个三角形就被切为了4个小三角形了。
对于本来存在的点来说:

图上面的白点为例,旧顶点的变化公式为:

n :顶点的度,即与该顶点相连的边的数量
u :权重系数
n = 3 :u = 3/16
其他情况:u = 3/(8*n)
1 - n*u :旧顶点自身保留的权重
P :旧顶点的坐标
P_neighbors:所有直接相邻顶点的坐标
所以Loop细分也就是生成新顶点,调整旧顶点,重构三角形。
四、Catmull--Clark 细分
对于三角形和四边形都存在的网格,经典的细分方法是 Catmull--Clark Subdivision。
与 Loop 不同,Catmull--Clark 在每一轮细分中,会为每个原始面生成一个面点,为每条原始边生成一个边点,并更新每个原始顶点。
这里可能有点疑问:就是在光栅化之后,所有的网格都会变成了三角形,怎么还会有四边形的网格呢,对于网格细分的操作是在顶点着色器之后,光栅化之后实现的,在OpenGL里面也提供了一个glsl叫做几何着色器可以对视角转化后的顶点进行操作。

-
Ordinary Vertex(普通顶点):顶点度(Valence)等于 4,即有四条边与该顶点相连。这是规则四边形网格中典型的内部顶点。
-
Extraordinary Vertex(奇异顶点):顶点度不等于 4,即与该顶点相连的边数不是 4。图中原始网格的 5 个紫色圆点就是奇异顶点。
经过变化后的图形如下:

可以看到新增了一个面点,两个边点,和两个奇异点。
面点的计算:
计算方法:将该面所有顶点的坐标相加,再除以顶点数量。

公式:
边点的计算:

旧顶点的计算:
原始顶点也需要更新位置,而不是简单地保留在原处。

奇异顶点的数量不变,度数不变(还是原来那5个紫色点,没有新增奇异点),顶点和面片数量暴涨,网格变密,模型表面变得平滑。
Catmull-Clark 一轮细分可以记成一句话:先算面点,再算边点,最后更新旧顶点。
八、边坍缩
一、什么是边坍缩
边坍缩(Edge Collapse)是一种常见的三角形网格简化算法。
它的核心思想是:将一条边的两个端点合并为一个新顶点,同时删除退化的三角形,并更新周围网格的连接关系,从而减少模型的顶点数、边数和三角形数量。

二、二次误差度量
对于边的坍塌,不能随意选择,所以使用到二次误差度量。
也就是找到一个点,使得这个点与要坍缩的两个顶点所连接的各个面的平方和距离最小。
每个平面都像一个约束,新顶点偏离这些平面越多,误差通常就越大。 二次误差度量通过最小化所有相关平面的距离平方和,寻找一个整体上最合适的新顶点位置。
具体过程可以去了解,最后可以得出一个浮点数,浮点数越小说明这条边二次误差度量后的影响越小,还可以顺便算出来另外一个东西,也就是最新顶点坐标。
所以接下来就可以将所有的边记录一下二次误差度量,之后排序,将误差少的优先进行二次误差度量,直到只用规定的边来表示模型。
这里同时会出现一个问题,就是计算出每一条边的二次误差度量后,在我们进行边坍缩之后,会影响这条边附近的边。
可以使用小根堆+版本号解决:
给每条边维护一个版本号。
cpp
struct Edge {
bool alive;
int version;
double cost;
};
将所有边的二次误差度量装入小根堆后,取出堆顶的边,更新边的顶点后,对边附近的其他会受影响的边重新计算二次误差度量,更新边的版本号,之后每一次取出边都与版本号比较,如果不一样,更改边的二次误差度量,重新插入小根堆中。
三、边坍缩的场景
对于游戏和实时渲染里面,很多时候,当照相机在远处的时候,模型是非常小的,也就不需要花费大量的面来构建模型,当照相机靠近的时候,根据照相机的距离来使用合适的面来构造模型。
一般来说,生成好的模型,在制作的时候就会提前用边坍缩的算法制作由不同面构成的模型,判断LOD来决定改用哪一套模型。
-
LOD0:最高精度,物体离相机很近时用
-
LOD1:面数减半,中等距离
-
LOD2:面数更少,更远
-
LOD3:极简模型,很远的时候渲染
每一级LOD模型,都是提前在软件里算好、导出。