三维扫描获取的原始网格数据往往因为光学遮挡或设备噪声而存在表面缺失。这些缺失表现为拓扑结构上的孔洞,直接破坏了三维模型的完整性。如果不加以处理,这些孔洞会导致模型在三维打印时出现物理结构脆弱的问题,或者在渲染管线中引发光照计算错误。基于曲率与平面的孔洞修补技术,正是为了解决这一痛点而设计的。它不仅关注填补缺口本身,更注重修补后的网格与原有模型表面在几何特征上的连续性。

孔洞边界提取与特征分析
孔洞修补的第一步是准确定位破损区域。在三维网格数据结构中,通常采用半边数据结构来管理拓扑关系。一条边如果只被一个面引用,那么这条边就是边界边。通过遍历所有边并找出这些边界边,我们可以将它们串联成闭合的边界环,这就是孔洞的轮廓。提取出边界环后,系统就能明确修补操作的作用范围。
仅仅找到边界还不够,为了让修补后的网格自然过渡,必须分析孔洞周围的几何特征。曲率是衡量表面弯曲程度的重要指标。在离散网格中,可以通过计算顶点周围相邻法向量的夹角变化来估算离散曲率。如果孔洞周围曲率较小,说明表面接近平面,可以采用简单的平面投影法填补;如果曲率较大,说明表面存在明显弯曲,修补时必须考虑曲率延伸趋势,否则会产生生硬的平面断层。
下面是使用C++伪代码结合常见网格库展示的边界提取与曲率计算逻辑。这段代码演示了如何遍历网格寻找边界,并基于相邻面的法向量计算顶点的近似曲率。
// 寻找边界边并构建边界环
std::vector<EdgeHandle> findHoleBoundary(Mesh& mesh) {
std::vector<EdgeHandle> boundaryEdges;
for (auto eh : mesh.edges()) {
if (!mesh.is_boundary(eh)) {
// 如果一条边只被一个面引用,则是边界边
if (mesh.face_handle(mesh.halfedge_handle(eh, 0)).is_valid() !=
mesh.face_handle(mesh.halfedge_handle(eh, 1)).is_valid()) {
boundaryEdges.push_back(eh);
}
}
}
return boundaryEdges;
}
// 计算顶点离散曲率(基于法向量偏差)
float calculateVertexCurvature(Mesh& mesh, VertexHandle vh) {
auto normal = mesh.normal(vh);
float curvature = 0.0;
int count = 0;
for (auto vv_it = mesh.vv_iter(vh); vv_it.is_valid(); ++vv_it) {
auto neighborNormal = mesh.normal(*vv_it);
// 计算法向量夹角的余弦值,近似曲率
curvature += dot(normal, neighborNormal);
count++;
}
return 1.0 - (curvature / count);
}
基于平面投影的初始网格生成
在明确了孔洞边界及其曲率特征后,需要生成初始的填补网格。对于局部曲率较小的孔洞,平面投影法是一种高效且稳定的策略。该方法的核心思想是将三维空间中的边界顶点投影到一个最佳拟合平面上,将复杂的三维三角化问题降维为二维平面上的三角化问题,从而大幅降低计算复杂度。
最佳拟合平面的计算通常采用主成分分析(PCA)方法。首先收集孔洞边界所有顶点的坐标,计算其质心。然后构建协方差矩阵,求解该矩阵的最小特征值对应的特征向量,这个特征向量就是拟合平面的法向量。有了平面方程后,将边界顶点投影到该平面上,并在二维参数域内进行德劳内三角化。最后,将生成的二维三角形网格的顶点映射回三维空间,形成初始的孔洞填补网格。
以下是利用Eigen库计算最佳拟合平面并进行投影映射的代码示例。通过矩阵运算,我们可以快速得到平面法向量,并构建从三维到二维的坐标系转换矩阵。
// 使用Eigen计算最佳拟合平面法向量
Vector3f computeBestFitPlane(const std::vector<Vector3f>& boundaryPoints) {
Vector3f center(0, 0, 0);
for (const auto& p : boundaryPoints) center += p;
center /= boundaryPoints.size();
MatrixXf points(boundaryPoints.size(), 3);
for (size_t i = 0; i < boundaryPoints.size(); ++i) {
points.row(i) = boundaryPoints[i] - center;
}
// 计算协方差矩阵
Matrix3f cov = points.transpose() * points;
SelfAdjointEigenSolver<Matrix3f> eig(cov);
// 最小特征值对应的特征向量即为平面法向量
return eig.eigenvectors().col(0).normalized();
}
// 将三维点投影到二维参数域
Vector2f projectTo2D(const Vector3f& point, const Vector3f& origin,
const Vector3f& u, const Vector3f& v) {
Vector3f d = point - origin;
return Vector2f(d.dot(u), d.dot(v));
}
曲率驱动的网格优化与平滑过渡
虽然平面投影法能快速填补孔洞,但在高曲率区域(如模型的手指或耳朵部位),简单的平面填补会导致表面出现明显的凹陷或断层。为了解决这个问题,必须引入曲率驱动的网格优化策略。该策略的核心是对初始生成的网格顶点进行位置微调,使其高度符合周围表面的弯曲趋势。
拉普拉斯平滑是网格优化中常用的手段。传统的拉普拉斯平滑会将顶点移动到其相邻顶点的几何中心,但这会导致模型体积收缩。在基于曲率的修补中,我们采用加权拉普拉斯平滑。根据顶点周围的曲率分布,赋予不同邻居顶点不同的权重。曲率变化平缓的方向赋予较高权重,曲率突变的方向赋予较低权重。这样,新顶点在平滑过程中能够更好地保持原有的几何特征,实现与原模型的无缝过渡。
下面的代码展示了曲率加权平滑的核心逻辑。通过计算相邻顶点的曲率权重,对新生成的孔洞内部顶点进行迭代调整,直到满足收敛条件或达到最大迭代次数。
// 曲率加权拉普拉斯平滑
void curvatureWeightedSmoothing(Mesh& mesh, VertexHandle vh, float lambda) {
Vector3f newPos(0, 0, 0);
float totalWeight = 0.0;
// 遍历相邻顶点
for (auto vv_it = mesh.vv_iter(vh); vv_it.is_valid(); ++vv_it) {
// 获取相邻顶点的曲率(假设已计算并存储在属性中)
float neighborCurvature = mesh.property(curvature_prop, *vv_it);
// 曲率越小,权重越大(使用高斯函数计算权重)
float weight = exp(-neighborCurvature * neighborCurvature);
newPos += mesh.point(*vv_it) * weight;
totalWeight += weight;
}
if (totalWeight > 0) {
newPos /= totalWeight;
// 插值更新顶点位置
Vector3f oldPos = mesh.point(vh);
mesh.set_point(vh, oldPos + (newPos - oldPos) * lambda);
}
}
通过上述三个步骤,基于曲率与平面的孔洞修补算法能够有效处理大多数三维模型破损问题。它结合了平面投影的高效性与曲率分析的精确性,在保证拓扑完整的同时,兼顾了表面的几何连续性。在实际应用中,开发者可以根据模型的具体特征,调整曲率计算的邻域范围和平滑迭代的次数,以获得最佳的修补效果。