《Surface Simplification Using Quadric Error Metrics》这篇论文介绍了一种网格简化的算法,通过“edge contraction”(边收缩)的方法来简化网格。边收缩的结果就是将两个顶点合成一个顶点,因此可以按照任意的顶点数目去简化网格。
假设有一个二次函数
Δ
(
v
)
=
v
T
Q
v
\Delta(\mathbf{v})=\mathbf{v}^T\mathbf{Q}\mathbf{v}
Δ(v)=vTQv 可以描述顶点
v
\mathbf{v}
v的“变形程度”,这篇论文的算法目的就是最小化总体的“变形程度”,即:
minimize
∑
v
v
T
Q
v
\text{minimize} \quad \sum_{\mathbf{v}} \mathbf{v}^T\mathbf{Q}\mathbf{v}
minimizev∑vTQv
但是由于直接最小化不太现实,因为顶点数目会发生改变,所以这篇论文的算法步骤是这样的:
- 对每个顶点计算其初始的 Q \mathbf{Q} Q,满足 v T Q v = 0 \mathbf{v}^T\mathbf{Q}\mathbf{v}=0 vTQv=0 ;
- 保存所有合法的“点对”,包括所有的边对应的点对,以及距离小于一定阈值的顶点对;
- 对于每个合法的点对( v 1 , v 2 \mathbf{v}_1,\mathbf{v}_2 v1,v2),生成合成的顶点 v s \mathbf{v}_s vs,这一步边收缩的代价为 Δ ( v s ) = v s T ( Q 1 + Q 2 ) v s \Delta(\mathbf{v}_s)=\mathbf{v}_s^T(\mathbf{Q}_1+\mathbf{Q}_2)\mathbf{v}_s Δ(vs)=vsT(Q1+Q2)vs,也就是新顶点的“变形程度”。这里合成顶点的方法有很多,可以是中点、其中一个顶点、或者最小化 Δ ( v s ) \Delta(\mathbf{v}_s) Δ(vs) 对应的顶点位置;
- 重复选择 Δ ( v s ) \Delta(\mathbf{v}_s) Δ(vs) 最小的顶点对进行边收缩,然后更新mesh、相关顶点的 Q \mathbf{Q} Q、以及相关合成顶点的代价,直到目标顶点数目。可以用一定的数据结构来维护。
接下来我用我的理解来描述这个二次误差函数 Δ ( v ) = v T Q v \Delta(\mathbf{v})=\mathbf{v}^T\mathbf{Q}\mathbf{v} Δ(v)=vTQv 的构造过程:
首先我们用一种启发式的思想去思考,要如何度量一个mesh的微小变形的程度呢?
假如只有一个平面三角形,当它的一个顶点A有一个微小的偏移时,这个顶点相对于对边BC的距离h也会发生变化,因此可以用h来度量点A的“变形程度”,亦即
Δ
(
A
)
=
h
。
\Delta(A)=h 。
Δ(A)=h。
也许当A的位移平行于BC时,用h无法表示这样的变形,但是这只是一种思想,既考虑了简洁性,也考虑了在实际变形中大概率不会有这种特殊的变形。
类比到空间中的一个三角网格(triangular mesh),当一个顶点
v
\mathbf{v}
v发生轻微变形时,我们用点到面的距离去度量其“变形程度”,顶点已经给出了,那么“面”有哪些呢?我们先假定每个顶点都保存了一些用于度量的面
planes
(
v
)
\text{planes}(\mathbf{v})
planes(v),在这种情况下,顶点
v
=
[
v
x
,
v
y
,
v
z
,
1
]
T
\mathbf{v}=[v_x,v_y,v_z,1]^T
v=[vx,vy,vz,1]T 的“变形程度”的度量可以写作
Δ
(
v
)
=
∑
p
∈
planes
(
v
)
(
p
T
v
)
2
,
\Delta(\mathbf{v})=\sum_{\mathbf{p}\in\text{planes}(\mathbf{v})} (\mathbf{p}^T \mathbf{v})^2 ,
Δ(v)=p∈planes(v)∑(pTv)2,
其中
p
=
[
a
,
b
,
c
,
d
]
T
\mathbf{p}=[a,b,c,d]^T
p=[a,b,c,d]T表示平面
a
x
+
b
y
+
c
z
+
d
=
0
ax+by+cz+d=0
ax+by+cz+d=0,其中
a
2
+
b
2
+
c
2
=
1
a^2+b^2+c^2=1
a2+b2+c2=1,这个单位化的限制就是告诉我们平面
p
\mathbf{p}
p的法向量就是
(
a
,
b
,
c
)
(a,b,c)
(a,b,c),因此点到面的距离就是
p
T
v
\mathbf{p}^T\mathbf{v}
pTv ,高中时不是学过点到平面的距离公式吗,单位化后就不需要分母的那个法向量长度了。
现在回到"edge contraction",在边收缩前我们有两个顶点
v
1
\mathbf{v}_1
v1和
v
2
\mathbf{v}_2
v2,收缩完之后变成了一个顶点
v
s
\mathbf{v}_s
vs,而与
v
s
\mathbf{v}_s
vs关联的面的集合就变成了
planes
(
v
s
)
=
planes
(
v
1
)
⋃
planes
(
v
2
)
\text{planes}(\mathbf{v}_s)=\text{planes}(\mathbf{v}_1) \bigcup \text{planes}(\mathbf{v}_2)
planes(vs)=planes(v1)⋃planes(v2) ,用集合的表示形式不太方便,因此我们写成矩阵的形式:
Δ
(
v
)
=
∑
(
p
T
v
)
2
=
v
T
(
∑
p
p
T
)
v
=
v
T
(
∑
Q
i
)
v
\Delta(\mathbf{v})=\sum (\mathbf{p}^T \mathbf{v})^2 = \mathbf{v}^T (\sum\mathbf{p}\mathbf{p}^T) \mathbf{v} = \mathbf{v}^T (\sum\mathbf{Q_i}) \mathbf{v}
Δ(v)=∑(pTv)2=vT(∑ppT)v=vT(∑Qi)v
就对应上之前算法里说的“变形程度”的度量公式了。所以前面讲到的保存每个顶点关联的面的集合,实际上就是保存这些面对应的矩阵之和,而边收缩的过程中,新的顶点需要保存的,就是旧的两个顶点保存的矩阵之和。
因此在算法中每个顶点保存的 Q \mathbf{Q} Q,初始化时就是将每个顶点相邻的面作为其关联的面,然后计算 Q \mathbf{Q} Q,在后续做边收缩时,只需要将收缩的两个顶点的 Q \mathbf{Q} Q相加即可。