LSCM + Mean Value Coordinates 论文笔记
GAMES301-曲面参数化(GAMES 301: Surface Parameterization)作业3解析与matlab实现。
LSCM
LSCM(least squares conformal map)是经典的自由边界共形参数化算法。我们在课程中介绍过共形映射需要满足Cauchy-Riemann方程:
从Jacobian矩阵来看,Cauchy-Riemann方程说明映射

Conformality in a Triangulation
LSCM使用了复平面来推导共形能量。设三角形
类似地,我们把参数化后uv平面上的坐标也表示为复数形式:
使用复数形式的好处在于我们可以把坐标转换为标量函数进行推导。假设映射
利用反函数导数定理可以得到
接下来我们定义共形能量
显然当
Gradient in a Triangle
对于线性三角形网格,其共形能量在三角形内每一点都是相同的。因此我们可以把积分转换为能量与三角形面积相乘:
上式说明我们只需要计算三角形面积
其中
其中
这样整个三角形上的共形能量可以表示为:
对所有三角形求和后就得到了网格上的共形能量,其可以表示为关于顶点复函数的二次型:
上标
式中
Least Square Optimization
对于二次型的优化问题我们可以通过求导并令导数为0的方式进行直接求解。但需要注意在LSCM中系数矩阵
其中
上面的推导说明我们只需要处理实数矩阵构成的二次型优化即可。对它求导并令导数为0可以得到uv坐标需要满足的线性方程组:
为了避免出现平凡解,我们需要固定网格在uv平面上至少2个点的坐标。这种做法的几何意义非常直观:由于共形映射无法区分平移、旋转和缩放(共形映射关于相似变换是相互等价的),我们至少需要2个确定的点才能获得唯一的共形映射。此时可以将原始的复系数矩阵拆分成为自由顶点和固定顶点两部分:
由于
重新整理后可以得到自由顶点uv坐标需要满足的线性方程组:
求解这个线性方程组即可获得所需的共形参数化。
Implementation
整个LSCM算法可参考下面LSCM()
函数的实现。整个算法流程包括计算网格三角形面积、计算初始构型、构造稀疏线性方程组、固定网格上两个点、以及最后求解线性方程组得到参数坐标等步骤。其中计算初始构型的project2Plane()
函数与作业2完全相同,可以参见之前的代码片断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function uv = LSCM(V, F)
%% LSCM parameterization
%% Args:
%% V[nV, 3]: vertices in 3D
%% F[nF, 3]: face connectivity
%% Returns:
%% uv[nV, 2]: uv coordinates
nV = size(V, 1);
nF = size(F, 1);
%% areas
AT = doubleArea(V, F);
AT_sq = sqrt(AT);
%% rest pose
Xs = zeros(nF, 3, 2);
for i=1:nF
Xs(i,:,:) = project2Plane(V(F(i, :), :));
end
%% set up sparse complex matrix
A = (Xs(:, [3 1 2], 1) - Xs(:, [2 3 1], 1)) ./ AT_sq; %% real part
B = (Xs(:, [3 1 2], 2) - Xs(:, [2 3 1], 2)) ./ AT_sq; %% imag part
MA = sparse(repmat((1:nF)', 1, 3), F, A, nF, nV);
MB = sparse(repmat((1:nF)', 1, 3), F, B, nF, nV);
%% pin 2 points on the boundary
[b1, b2] = pinBoundary(V, F);
%% split free and pinned vertices
p = [b1 b2]; f = setdiff(1:nV, p);
Af = MA(:, f); Ap = MA(:, p); %% real part
Bf = MB(:, f); Bp = MB(:, p); %% imag part
AM = [Af -Bf; Bf Af];
b =-[Ap -Bp; Bp Ap] * [0; 1; 0; 0];
%% solve linear system
uv = zeros(nV, 2);
uv(f, :) = reshape(AM \ b, [nV-2 2]);
%% fix pinned points
uv(p, :) = [[0 0]; [1 0]];
end
pinBoundary()
函数实现了选择网格边界的两个固定点的功能,这里我简单地选择了边界的起点和中间点。当然其它的选取方式也是可行的,但需要注意不同的选取方式会影响最终的参数化结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function [b1, b2] = pinBoundary(V, F)
%% A helper function to pin 2 points on the boundary
%% Args:
%% V[nV, 3]: vertices in 3D
%% F[nF, 3]: face connectivity
%% Returns:
%% b1: pinned boundary vertex
%% b2: pinned boundary vertex
[B, ~] = findBoundary(V, F);
nB = length(B);
%% select the first and middle boundary points
b1 = B(1); b2 = B(round(nB/2));
end
最后,doubleArea()
函数给出了计算网格三角形面积的一个向量化实现。和利用循环进行遍历相比,使用向量化的效率会高很多。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function As = doubleArea(V, F)
%% Compute doubled area of triangles
%% Args:
%% V[nV, 3]: vertices in 3D
%% F[nF, 3]: face connectivity
%% Returns:
%% As[nF, 1]: doubled area of triangles
nF = size(F, 1);
Es = reshape(V(F(:, 2:3), :)-V(F(:, [1 1]), :), [nF 2 3]);
As = vecnorm(cross(Es(:,1,:), Es(:, 2, :), 3), 2, 3);
end
Results
在cow.obj
网格上使用LSCM可以得到如下的参数化结果。需要说明的是LSCM可以保证参数化后网格不会出现翻转,但无法保证不会出现自交。在本例中我们可以看到网格在边界上出现了自交的情况。


Mean Value Coordinates
Tutte’s Embedding
Mean value coordinates是Tutte’s embedding的一种变体,这里我们首先回忆一下Tutte’s embedding的原理。Tutte’s embedding中网格内部顶点
其中系数
Tutte’s embedding的一个重要性质在于当边界点位于一个凸多边形上时,求解线性方程组得到的参数化是无翻转且无自交的。
Mean Value Coordinates
Tutte’s embedding的缺陷在于参数化后可能会出现比较严重的几何扭曲,因此Tutte’s embedding相关算法的核心在于如何构造合适的系数来减少扭曲。Mean value coordinates使用了夹角半角的正切来构造权重:

Implementation
使用mean value coordinates作为权重的Tutte’s embedding算法可参见MVCTutte()
函数。这里首先使用了LSCM来固定网格参数化边界,然后对内部顶点求解线性方程组来求解uv坐标。在实现时的一个技巧是从三角形内角来考虑权重,比如说在三角形
因此对于三角形的每个内角我们只需要计算它的半角正切以及邻边边长,最后填到对应位置上进行求和即可。除此之外,在计算半角正切时还可以结合三角函数的变换技巧:
而内角
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
function uv = MVCTutte(V, F)
%% Tutte's embedding with mean value coordinates
%% Args:
%% V[nV, 3]: vertices in 3D
%% F[nF, 3]: face connectivity
%% Returns:
%% uv[nV, 2]: uv coordinates
nV = size(V, 1);
[B, ~] = findBoundary(V, F);
nB = length(B);
%% LSCM to pin the boundary
uv = LSCM(V, F);
%% edges
Es = reshape(V(F(:, [2, 3, 1]), :) - V(F, :), [size(F), 3]);
Enorm = vecnorm(Es, 2, 3);
Edir = Es ./ Enorm;
%% trigonometry
coss =-dot(Edir(:, :, :), Edir(:, [3, 1, 2], :), 3);
sins = vecnorm(cross(Edir(:, :, :), -Edir(:, [3, 1, 2], :), 3), 2, 3);
%% tan(theta/2) = sin(theta) / (1+cos(theta))
tanHalf = sins ./ (coss + 1);
%% set up linear system
tripI = [F F];
tripJ = [F(:, [2 3 1]) F(:, [3 1 2])];
tripV = [tanHalf./Enorm(:,:) tanHalf./Enorm(:, [3 1 2])];
A = sparse(tripI, tripJ, tripV, nV, nV);
A = A - diag(sum(A, 2));
%% pinned boundary points
A(B, :) = sparse(nB, nV);
A(B, B) = speye(nB);
b = zeros(nV, 2);
b(B,:) = uv(B, :);
%% solve linear system
uv = A \ b;
end
Results
在cow.obj
网格上使用LSCM确定边界然后使用mean value coordinates计算内部顶点uv坐标的结果如下。从结果来看mean value coordinates的结果与直接使用LSCM差异不大。

