参数化(Parameterization)是三维几何处理里最基础也最关键的一环。简单来说,它要为网格上的每个顶点找到一个二维参数坐标,使得整个曲面可以被“摊平”到平面域或球面域上,并且尽量保持形状不被拉伸扭曲。纹理贴图、UV展开、网格重采样、曲面拟合都依赖一个高质量的参数化结果。根据参数域的不同,主流方法分成两大类:圆盘参数化(Disk Parameterization)针对有边界的开放曲面,把它映射到平面单位圆盘上;球面参数化(Spherical Parameterization)针对封闭流形,把它映射到单位球面上。这两类方法虽然目标域不同,但背后共享很多相同的数学工具,本文将逐一展开。

参数化的数学本质与畸变度量
从数学上看,网格参数化就是构造一个分段线性映射 f: M → D,其中 M 是三角网格曲面,D 是目标参数域(平面圆盘或球面)。由于映射是逐三角形线性的,每个三角形内部的畸变可以用雅可比矩阵 J 的奇异值 σ₁ 和 σ₂ 来刻画。当 σ₁ = σ₂ = 1 时映射是等距的,形状完全保留;当 σ₁ = σ₂ ≠ 1 时是保角的,只有均匀缩放;当 σ₁ ≠ σ₂ 时则同时存在角度畸变和面积畸变。
常用的畸变能量函数有几种。对称狄利克雷能量计算 (σ₁² + σ₂² + 1/σ₁² + 1/σ₂²),同时惩罚拉伸和压缩;角度畸变能量只关注 σ₁/σ₂ 的比值;面积畸变则关注乘积 σ₁σ₂。实际算法通常在保角与保面积之间做权衡,因为除了可展曲面之外,一般曲面上不存在既完全保角又完全保面积的参数化。这就是著名的高斯曲率约束:任何参数化方法都无法凭空消除总曲率,只能决定把畸变“堆”到哪里。
圆盘参数化:从Tutte嵌入到角度展平
圆盘参数化处理的是亏格为零、带有边界曲线的开放网格,比如一张人脸模型或半个球壳。最经典的方法是 Tutte 嵌入(也叫重心映射):先把网格边界顶点固定到平面凸多边形上,比如单位圆的等分点,然后让每个内部顶点落在其邻居的加权平均位置上。这可以写成一个线性方程组:
import numpy as np
def tutte_embedding(mesh, boundary_positions):
# boundary_positions: 已知的边界顶点平面坐标
# 构造拉普拉斯矩阵 L,权重可用均匀权重或余切权重
n = mesh.n_vertices
L = mesh.laplacian_matrix() # 余切权重拉普拉斯
inner = mesh.inner_vertices()
bnd = mesh.boundary_vertices()
# 分离内部与边界,求解 L_ii * U_i = -L_ib * U_b
L_ii = L[np.ix_(inner, inner)]
L_ib = L[np.ix_(inner, bnd)]
rhs = -L_ib @ boundary_positions
U_inner = np.linalg.solve(L_ii, rhs)
return assemble_uv(mesh, U_inner, boundary_positions)Tutte 嵌入的巨大优点是数学上可证明无翻转:只要边界固定在凸多边形上且权重为正,解必然是双射。但缺点也很明显,畸变往往较大,特别是边界形状与原边界曲线差异较大时,靠近边界的三角形会被明显拉伸。
为了降低畸变,业界发展出了 LSCM(最小二乘保角映射)和 ABF(角度展平)这类非线性方法。LSCM 通过最小化保角能量求解,只需固定两个顶点去除平移和旋转的自由度;ABF 则先展平角度再重建坐标,两者都能得到质量高得多的结果。现代几何库如 libigl、OpenMesh 都内置了这些实现,一个典型的调用流程是:先检测边界环,初始化一个初始参数化,再用 L-BFGS 迭代最小化对称狄利克雷能量。
球面参数化:封闭曲面的映射策略
当网格是封闭的(比如完整的人头或球状模型),平面参数化就无能为力了,必须把曲面映射到单位球面 S² 上。球面参数化的难点在于映射的每一步都必须保证顶点不塌陷到球心,而且球面上的线性运算不能直接用,需要在切空间或用球面插值处理。
最直接的做法是凸组合投影:先把所有顶点归一化到球面作为初始值,然后迭代地让每个顶点朝其邻居在球面上的加权平均方向移动。一种稳健的写法是先对邻居求平均向量再归一化:
def spherical_projection(mesh, iterations=200):
# 初始化:把顶点位置直接归一化到单位球面
V = mesh.vertices / np.linalg.norm(mesh.vertices, axis=1, keepdims=True)
W = mesh.uniform_weights() # 也可用余切权重
for it in range(iterations):
V_new = V.copy()
for i in mesh.inner_vertices():
avg = np.zeros(3)
for j in mesh.neighbors(i):
avg += W[i][j] * V[j]
V_new[i] = avg / np.linalg.norm(avg) # 投影回球面
V = V_new
return V这个方法实现简单,但对复杂模型容易陷入局部极小,出现翻转三角形。更稳健的路线是多分辨率策略:先对网格做简化,在低分辨率上用容易收敛的方法求出球面映射,然后逐级把结果上采样并优化,最后在原始分辨率上做细化。商业软件中的高质量球形UV展开基本都是这条流水线。
另一类思路是“切开再缝合”:先把封闭网格沿一条割线切开变成开放网格,做圆盘参数化得到平面结果,再通过立体投影(Stereographic Projection)把平面映射回球面,最后缝合割线并迭代消除割线两侧的不连续。这种方法的好处是可以复用成熟的平面参数化求解器,代价是需要处理割线处的缝合误差。
工程实践中的选择与常见坑
选哪种参数化方法,主要看曲面的拓扑结构和下游应用。开放曲面且对速度要求高,Tutte 嵌入加一轮狄利克雷能量优化通常就够了;对纹理质量敏感的模型,建议用 LSCM 或边界自由化的 SLIM 方法;封闭流形做重网格化或球面调和分析,则必须走球面参数化。下表总结了主流方法的适用范围:
| 方法 | 参数域 | 线性/非线性 | 保角性 | 适用场景 |
|---|---|---|---|---|
| Tutte嵌入 | 平面 | 线性 | 无保证 | 快速原型、初始解 |
| LSCM | 平面 | 非线性 | 较好 | 纹理映射 |
| ABF++ | 平面 | 非线性 | 优秀 | 高质量UV展开 |
| 凸组合球面投影 | 球面 | 迭代 | 一般 | 封闭网格 |
| 多分辨率球面法 | 球面 | 迭代 | 良好 | 重网格化、球面信号处理 |
实践中最常见的坑有几个。第一是输入网格不是流形:存在重复顶点、非流形边时任何参数化算法都会出错,预处理阶段必须做网格清理和拓扑检查。第二是忽略割线设计:无论平面还是球面参数化,高曲率区域(如兔子的耳朵尖)必然积累大量畸变,手动引导割线穿过这些区域能显著改善整体质量。第三是权重选择:余cot权重在钝角三角形上会变成负数导致翻转,需要改用中点权重或做符号修正。只要把这些细节处理到位,参数化结果的质量就能达到工程可用的水平。