QEM核心思想是“找最优替代点”而非删点:通过最小化新顶点在所有邻接平面上的平方距离和(即vᵀQv),求解二次误差最小化的线性方程组确定最佳收缩位置,其中Q为累加各邻面平面方程系数外积所得的4×4对称二次误差矩阵。

什么是 Quadric Error Metrics(QEM)的核心思想
QEM 不是“删点”而是“找最优替代点”:每次合并两个顶点时,不是简单取平均,而是求解一个二次误差最小化问题,让新顶点在所有关联面片上的几何失真总和最小。这个“失真”用平面方程的代入值平方来建模——每个三角面片对应一个 ax + by + cz + d = 0,误差就是 (ax + by + cz + d)²。
关键点在于:每个顶点维护一个 4×4 对称矩阵 Q(quadric matrix),它累加了所有邻接面片对应的二次型。合并顶点 v1 和 v2 时,新顶点位置 v_new 应使 v_newᵀ(Q1 + Q2)v_new 最小(带约束 v_new 在齐次坐标下最后一维为 1)。
怎么构造单个面片的 quadric 矩阵
给定三角面片三个顶点 v0, v1, v2,先算单位法向量 n 和面上一点 p(比如 v0),则平面方程为 n·(x − p) = 0,即 n·x − n·p = 0。写成齐次形式:[nₓ, n_y, n_z, −n·p] · [x, y, z, 1]ᵀ = 0。
这个平面定义的二次误差是其左边项的平方,展开后对应一个 4×4 矩阵:
立即学习“C++免费学习笔记(深入)”;
Q_face = outer_product(plane_vec, plane_vec); // plane_vec = {n.x, n.y, n.z, -dot(n,p)}注意:Q_face 是秩 1 矩阵;实际中建议加一个极小对角扰动(如 1e-8 * I)避免后续求逆失败。
常见错误:
- 法向量没单位化 → 导致误差量纲失衡,简化后网格严重塌陷
- 用非齐次坐标计算
Q→ 矩阵维度错,后续求解无意义 - 忽略面片朝向,直接叉积不判断顶点顺序 → 法向反向,
Q符号翻转但平方后看不出来,实则优化目标跑偏
怎么求解最优收缩点(collapse vertex)
对候选边 (v1, v2),其合并后的新顶点 v_new 应最小化总误差 v_newᵀ(Q1 + Q2)v_new,其中 Q1, Q2 是两端点当前 quadric 矩阵之和。
设 Q_sum = Q1 + Q2,记 v_new = [x, y, z, 1]ᵀ,则目标函数为:
f(x,y,z) = [x y z 1] * Q_sum * [x y z 1]ᵀ
对 x,y,z 求偏导并令为 0,可得线性方程组:A * [x y z]ᵀ = b,其中:
-
A是Q_sum的左上3×3子块 -
b是−Q_sum的第 4 列前 3 行(带符号)
如果 A 奇异(行列式 ≈ 0),说明约束太弱,退化为取 v1 或 v2 中 Q_sum 误差更小的那个;或强制设 v_new = (v1 + v2) * 0.5 并跳过该边。
性能提示:不要每次都 SVD 或 full inverse —— 用 std::getrf + std::getrs(LAPACK)或 Eigen 的 llt().solve() 更稳更快。
边坍缩时怎么更新邻接关系和 quadric 矩阵
真正难的不是数学,是拓扑维护。手写时最容易崩在半途:删了顶点但面索引没重映射,或某条边被多次坍缩导致非法三角形。
推荐用半边结构(half-edge)或至少维护:
- 每个顶点的邻接面列表(
vector<size_t></size_t>,存 face index) - 每个面的三顶点索引(
array<size_t></size_t>) - 一个布尔数组
is_deleted_vertex[]标记已移除顶点
坍缩边 (v1, v2) → v_new 后必须:
- 将所有含
v2的面,把v2替换为v_new(注意检查是否退化为线段) - 合并
v1和v2的邻接面列表,并去重 - 更新
v_new的 quadric 矩阵为Q1 + Q2(不是平均!) - 标记
v2为已删除,并从所有数据结构中逻辑剔除(暂不物理擦除,避免索引漂移)
容易被忽略的细节:当多个边共享同一端点时,一次只处理一条边;否则并发更新会破坏邻接一致性。QEM 本身不保证全局最优,但乱序坍缩会让结果完全不可控。


















