13.2 泊松曲面重建

泊松重建是 曲面重建 系列方法中最常用的算法之一,它通过求解泊松方程从有向点云重建水密表面。

0. 算法动机:为什么需要泊松重建?

现实问题引入

想象你是一位文物保护专家,收到了一尊古希腊雕像的激光扫描数据:

  • 数百万个点,带有位置信息和法向方向
  • 但这些都是离散的点,没有表面
  • 你需要生成一个封闭的水密网格用于3D打印

或者你是一位医学影像工程师:

  • 从CT扫描获得了骨骼的点云数据
  • 点云有噪声,有缺失区域
  • 需要重建出光滑的骨骼表面用于手术规划

传统方法的局限

基于Delaunay的方法

  • 对噪声敏感,会产生不规则的三角形
  • 难以处理非均匀采样
  • 可能产生自相交

基于局部拟合的方法

  • 在复杂拓扑处容易失败
  • 难以保证水密性
  • 对噪声鲁棒性差

推进前沿法

  • 需要高质量的点云
  • 难以处理大孔洞
  • 可能产生自相交

泊松重建的优势

全局优化:不是局部决策,而是全局求解 噪声鲁棒:通过求解泊松方程自然平滑噪声 水密保证:输出总是封闭的流形 理论保证:有坚实的数学基础

如果不使用泊松重建

  • 表面不封闭:出现孔洞,无法3D打印
  • 噪声放大:高频噪声产生表面褶皱
  • 拓扑错误:把手连接到主体,或产生不连通组件
  • 细节丢失:过度平滑导致重要特征消失

1. 直观理解:3D扫描的轮廓

1.1 “指示函数就像剪影”

想象你正在拍摄一个人的剪影:

2D剪影

  • 人物内部:黑色(1)
  • 背景:白色(0)
  • 轮廓:黑白交界处

3D指示函数

  • 物体内部:1
  • 外部:0
  • 表面:0.5等值面(轮廓)
2D剪影:                      3D指示函数截面:
  000000000000                 000000000000
  000111110000                 000111110000
  001111111000                 001111111000
  001111111000                 001111111000
  000111110000                 000111110000
  000000000000                 000000000000
  
  轮廓:黑/白边界              表面:函数值=0.5的位置

1.2 “梯度就像坡度指示”

指示函数的梯度指向变化最剧烈的方向:

  • 在表面处,梯度垂直于表面
  • 梯度大小反映了从内部到外部的变化率
  • 理想情况下,梯度等于表面法向

类比:想象一座山

  • 指示函数:海拔高度(内部=山下,外部=山上)
  • 梯度:坡度方向(垂直于等高线)
  • 表面:海平面(高度=0)

1.3 “泊松方程就像平滑填充”

我们有:

  • 输入:稀疏的法向采样(只在点云位置知道法向)
  • 目标:找到一个平滑的指示函数,其梯度匹配输入法向

类比:拼图游戏

  • 法向是边缘碎片,告诉你边界在哪里
  • 泊松方程帮你填充内部,形成完整的图像
  • 结果是平滑过渡,不是突兀变化

2. 数学基础

2.1 指示函数与梯度

泊松重建的核心思想是构造一个指示函数(indicator function),该函数在物体内部取值为 1,外部为 0:

其中 是待重建的实体区域。

指示函数的梯度在表面边界处形成一个向量场:

其中 是表面法向量, 是 Dirac delta 函数。

2.2 泊松方程

由于直接计算指示函数较困难,算法转而求解一个平滑的近似函数 ,使其梯度最佳匹配输入法向量场

这是泊松方程的标准形式,其中:

  • 是 Laplacian 算子
  • 是向量场的散度

2.3 变分形式

泊松方程等价于最小化以下能量泛函:

通过变分法,最小化该能量得到欧拉-拉格朗日方程正是泊松方程。


3. 算法流程详解

3.1 八叉树结构

泊松重建使用八叉树(Octree)来组织空间:

八叉树层次结构:

Level 0:          Level 1:          Level 2:
+--------+        +----+----+       +--+--+--+--+ 
|        |        |    |    |       |##|  |  |  |
|        |   →    +----+----+   →   +--+--+--+--+ 
|        |        |    |    |       |  |##|##|  |
+--------+        +----+----+       +--+--+--+--+ 
                                     |  |##|  |  |
                                     +--+--+--+--+ 
                                     |  |  |  |##|
                                     +--+--+--+--+ 

## 表示包含点云的区域(需要细分)

八叉树的作用

  1. 自适应细分:在点密集处细分更深
  2. 空间索引:快速定位相邻点
  3. 多分辨率:支持不同精度的重建

3.2 从点云到表面的重建过程

步骤可视化:

1. 输入点云:
   •     •     •
      •     •
   •     •     •
   (带有法向的离散点)

2. 构建八叉树:
   +---+---+---+---+
   |   |   |   |   |
   +---+---+---+---+
   |   |•••|•••|   |
   +---+---+---+---+
   |   |•••|•••|   |
   +---+---+---+---+
   |   |   |   |   |
   +---+---+---+---+
   (自适应细分)

3. 定义向量场:
   ↖  ↑  ↗
   ←  •  →    (每个节点有插值法向)
   ↙  ↓  ↘

4. 计算散度:
   [ 0.2] [ 0.5] [-0.1]
   [ 0.3] [ 1.0] [-0.2]   (标量场)
   [-0.1] [-0.3] [ 0.0]

5. 求解泊松方程:
   [0.1] [0.3] [0.5]
   [0.2] [0.6] [0.8]    (指示函数)
   [0.4] [0.7] [0.9]

6. 提取等值面:
       __________
      /          \
     /            \
    |              |   (三角网格)
     \\            /
      \\__________/

3.3 向量场构造

从输入点云 构造一个平滑的向量场:

其中 是基于八叉树的插值权重。

3.4 散度计算

计算向量场的散度:

3.5 泊松方程求解

在离散化后的空间中求解线性系统。CGAL 使用 Delaunay 三角剖分代替原始论文中的八叉树:

其中:

  • 是离散 Laplacian 矩阵
  • 是待求的隐函数值
  • 是散度向量

3.6 等值面提取

使用 Marching Cubes 或 Delaunay 细化提取 的等值面。等值通常选择为输入点处函数值的中位数。


4. CGAL 架构分析

4.1 核心类层次

Poisson_reconstruction_function<Gt>
    |
    +-- Reconstruction_triangulation_3<BaseGt, Gt, Tds>
            |
            +-- Delaunay_triangulation_3<Gt, Tds>
            |
            +-- Reconstruction_vertex_base_3<Gt, Vb>
            |       +-- Triangulation_vertex_base_3<Gt>
            |
            +-- Delaunay_triangulation_cell_base_3<Gt, Cb>
                    +-- Triangulation_cell_base_with_info_3<int, Gt>

4.2 关键文件位置

文件路径功能
Poisson_reconstruction_function.hPoisson_surface_reconstruction_3/include/CGAL/核心重建类
Reconstruction_triangulation_3.hPoisson_surface_reconstruction_3/include/CGAL/重建专用三角剖分
Poisson_mesh_domain_3.hPoisson_surface_reconstruction_3/include/CGAL/Mesh_3 域适配器
poisson_surface_reconstruction.hPoisson_surface_reconstruction_3/include/CGAL/便捷函数接口

4.3 Poisson_reconstruction_function 类

template <class Gt>
class Poisson_reconstruction_function
{
public:
    // 几何类型定义
    typedef Gt Geom_traits;
    typedef typename Geom_traits::FT FT;
    typedef typename Geom_traits::Point_3 Point;
    typedef typename Geom_traits::Vector_3 Vector;
    
    // 三角剖分类型
    typedef Reconstruction_triangulation_3<...> Triangulation;
    typedef typename Triangulation::Cell_handle Cell_handle;
    typedef typename Triangulation::Vertex_handle Vertex_handle;
    
    // 构造函数:从点云创建
    template <typename InputIterator, typename PointPMap, typename NormalPMap>
    Poisson_reconstruction_function(
        InputIterator first, InputIterator beyond,
        PointPMap point_pmap, NormalPMap normal_pmap);
    
    // 核心操作:计算隐函数
    template <class SparseLinearAlgebraTraits_d>
    bool compute_implicit_function(SparseLinearAlgebraTraits_d solver, 
                                   bool smoother_hole_filling = false);
    
    // 隐函数求值(实现 ImplicitFunction 概念)
    FT operator()(const Point& p) const;
    
    // 获取内部点和包围球
    Point get_inner_point() const;
    Sphere bounding_sphere() const;
};

5. 实现细节

5.1 Delaunay 细化

CGAL 的实现使用自适应 Delaunay 细化来离散化空间,而非固定八叉树:

// Delaunay 细化参数
const FT radius_edge_ratio_bound = 2.5;  // 半径-边比上界
const unsigned int max_vertices = 10000000;  // 最大顶点数
const FT enlarge_ratio = 1.5;  // 包围盒扩展比例
 
// 执行细化
unsigned int delaunay_refinement(FT radius_edge_ratio_bound,
                                 Sizing_field sizing_field,
                                 unsigned int max_vertices,
                                 FT enlarge_ratio);

细化过程迭代地插入 Steiner 点,直到满足以下条件:

  • 所有四面体的半径-边比小于给定阈值
  • 四面体尺寸符合尺寸场要求
  • 顶点数不超过上限

5.2 线性系统组装

泊松方程离散化为稀疏线性系统:

template <class SparseLinearAlgebraTraits_d>
bool solve_poisson(SparseLinearAlgebraTraits_d solver, double lambda)
{
    // 初始化单元索引
    initialize_cell_indices();
    initialize_barycenters();
    
    // 约束一个顶点以固定常数偏移
    constrain_one_vertex_on_convex_hull();
    m_tr->index_unconstrained_vertices();
    
    unsigned int nb_variables = m_tr->number_of_vertices() - 1;
    
    // 组装矩阵 A 和右端项 B
    typename SparseLinearAlgebraTraits_d::Matrix A(nb_variables);
    typename SparseLinearAlgebraTraits_d::Vector X(nb_variables), B(nb_variables);
    
    // 对每个顶点组装行
    for (auto v = m_tr->finite_vertices_begin(); v \!= m_tr->finite_vertices_end(); ++v)
    {
        if (\!m_tr->is_constrained(v)) {
            B[v->index()] = div(v);  // 计算散度
            assemble_poisson_row<SparseLinearAlgebraTraits_d>(A, v, B, lambda);
        }
    }
    
    // 求解系统
    double D;
    if (\!solver.linear_solver(A, B, X, D))
        return false;
    
    // 将解复制回顶点
    // ...
    return true;
}

5.3 散度计算

散度计算有两种模式:

非归一化模式(默认):

FT div(Vertex_handle v)
{
    FT div = 0.0;
    for (auto cell : incident_cells(v))
    {
        if (m_tr->is_infinite(cell)) continue;
        
        const int index = cell->index(v);
        const Point& a = cell->vertex(m_tr->vertex_triple_index(index, 0))->point();
        const Point& b = cell->vertex(m_tr->vertex_triple_index(index, 1))->point();
        const Point& c = cell->vertex(m_tr->vertex_triple_index(index, 2))->point();
        const Vector nn = CGAL::cross_product(b-a, c-a);
        
        // 累加相邻顶点的法向
        div += nn * (cell->vertex((index+1)%4)->normal() +
                     cell->vertex((index+2)%4)->normal() +
                     cell->vertex((index+3)%4)->normal());
    }
    return div;
}

归一化模式:考虑 Voronoi 面积权重,更精确但计算量更大。

5.4 Laplacian 矩阵组装

使用余切公式(cotangent formula)组装离散 Laplacian:

template <class SparseLinearAlgebraTraits_d>
void assemble_poisson_row(typename SparseLinearAlgebraTraits_d::Matrix& A,
                          Vertex_handle vi,
                          typename SparseLinearAlgebraTraits_d::Vector& B,
                          double lambda)
{
    double diagonal = 0.0;
    
    // 遍历所有邻边
    for (auto edge : incident_edges(vi))
    {
        Vertex_handle vj = edge.first->vertex(edge.third);
        if (vj == vi) vj = edge.first->vertex(edge.second);
        if (m_tr->is_infinite(vj)) continue;
        
        // 计算余切权重
        double cij = cotan_geometric(edge);
        
        if (m_tr->is_constrained(vj)) {
            B[vi->index()] -= cij * vj->f();  // 移到右端项
        } else {
            A.set_coef(vi->index(), vj->index(), -cij, true);
        }
        diagonal += cij;
    }
    
    // 设置对角元
    if (vi->type() == Triangulation::INPUT)
        A.set_coef(vi->index(), vi->index(), diagonal + lambda, true);
    else
        A.set_coef(vi->index(), vi->index(), diagonal, true);
}

5.5 等值面提取

CGAL 使用 Mesh_3 包进行等值面提取:

// 创建网格域
Mesh_domain domain = Mesh_domain::create_Poisson_mesh_domain(
    function, 
    Sphere(inner_point, sm_sphere_radius),
    CGAL::parameters::relative_error_bound(sm_dichotomy_error / sm_sphere_radius)
);
 
// 定义网格标准
Mesh_criteria criteria(
    CGAL::parameters::facet_angle = sm_angle,
    CGAL::parameters::facet_size = sm_radius * spacing,
    CGAL::parameters::facet_distance = sm_distance * spacing
);
 
// 生成网格
C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(
    domain, criteria,
    CGAL::parameters::surface_only()
                     .manifold_with_boundary()
);

6. 使用示例

6.1 完整示例代码

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Poisson_reconstruction_function.h>
#include <CGAL/Mesh_triangulation_3.h>
#include <CGAL/Mesh_complex_3_in_triangulation_3.h>
#include <CGAL/Mesh_criteria_3.h>
#include <CGAL/Poisson_mesh_domain_3.h>
#include <CGAL/make_mesh_3.h>
#include <CGAL/facets_in_complex_3_to_triangle_mesh.h>
#include <CGAL/property_map.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/compute_average_spacing.h>
 
#include <vector>
#include <fstream>
 
// 类型定义
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::FT FT;
typedef Kernel::Point_3 Point;
typedef Kernel::Vector_3 Vector;
typedef std::pair<Point, Vector> Point_with_normal;
typedef CGAL::First_of_pair_property_map<Point_with_normal> Point_map;
typedef CGAL::Second_of_pair_property_map<Point_with_normal> Normal_map;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron;
 
// 泊松重建相关类型
typedef CGAL::Poisson_reconstruction_function<Kernel> Poisson_function;
typedef CGAL::Poisson_mesh_domain_3<Kernel> Mesh_domain;
typedef CGAL::Mesh_triangulation_3<Mesh_domain>::type Tr;
typedef CGAL::Mesh_complex_3_in_triangulation_3<Tr> C3t3;
typedef CGAL::Mesh_criteria_3<Tr> Mesh_criteria;
 
int main(int argc, char* argv[])
{
    // 1. 读取点云数据
    std::vector<Point_with_normal> points;
    const char* input_file = (argc > 1) ? argv[1] : "input.xyz";
    
    if (\!CGAL::IO::read_points(input_file, std::back_inserter(points),
                               CGAL::parameters::point_map(Point_map())
                                                .normal_map(Normal_map())))
    {
        std::cerr << "Error: cannot read file " << input_file << std::endl;
        return EXIT_FAILURE;
    }
    
    std::cout << "Loaded " << points.size() << " points" << std::endl;
    
    // 2. 创建泊松重建函数
    Poisson_function function(points.begin(), points.end(),
                              Point_map(), Normal_map());
    
    // 3. 计算隐函数(使用默认的 Eigen 求解器)
    if (\!function.compute_implicit_function())
    {
        std::cerr << "Error: cannot solve Poisson equation" << std::endl;
        return EXIT_FAILURE;
    }
    
    // 4. 计算平均点间距
    FT average_spacing = CGAL::compute_average_spacing<CGAL::Sequential_tag>(
        points, 6, CGAL::parameters::point_map(Point_map()));
    
    // 5. 设置网格参数
    FT sm_angle = 20.0;      // 最小三角形角度(度)
    FT sm_radius = 30.0;     // 最大三角形大小(相对于平均间距)
    FT sm_distance = 0.375;  // 表面逼近误差(相对于平均间距)
    
    Sphere bsphere = function.bounding_sphere();
    FT radius = CGAL::approximate_sqrt(bsphere.squared_radius());
    
    // 6. 创建网格域并生成网格
    Mesh_domain domain = Mesh_domain::create_Poisson_mesh_domain(
        function, bsphere,
        CGAL::parameters::relative_error_bound(sm_distance * average_spacing / (1000.0 * 5.0 * radius))
    );
    
    Mesh_criteria criteria(
        CGAL::parameters::facet_angle = sm_angle,
        CGAL::parameters::facet_size = sm_radius * average_spacing,
        CGAL::parameters::facet_distance = sm_distance * average_spacing
    );
    
    C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(
        domain, criteria,
        CGAL::parameters::surface_only()
                         .manifold_with_boundary()
    );
    
    // 7. 提取并保存结果
    Polyhedron output_mesh;
    CGAL::facets_in_complex_3_to_triangle_mesh(c3t3, output_mesh);
    
    std::ofstream out("output.off");
    out << output_mesh;
    out.close();
    
    std::cout << "Reconstruction completed. Output saved to output.off" << std::endl;
    
    return EXIT_SUCCESS;
}

6.2 便捷函数接口

CGAL 还提供了更简单的便捷函数:

#include <CGAL/poisson_surface_reconstruction.h>
 
// 使用便捷函数进行重建
bool success = CGAL::poisson_surface_reconstruction_delaunay(
    points.begin(), points.end(),           // 点云范围
    Point_map(), Normal_map(),              // 属性映射
    output_mesh,                            // 输出网格
    spacing,                                // 间距参数
    20.0,                                   // 角度参数
    30.0,                                   // 半径参数
    0.375,                                  // 距离参数
    CGAL::Manifold_with_boundary_tag()      // 流形标签
);

6.3 实际应用:文物数字化

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Poisson_reconstruction_function.h>
#include <CGAL/IO/read_points.h>
#include <CGAL/IO/OBJ.h>
#include <CGAL/compute_average_spacing.h>
#include <CGAL/edge_aware_upsampling_point_set.h>
#include <CGAL/jet_estimate_normals.h>
#include <CGAL/mst_orient_normals.h>
#include <iostream>
#include <vector>
 
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef Kernel::Vector_3 Vector;
typedef std::pair<Point, Vector> Point_with_normal;
typedef CGAL::Surface_mesh<Point> Mesh;
 
// 文物数字化完整流程
void digitize_cultural_heritage(const std::string& input_file, 
                                const std::string& output_file)
{
    std::vector<Point_with_normal> points;
    
    // 1. 读取扫描数据
    if (\!CGAL::IO::read_points(input_file, std::back_inserter(points),
                               CGAL::parameters::point_map(CGAL::First_of_pair_property_map<Point_with_normal>())
                                                .normal_map(CGAL::Second_of_pair_property_map<Point_with_normal>())))
    {
        std::cerr << "Failed to read input" << std::endl;
        return;
    }
    
    std::cout << "Loaded " << points.size() << " points" << std::endl;
    
    // 2. 预处理:上采样(如果点云太稀疏)
    if (points.size() < 10000) {
        std::vector<Point_with_normal> upsampled;
        CGAL::edge_aware_upsampling_point_set(
            points,
            std::back_inserter(upsampled),
            CGAL::parameters::point_map(CGAL::First_of_pair_property_map<Point_with_normal>())
                             .normal_map(CGAL::Second_of_pair_property_map<Point_with_normal>())
                             .sharpness_angle(25.0)
                             .edge_sensitivity(0.5)
                             .neighbor_radius(2.0)
                             .number_of_output_points(points.size() * 4)
        );
        points.swap(upsampled);
        std::cout << "Upsampled to " << points.size() << " points" << std::endl;
    }
    
    // 3. 预处理:法向估计(如果没有)
    // 法向定向
    CGAL::mst_orient_normals(
        points,
        12,  // K-近邻数
        CGAL::parameters::point_map(CGAL::First_of_pair_property_map<Point_with_normal>())
                         .normal_map(CGAL::Second_of_pair_property_map<Point_with_normal>())
    );
    
    // 4. 泊松重建
    CGAL::Poisson_reconstruction_function<Kernel> function(
        points.begin(), points.end(),
        CGAL::First_of_pair_property_map<Point_with_normal>(),
        CGAL::Second_of_pair_property_map<Point_with_normal>()
    );
    
    if (\!function.compute_implicit_function()) {
        std::cerr << "Poisson reconstruction failed" << std::endl;
        return;
    }
    
    // 5. 参数设置(根据文物细节调整)
    FT average_spacing = CGAL::compute_average_spacing<CGAL::Sequential_tag>(
        points, 6, CGAL::parameters::point_map(CGAL::First_of_pair_property_map<Point_with_normal>()));
    
    // 对于精细文物,使用更小的间距
    FT spacing = average_spacing * 0.5;  // 更精细的网格
    FT angle = 20.0;                      // 标准角度
    FT radius = 20.0;                     // 更小的三角形
    FT distance = 0.2;                    // 更精确的表面逼近
    
    // 6. 网格生成
    Mesh mesh;
    bool success = CGAL::poisson_surface_reconstruction_delaunay(
        points.begin(), points.end(),
        CGAL::First_of_pair_property_map<Point_with_normal>(),
        CGAL::Second_of_pair_property_map<Point_with_normal>(),
        mesh,
        spacing,
        angle,
        radius,
        distance,
        CGAL::Manifold_with_boundary_tag()
    );
    
    if (success) {
        CGAL::IO::write_OBJ(output_file, mesh);
        std::cout << "Saved to " << output_file << std::endl;
        std::cout << "Vertices: " <> mesh.number_of_vertices() << std::endl;
        std::cout << "Faces: " << mesh.number_of_faces() << std::endl;
    }
}
 
int main(int argc, char* argv[])
{
    std::string input = (argc > 1) ? argv[1] : "artifact.xyz";
    std::string output = (argc > 2) ? argv[2] : "artifact.obj";
    
    digitize_cultural_heritage(input, output);
    
    return 0;
}

7. 复杂度分析

7.1 时间复杂度

步骤复杂度说明
三角剖分构建 为输入点数
Delaunay 细化 为细化后顶点数
线性系统组装稀疏矩阵操作
线性系统求解 为迭代次数(共轭梯度)
等值面提取 为输出三角形数

总体复杂度约为

7.2 空间复杂度

  • 三角剖分
  • 稀疏矩阵(平均每个顶点约 15 个非零元)
  • 临时存储

总体空间复杂度为 ,其中 通常与 同阶或略大。

7.3 参数影响

  • spacing:控制输出网格分辨率。较小的值产生更精细的网格,但增加计算时间和内存使用。
  • sm_angle:控制三角形最小角度。较大的值产生更规则的三角形,但可能增加顶点数。
  • sm_distance:控制表面逼近精度。较小的值产生更精确的表面,但需要更多三角形。

8. 应用建议

8.1 适用场景

泊松重建最适合以下场景:

  • 封闭物体的扫描数据
  • 具有法向信息的点云
  • 需要水密输出的应用
  • 允许输出网格不经过输入点

8.2 预处理建议

  1. 法向估计:如果输入没有法向,使用 PCA 或 jet 拟合估计
  2. 法向定向:确保法向一致朝外(可使用 MST 传播)
  3. 去噪:移除离群点,减少噪声
  4. 采样:对于非常密集的点云,可适当下采样

8.3 常见问题

问题 1:重建结果有孔洞

  • 原因:输入点云不完整或法向不一致
  • 解决:启用 smoother_hole_filling 选项,或修复输入数据

问题 2:表面过于平滑,丢失细节

  • 原因:spacing 参数过大
  • 解决:减小 spacing 值,或使用更小的 sm_distance

问题 3:重建失败(线性系统求解失败)

  • 原因:输入点过少或法向全为零
  • 解决:检查输入数据质量

9. 参数调优指南

9.1 根据数据特征选择参数

数据特征推荐参数说明
高精度扫描spacing=0.3, distance=0.2保留细节
噪声数据spacing=0.8, distance=0.5平滑噪声
大规模数据spacing=1.0, max_vertices=1M控制内存
精细特征spacing=0.2, angle=15保留尖锐特征

9.2 多分辨率重建策略

// 粗粒度快速预览
void coarse_reconstruction(const Point_set& points) {
    FT spacing = CGAL::compute_average_spacing(points, 6) * 2.0;
    poisson_reconstruction(points, spacing, 20.0, 30.0, 0.5);
}
 
// 细粒度最终输出
void fine_reconstruction(const Point_set& points) {
    FT spacing = CGAL::compute_average_spacing(points, 6) * 0.5;
    poisson_reconstruction(points, spacing, 20.0, 20.0, 0.2);
}

参考文献

  1. Kazhdan, M., Bolitho, M., & Hoppe, H. (2006). Poisson surface reconstruction. In Proceedings of the fourth Eurographics symposium on Geometry processing (pp. 61-70).

  2. Kazhdan, M., & Hoppe, H. (2013). Screened Poisson surface reconstruction. ACM Transactions on Graphics, 32(3), 1-13.

  3. CGAL Documentation: Poisson Surface Reconstruction. https://doc.cgal.org/latest/Poisson_surface_reconstruction_3/