网格优化Remesh——Tangential Smooth
一、Laplace平滑
简单的拉普拉斯平滑算法的原理是将每个顶点都移动到相邻顶点的平均位置,即采用所谓伞状算子:
在伞状结构中表示这样的过程如下图:
拉普拉斯平滑算法有很多进一步的变形,首先在求取平均位置时,可以采用不同的加权策略,例如对不同的邻接点采用不同的权值。一般来说,距离中心点P较远的邻接点,我们可以让他对P平滑后的位置影响小一点。这样就可以采用一种距离的倒数为权值的拉普拉斯平滑。
有时为了控制平滑的速率,也会引入参数lambda来控制平滑的速率,即从原来所执行的:
转变成同时,平滑算法往往可以反复对Mesh执行,使得Mesh越来越光顺,迭代次数T也是平滑算法中重要的参数。
二、切向Laplace平滑
右边图中的第三幅,代表reproject的效果,需要额外实现(比如用AABBtree等),在公式中没有体现。
另一种写法:
区别在于(I - ni . ni.T),还有正负号
三、改进的切向Laplace平滑(基于面积的切向Laplace平滑)
首先计算gi:
然后用以下公式将gi被投影会pi的切面:
其中ni是pi的法向,λ是阻尼系数用来防止震荡。
一些Voronoi面积大的节点拥有更大的重力,所以吸引了其它节点,因此减小了他们自身的面积。一般通过<20次迭代,面积方差可以减小5倍,最终产生一个每个节点面积大致相同的网格,如下图所示。
代码:
Eigen::Vector3d tangential_smooth(const Tuple& t) { auto one_ring_tris = get_one_ring_tris_for_vertex(t); if (one_ring_tris.size() < 2) return vertex_attrs[t.vid(*this)].pos; Eigen::Vector3d after_smooth = smooth(t); // 计算了普通Laplace平滑后的点位置 // get normal and area of each face auto area = [](auto& m, auto& verts) { return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos) .cross( m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)) .norm() / 2.0; }; auto normal = [](auto& m, auto& verts) { return ((m.vertex_attrs[verts[0].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos) .cross( m.vertex_attrs[verts[1].vid(m)].pos - m.vertex_attrs[verts[2].vid(m)].pos)) .normalized(); }; auto w0 = 0.0; Eigen::Vector3d n0(0.0, 0.0, 0.0); for (auto& e : one_ring_tris) { auto verts = oriented_tri_vertices(e); w0 += area(*this, verts); n0 += area(*this, verts) * normal(*this, verts); } n0 /= w0; if (n0.norm() < 1e-10) return vertex_attrs[t.vid(*this)].pos; n0 = n0.normalized(); after_smooth += n0 * n0.transpose() * (vertex_attrs[t.vid(*this)].pos - after_smooth); assert(check_mesh_connectivity_validity()); return after_smooth; }
四、非流形网格的平滑
对于non-manifold网格,上面的公式是否还适用,有待调研。
参考文献:
sgp04 M. Botsch and L. Kobbelt / Remeshing for Multiresolution Modeling
Polygon mesh processing (Botsch M., et al.) Chapter6 Section 6.5 Triangle-based Remeshing