一、算法相关资料
算法来自论文:
Surface Simplification Using Quadric Error Metrics
通过计算网格图形上的每一条边的权重,每次移除最小权重的边。重复这个过程达到简化效果。
二、算法步骤
- 初始化所有顶点的 Q 矩阵。
- 选择所有有效边。 (所有联通边 (v1, v2) 、或者长度小于某一个阈值的边。)
- 计算所有有效边的误差 ¯vT(Q1+ Q2)¯v 作为这条边的cost。
- 将所有的边按照cost的权值放到一个最小堆里。
- 每次移除最小的边,并且更新包含着v1的所有有效边的代价。
Q矩阵计算方法
基础知识平面方程(Plane Equation)
定义顶点的误差为顶点到该顶点相交的三角形的平面的距离平方和:
其中P为平面方程 [a,b,c,d]T , v为顶点[x,y,z,1],法向量 n = [a,b,c]
(P·v)/|n| 为点v到平面P的距离
n模长等于1时,P·v即点到平面距离。
这个基本二次误差Kp可以用来求空间中任意点到平面P的平方距离。我们可以把这些基本二次曲面加起来,用一个矩阵Q表示整个平面集合。
新顶点位置计算
选择v1 、v2、(v1+v2)/2中选择一个;
对二次项式Δ(v)求导,当求导等于0时;
当左边矩阵可逆时,可求解:
否则,根据第一条策略。
实现
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 48 49
| function calculatorEdgeDelta(geometry, edge) { let mat = new THREE.Matrix4() let deltaV = 0; let q1 = calculatorVertexDelta(geometry, edge.a).elements let q2 = calculatorVertexDelta(geometry, edge.b).elements mat.elements = q1.map((e, i) => { return e + q2[i] }) edge.midv = calculatorVertexPos(geometry, edge) for (let j = 0; j < 4; j++) { let t = 0; for (let k = 0; k < 4; k++) { t += edge.midv.getComponent(k) * mat.elements[j * 4 + k]; } deltaV += t * edge.midv.getComponent(j) } edge.deltaV = deltaV return edge }
function calculatorVertexDelta(geometry, vIndex) { let result = new THREE.Matrix4() result.elements[0] = 0 result.elements[5] = 0 result.elements[10] = 0 result.elements[15] = 0 let tmp = new THREE.Vector4() let f = findFaceWithVindex(geometry, vIndex) f.map(face => { let { x, y, z } = face.normal; let d = -geometry.vertices[face.a].dot(face.normal) tmp.set(x, y, z, d) for (let j = 0; j < 4; j++) { for (let k = 0; k < 4; k++) { result.elements[j * 4 + k] += tmp.getComponent(j) * tmp.getComponent(k); } } }) return result; }
|
效果
下图是使用three.js 实现QEM算法。对OBJ模型动态计算过程。可以看到对于简单模型,简化效果基本满足。经过多个模型测试,对于特殊形状模型简化容易出现破面现象。