ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LSCM最小二乘保角参数化:从原理到Python实现的核心指南

LSCM最小二乘保角参数化:从原理到Python实现的核心指南 1. 项目概述为什么你要了解LSCM最小二乘保角参数化做三维模型的都绕不开一个问题怎么把一张立体的曲面“摊平”到二维平面上同时尽量不让它变形。不管是给模型刷贴图、做三维雕刻的纹理映射还是做网格重剖分、形状分析都离不开这个底层操作。网格参数化就是把三维网格上的每个顶点映射到二维UV平面上的过程。而LSCMLeast Squares Conformal Maps最小二乘保角映射就是这类方法里一个极其经典、极其能打的方案。我第一次接触LSCM是被一个纹理映射的需求逼的。当时手上有个扫描出来的雕塑模型要给它贴一张高精度图直接投影肯定不行曲面太复杂接缝处全是撕裂。后来改用LSCM做参数化才算是把问题解决了——它在“尽量保持三角形角度不变”这件事上做得非常出色映射完的UV展开图变形极小贴上去的纹理几乎看不出拉伸。而且关键是它只需要解一个线性方程组速度极快不需要像很多非线性优化方法那样反复迭代。适合谁来学这套东西做游戏美术、三维扫描、计算几何、人脸重建的开发者都很值得掌握。如果你对数学推导不太敏感也没关系LSCM的核心思想并不难懂三角形网格展开时每个三角形的角度尽量保持不变把“保角”这个条件写成数学形式再用最小二乘法去逼近它。这篇文章我会从原理、公式拆解到实操代码、避坑经验完整走一遍LSCM的落地流程。2. 核心原理拆解保角、最小二乘和参数化到底是什么关系2.1 从“摊平橘子皮”说起什么是保角参数化想象一下把一个橘子皮完整地剥下来然后用力压平成一张圆形皮。会发生什么边缘必然会被撕裂或者褶皱。这就说明把一个三维曲面完全平铺到二维平面上绝对保面积的映射几乎不存在但我们可以退而求其次——不保面积只保角度。保角Conformal的意思就是三维曲面上的两条曲线交叉形成夹角映射到二维平面后这个夹角的大小保持不变。换成人话说曲面上的一个小圆映射到UV平面上后仍然是一个圆只是一个放大或缩小过的圆而不是被拉成椭圆。这样做的好处很直观贴纹理时不会有剪切变形图案不会出现不自然的拉伸或扭曲。在数学上从曲面到平面的保角映射可以在每个局部坐标系下用一个复数函数来表示。如果把三维曲面上的点表示为复平面上的复数那么一个映射 (f(u,v)) 要满足柯西-黎曼方程[ \frac{\partial f}{\partial u} i \frac{\partial f}{\partial v} 0 ]这个方程是复分析里的核心它描述的就是“这个函数是共形映射”的条件。对所有三维网格来说如果这个方程在每个点上都严格成立那映射就是严格保角的。但现实很骨感一个拓扑复杂的网格你很难找到一个映射让它处处满足柯西-黎曼方程。那退一步我们去找一个映射让柯西-黎曼方程的残差尽可能小。这就是“最小二乘保角”这个名字的来历——把“保角”从一个必须满足的硬条件变成一个尽可能满足的软目标。2.2 LSCM的数学表达式离散化与能量函数现在我们把连续曲面替换成三角形网格用离散的语言重新表述这个问题。每个三角形面片都有一个局部二维坐标系在这个坐标系里每个顶点的三维坐标 ((x, y, z)) 可以写成二维坐标 ((x, y))然后定义在这个局部坐标系下的复数坐标[ (x, y, z) \rightarrow (u, v) ]我们要找每个顶点对应的 ((u, v))使得每个三角形的映射满足保角条件。网格离散化后柯里-黎曼方程在每个三角形内采样、近似定义在三角形局部坐标系上的复数梯度为[ \frac{\partial f}{\partial u} \frac{1}{2}\left( \frac{\partial y}{\partial v} - \frac{\partial x}{\partial v} \right) ]不过在实际工程中我们通常不需要直接面对这些求导的展开式而是用另一种更清晰、更利于编程实现的方式把每个三角形的“保角误差”定义为一种能量所有三角形的能量求和然后求这个总能量的最小值。具体来说对三角形 (T) 的三个顶点已知其三维局部坐标 ((x_j, y_j)) 和待求的二维UV坐标 ((u_j, v_j))我们可以通过顶点的重心插值定义入射向量。最终LSCM的离散化目标函数为[ E_{LSCM}(U) \sum_{T} |\nabla_T u - \mathrm{rot}_{90}(\nabla_T v)|^2 ]这里 (u) 和 (v) 是待求解的标量函数UV坐标的两个分量(\nabla_T) 是三角形上的梯度算子(\mathrm{rot}_{90}) 是旋转90度的线性算子。整个式子表达了一个朴素的意图在每个三角形上梯度 (u) 和旋转后的梯度 (v) 应该尽可能一致。这个能量是二次的把它展开后待求量 (U (u_1, v_1, \dots, u_n, v_n)^T) 前的系数矩阵是稀疏对称正定的于是最小化问题就化简成了解一个稀疏线性方程组[ A^T A U A^T b ]本质上就是最小二乘法。这一步是整个LSCM的巨大优势不需要迭代不需要调学习率解开这个大方程组结果就出来了。相比那些需要通过梯度下降反复优化的非线性参数化方法LSCM在计算效率上不知道高到哪里去了。2.3 自由度和固定点一个必须想清楚的问题讲LSCM时很多教程不会强调但我这里必须单独拎出来说——这个方程组不是唯一确定的。原因在于保角映射本身自带三个全局自由度严格说还包含缩放分别是二维平移两个自由度、旋转一个自由度、均匀缩放一个自由度。想一想也合理你把一张UV图整体移动一下、转一下、放大缩小一下它的“保角程度”并没变角度还是那些角度。划分到计算上这些自由度会让奇异singular方程组出现。如果不做任何处理直接拿去求解数值上会崩掉或者算出个全是0的平凡解。通常的解决策略是固定至少一个顶点给出确定的UV位置有时还需要固定两个顶点固定一条边的方向。固定顶点的选择有讲究。我的经验是尽量选择在网格边界上距离较远的两个顶点来固定这样能最大程度避免展开后网格被过度压扁或旋转。如果网格拓扑较复杂、有多个边界环可以额外选取几个边界顶点用“软约束”或“硬约束”的方式加入线性方程组中。在后面写代码的段落我会演示怎么把固定点约束构建成方程组的一部分。3. 实操过程从零构建一个LSCM展开器3.1 算法流程总览把理论转成代码并不复杂LSCM的主要流程如下读取三维网格构建半边结构或者邻接关系便于访问每个三角形的三个顶点。对每个三角形计算其局部二维坐标系。方法把第一条边对齐到局部坐标的X轴方向把该三角形的法线对齐到Z轴方向然后将顶点投影到局部坐标平面上。构造能量函数。逐个三角形计算梯度算子组装稀疏矩阵 (A)同时计算右侧向量 (b)。加入固定点约束调解奇异问题。用稀疏线性求解器Eigen、Taucs、SuiteSparse、scipy.sparse.linalg等解方程得到所有顶点的 (u, v) 坐标。后处理把UV坐标归一化到 ([0,1]^2) 区间内输出结果。这个流程在论文里看起来很简单但真正实现时每一步都有坑。下面我会把每个步骤展开给出一段可以直接跑通的Python示例代码方便你对照理解。3.2 局部坐标系构建与梯度算子构建局部坐标系这一步是很多初学者的第一道坎。对每个三角形 ((p_0, p_1, p_2))我希望把法线对齐到Z轴这样三角形在局部坐标下就落在XOY平面上计算二维梯度才会方便。实现方式并不需要真的做一次极坐标变换矩阵乘法。一个更简单的等效做法是在三角形内部定义两个正交基向量 (\mathbf{e}_1, \mathbf{e}_2)让它们都在三角形所在的平面上然后用内积计算每个点的局部二维坐标。具体算法设 (p_0) 为局部坐标原点 ((0,0))设 (X) 轴基向量为 (\mathbf{e}_1 \text{normalize}(p_1 - p_0))。设 (Z) 轴基向量为三角形法线 (\mathbf{n} \text{normalize}((p_1-p_0) \times (p_2-p_0)))。设 (Y) 轴基向量为 (\mathbf{e}_2 \mathbf{n} \times \mathbf{e}_1)。则顶点 (p_i) 的局部坐标为 (( (p_i - p_0) \cdot \mathbf{e}_1, (p_i - p_0) \cdot \mathbf{e}_2 ))。拿到局部坐标后我开始为每个三角形计算梯度。对三角形 (T) 上的线性插值函数 (f_i(x, y))其梯度是一个 (2 \times 1) 的向量可以由顶点函数值 ((f_0, f_1, f_2)) 和顶点局部坐标线性表出。具体地定义如下矩阵[ D_T \begin{bmatrix} x_1 - x_0 y_1 - y_0 \ x_2 - x_0 y_2 - y_0 \end{bmatrix}^{-1} ]那么梯度向量 (\nabla f) 等于 (D_T \cdot \begin{bmatrix} f_1 - f_0 \ f_2 - f_0 \end{bmatrix})。这一步相当于解一个2元线性方程组把函数在两个边方向上的变化率转换为在 (X,Y) 方向上的变化率。3.3 完整代码实现Python SciPy下面这段代码用纯Python和SciPy实现了一个最简LSCM展开器。我平时做原型验证时就是这么写的它可以在小规模网格上直接跑通也方便你在模型上测试效果。import numpy as np import scipy.sparse as sp from scipy.sparse.linalg import spsolve def build_lscm_system(vertices, triangles): n_v vertices.shape[0] n_f triangles.shape[0] # 每行三角形产生4行约束: (du/dx, du/dy, dv/dx, dv/dy) # 每个顶点有两个未知数 u, v n_unknowns 2 * n_v rows_A [] cols_A [] vals_A [] rhs [] # 右侧向量实际没有非零项最终由固定点约束决定 # 依次处理每个三角形 for f in range(n_f): t triangles[f] p0, p1, p2 vertices[t[0]], vertices[t[1]], vertices[t[2]] # 计算局部二维坐标 (x, y) e1 p1 - p0 e1 e1 / np.linalg.norm(e1) nrm np.cross(p1 - p0, p2 - p0) nrm nrm / np.linalg.norm(nrm) e2 np.cross(nrm, e1) def proj(p): v p - p0 return np.array([v.dot(e1), v.dot(e2)]) q0 proj(p0) # 应该等于 [0, 0] q1 proj(p1) q2 proj(p2) # 梯度算子的线性变换矩阵 M np.array([[q1[0] - q0[0], q2[0] - q0[0]], [q1[1] - q0[1], q2[1] - q0[1]]]) # M 的逆对应 D_T invM np.linalg.inv(M) # 三角形的三个顶点索引 t[0], t[1], t[2] # 用u分量构造两行约束: du/dx, du/dy # 先构造局部梯度系数矩阵 grad_coeff invM [[f1-f0], [f2-f0]] # 我们要把这个线性关系填入A矩阵 # 对u来说grad_u c0 * u0 c1 * u1 c2 * u2c是固定系数 # 所以A两行里u分量对应的列系数就是c0,c1,c2 # 对v分量同理但要注意LSCM能量要求旋转一致性 # 这里为了方便理解我们直接构造4行约束 # (du/dx - dv/dy) 0, # (du/dy dv/dx) 0 # 这正好是柯里-黎曼方程的离散化。 # 计算梯度系数相当于把顶点值映射到梯度再映射到差 # du/dx invM[0, 0]*(u1-u0)invM[0, 1]*(u2-u0) # (invM[0,0]invM[0,1])*u0 (-invM[0,0])*u1 (-invM[0,1])*u2 # du/dy类似。 # 我们直接用向量的方式做 grad_coeff invM np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) # 4行约束的系数行索引 base_row f * 4 # 约束1: du/dx - dv/dy 0 for i, coef in enumerate(grad_coeff[0, :]): col_u 2 * t[i] # u 对应列 col_v 2 * t[i] 1 # v 对应列 rows_A.append(base_row 0) cols_A.append(col_u) vals_A.append(coef) rows_A.append(base_row 0) cols_A.append(col_v) vals_A.append(-coef if i 0 else 0) # hold on, v这部分的系数要单独算 # 用稍后更规范的方式重构——注意完整代码见文末说明 # 由于上面注释里留下了占位实际完整构造见下方build_constraints return None写到这里我突然意识到直接在演示代码里写出完整、严格正确的组装逻辑篇幅会很长而且容易把阅读重点带偏。我这里提供一种更便于工程上一步步理解和调试的写法先逐三角形计算梯度系数矩阵再按下面描述的“分块组装”思路把系数填入稀疏矩阵。核心思路是对每个三角形 (T)构造两个“梯度系数”向量[ G_u D_T \cdot \begin{bmatrix} u_1 - u_0 \ u_2 - u_0 \end{bmatrix},\quad G_v D_T \cdot \begin{bmatrix} v_1 - v_0 \ v_2 - v_0 \end{bmatrix} ]然后 LSCM 的能量是 (|G_u - J(G_v)|^2)在2D中 (J) 把 ((a, b)) 映射到 ((b, -a))。所以两个残差分别是残差1(G_{u,x} - G_{v,y})残差2(G_{u,y} G_{v,x})把这两个残差写成顶点 (u_0, u_1, u_2, v_0, v_1, v_2) 的线性组合就得到两行稀疏矩阵行。最后所有三角形拼接成一个大矩阵求最小二乘解。这样写起来紧凑且不易出错。完整实现放在第4节里作为“可直接抄作业”的参考代码。3.4 固定顶点和求解技巧在设计矩阵 (A) 和右侧向量 (b) 组装完成后固定顶点的最简做法是把对应未知数所在的行替换为一行单位向量右侧设为期望的UV位置。比如固定顶点 (i)让它 (u0, v0)就分别替换 (u_i) 所在行和 (v_i) 所在行。我这里更推荐用“软约束”的方式在原能量后面加一个大权重项 (\lambda \sum_{i \in F} |u_i - u_i^{fix}|^2)这样既不会让矩阵结构变复杂又能保持数值稳定。(\lambda) 通常取 (10^6) 或者更大相当于硬性固定。实践中我把一个边界顶点固定到 ((0,0))另一个边界顶点固定到 ((1,0))就可以同时消除平移、旋转和缩放自由度得到稳定的解。求解器方面因为我这边组装出来的矩阵是一个超定的 ((4N_F) \times (2N_V)) 矩阵直接求最小二乘解的话要用正规方程 (A^T A U A^T b)或者用QR分解、SVD求最小范数最小二乘解。对于上万顶点的网格正规方程的稀疏Cholesky分解在绝大多数情况下都能快速求解。Python里面可以用scipy.sparse.linalg.spsolve配合A.T A的CSR格式效率不错当网格达到几十万甚至上百万顶点时可以换用Eigen的稀疏LU或共轭梯度预处理器。别指望用np.linalg.lstsq那是稠密算法几万顶点就把内存吃光了。4. 核心环节实现一份能跑通的最小实现与参数调优4.1 一个简洁清晰的LSCM实现示例这里我给出一个风格简洁、完整可运行的参考代码。我会用numpy和scipy并且刻意让注释“讲人话”方便你对照公式理解。import numpy as np import scipy.sparse as sp from scipy.sparse.linalg import spsolve def lscm_param(vertices, triangles): nv len(vertices) nt len(triangles) # 未知量顺序先所有u再所有v # 即 X [u, v] 长度 2*nv n_unknowns 2 * nv rows [] cols [] data [] b [] row_cnt 0 def add_entry(r, c, val): rows.append(r) cols.append(c) data.append(val) for t in triangles: p0, p1, p2 vertices[t[0]], vertices[t[1]], vertices[t[2]] # 局部正交基 e1 p1 - p0 e1 e1 / np.linalg.norm(e1) nrm np.cross(p1 - p0, p2 - p0) nrm nrm / np.linalg.norm(nrm) e2 np.cross(nrm, e1) def local2d(p): v p - p0 return np.array([v.dot(e1), v.dot(e2)]) q0 local2d(p0) q1 local2d(p1) q2 local2d(p2) # 梯度映射矩阵 M np.array([[q1[0] - q0[0], q2[0] - q0[0]], [q1[1] - q0[1], q2[1] - q0[1]]]) invM np.linalg.inv(M) # G_u invM [u1-u0, u2-u0] # G_v invM [v1-v0, v2-v0] # 残差1: G_u[0] - G_v[1] # 残差2: G_u[1] G_v[0] # 构建系数先构造 grad_u0, grad_u1, grad_u2 # 对任意函数fgrad_f_x和grad_f_y均为 f0,f1,f2 的线性组合 # 矩阵形式 grad_f invM [[-1, 1, 0], [-1, 0, 1]] [f0,f1,f2]^T aff invM np.array([[-1.0, 1.0, 0.0], [-1.0, 0.0, 1.0]]) # aff shape: (2,3) # aff[0, k] 是 df/dx 关于 f_k 的系数 # aff[1, k] 是 df/dy 关于 f_k 的系数 # 残差1 du/dx - dv/dy 0 for k in range(3): add_entry(row_cnt, t[k], aff[0, k]) # u_k 的系数 add_entry(row_cnt, t[k] nv, -aff[1, k]) # v_k 的系数带负号 b.append(0) row_cnt 1 # 残差2 du/dy dv/dx 0 for k in range(3): add_entry(row_cnt, t[k], aff[1, k]) # u_k 的系数 add_entry(row_cnt, t[k] nv, aff[0, k]) # v_k 的系数 b.append(0) row_cnt 1 A sp.coo_matrix((data, (rows, cols)), shape(row_cnt, n_unknowns)).tocsr() b np.array(b, dtypefloat) # 固定两个顶点索引0固定到 (0,0), 索引1固定到 (1,0) # 这里用软约束方式权重给大 weight 1e10 for i, uv in [(0, (0.0, 0.0)), (1, (1.0, 0.0))]: row_idx A.shape[0] u_row sp.lil_matrix((1, n_unknowns)) u_row[0, i] weight v_row sp.lil_matrix((1, n_unknowns)) v_row[0, i nv] weight A sp.vstack([A, u_row.tocsr(), v_row.tocsr()]).tocsr() b np.concatenate([b, [weight * uv[0], weight * uv[1]]]) # 解最小二乘正规方程 ATA (A.T A).tocsr() ATb A.T b x spsolve(ATA, ATb) u x[:nv] v x[nv:] return np.stack([u, v], axis1)这段代码的核心逻辑就是把每个三角形的两个残差行组装进大稀疏矩阵。运行之后返回一个 (n_v \times 2) 的UV坐标数组。比较关键的点我把未知量排列成“先所有u分量再所有v分量”这样在索引列号时比较直观也不容易搞混。4.2 参数选择与数值稳定性经验在真正实际用LSCM时有几个参数和细节值得反复调整固定点位置固定点之间的欧氏距离不宜过近否则求解出的UV整体会被压缩得很小后处理时需要大比例放大。固定点的位置还会影响展开图的方向例如固定在网格的边界上输出UV的方向往往能保持和原始边界走向基本一致。梯度映射矩阵 (M) 的条件数这个矩阵在某些极端瘦长的三角形上会接近奇异求逆时数值误差会放大。遇到这种情况不要盲目提高数据精度而是先做网格预处理如去除退化三角形、合理的三角形细分。瘦长三角形在LSCM里的危害比想象中大它会在那个局部产生一个很大的能量项拉歪周围一片顶点的UV。软约束权重的选择如果固定点的约束权重过小比如只设成100一些优化求解器会把固定点“拉扯”到其他位置导致最终UV位置不符合预期。如果权重过大比如超过1e14又可能造成稀疏矩阵条件数恶化。实践下来1e8到1e10之间是一个比较舒适的区间。4.3 后处理无缝纹理与接缝处理求解完成后拿到的UV坐标通常是任意尺度、任意平移的。如果要用于渲染或者贴图还需要做几个后处理步骤。第一步是归一化把所有UV坐标的包围盒缩放到 ([0,1]^2) 区间内注意保持纵横比否则会破坏刚才辛辛苦苦维持的保角性。具体做法是计算所有UV点在x和y方向的极差取较大的一个作为缩放基准整体等比缩放再平移到原点附近。第二步是处理接缝如果模型拓扑比较复杂一张UV无法覆盖整个网格就要在参数化前先把网格沿着某些边切开展开成多个UV岛。LSCM在带边界的网格上表现很好但如果你不管接缝直接整体展开一个封闭的球体灾难就会发生——球体上两个完全不相邻的区域会被挤到一起贴图会出现明显撕裂。所以我的建议是封闭网格务必先自动切缝。开缝区域一般选模型不太显眼的地方比如头部的后脑勺、身体的底面。5. 常见问题与排查技巧实录5.1 UV翻转或重叠这是LSCM新手最容易遇到的问题。明明解出来了检查UV三角形却发现法线方向反了或者大量三角形叠在一起。为什么会这样原因通常有两类。第一类是固定点选择不当。保角映射本身具有全局旋转性如果你把固定点选在了一个导致整体方向翻转的位置那么很大区域的三角形就会跟着旋转180度视觉上表现为UV上下颠倒甚至在展开图上大面积折叠。解决方法很简单尝试更换固定的顶点组合或者对最终UV做一次整体镜像。第二类是网格质量太差。如果输入网格包含很多狭长三角形这些三角形在保角能量中占据的主导权重会局部异常导致相邻UV三角形被“挤”到一边间接产生翻转。排查时可以写个简单脚本统计每个UV三角形的有向面积符号。如果符号以负为主就整体乘上一个负号如果有正有负且分布混乱问题基本出在切缝或网格质量上而不是LSCM本身。5.2 数值奇异或求解缓慢正规方程 (A^T A) 的求解是LSCM里性能的关键。如果网格规模超过几十万面稀疏Cholesky分解一般仍然很快但要注意矩阵的稀疏模式。如果我们把未知量排列为“先所有u再所有v”那么 (A^T A) 会形成两个大块的稀疏结构这在很多求解器里反而不如“顶点交错排列”u0, v0, u1, v1, ...来得高效。所以当你用Eigen或CGAL里的LSCM实现时如果性能异常先检查内部是否做了良好的重编号。自研时建议用spsolve默认的AMD重排序通常能显著提升速度。此外如果固定点约束没有加够比如只固定了一个顶点那 (A^T A) 仍然奇异解出来全是NaN或者一个接近零向量的平庸解。遇到NaN最先检查就是自由度和固定条件而不是去调求解器。5.3 与其他参数化方法怎么选LSCM不是唯一选择我也用它跟几种常见方法对过线方法保角性保面积计算效率适用场景LSCM优秀一般高线性求解纹理贴图、快速UV展开ARAP尽量刚性良好优秀中迭代求解形状变形、高保真参数化保面积参数化一般优秀中展平面积要求高的场景共形映射非线性极强不确定低理论分析、高精尖形状处理如果只是要快速得到一个“看起来没拉伸”的UVLSCM足够应付90%的场景。但如果你的下游应用对面积变形很敏感比如测量每个区域的实际大小那需要选择ARAP这类方法。不过ARAP要迭代求解对新手调试难度会高不少。5.4 网格边界缺失或退化LSCM默认对带边界的网格效果最好。如果输入是封闭网格你必须先用一个分割工具切出边界环。常见的做法是“广度优先展开切缝”或者用Libigl的cut_to_disk算法。切缝的好坏直接决定UV岛的形状。切得太碎UV边界碎片化贴图连续感差切得太少某些曲率大的区域展平后会严重拉伸。我一般喜欢把切缝放在曲率最大的边缘比如面部模型的下颌线附近这样视觉上不显眼。实际经验告诉我LSCM对边界数量也有要求。太多边界环会让线性系统变得更可靠约束更多但边界环之间的相对位置关系不容易控制可能出现一个UV岛落在另一个UV岛内部、互相嵌套的情况。如果遇到这种需要手动指定哪个边界环作为外部主边界把其余边界作为内边界处理。6. 个人体会这个项目带给我的一些经验如果非要用一句话总结LSCM我会说它是在“效率”和“效果”之间取得平衡的典范。不用像非线性方法那样反复试验参数也不用担心收敛问题只要网格质量过关、固定点选得合理几乎一次就能跑出可用的UV展开结果。我个人在实际操作中的最大心得是LSCM的难点从来不在数学和代码本身而在于准备工作——网格清理、切缝、固定点选择。这三样做好了一个200行的LSCM实现就能达到非常专业的展开效果这三样没做好即使直接调库结果也照样是一团乱麻。另外我在调试时习惯先把结果可视化出来而不是只盯着数值误差看。把UV网格和原始网格逐三角形对比颜色编码能非常直观地发现哪些局部变形过大、哪里出现了翻转比看一堆能量数值高效得多。最后再分享一个实用小技巧如果你发现LSCM展开的UV在某些高曲率区域比如球面的极点附近拉伸严重可以先把这个区域沿高曲率边做一个局部细分把更多自由度分配给需要“舒展”的三角形。网格分辨率在参数化中的作用往往被低估实际上它比调任何算法参数都好使。用LSCM大半年后我越发觉得它是那种“越用越顺手”的工具——基础原理简单扩展空间却很大值得每个玩三维的同行认真吃透。
返回列表