1、SDF形状直径函数
SDF英文全称是Shape Diameter Function也就是形状直径函数。SDF是一个定义在网格表面的标量函数,它表达了网格表面上相邻各点物体体积直径的测量方法,以基于体积的形状函数为基础,在很大程度上能对相同对象的姿势改变保持无关性并且不同对象的相似部分维持相似值。下面是网格表面一个SDF值的示意图;给定表面网格上的一点,并以它为中心我们使用一个锥沿着内向法线方向(与法线方向相反的方向)发送数条射线,从锥的内部一直到达网格的另一面。一点处的SDF定义为所有射线长度的加权平均值,这属于所有长度中值的一个标准偏差。
2、使用CGAL计算模型的SDF值
CGAL4.5及以上版本提供了一个 Triangulated Surface Mesh Segmentation package(三角曲面网格分割包),这个包里面用提供计算网格表面SDF值的方法并用SDF来做网格分割,但是,我们也可以用SDF进行抽骨架。
3、Assimp
Assimp(Open Asset Import Library)是一个支持读取多种模型资源的开源库,当前最新的版本是3.0版,支持读取obj等许多格式的3D模型
4、使用Assimp将模型数据导入到CGLA AssimpToPolyhedron
要使用CGAL里的sdf_values()函数计算SDF,必须将我们自己定义的网格模型转为CGAL里的多面体类Polyhedron可以识别的模型;这里使用通用的obj模型,并使用Assimp转化到CGAL的多面体类。
#include <CGAL/property_map.h> #include <CGAL/internal/Operations_on_polyhedra/compute_normal.h> #include <vector> #include <assimp/Importer.hpp> // C++ importer interface #include <assimp/scene.h> // Output data structure #include <assimp/postprocess.h> // Post processing flags #include "vpoint.h" #include <fstream> template <class HDS> class Builder_obj : public CGAL::Modifier_base<HDS> { private: typedef typename HDS::Vertex::Point Point; typedef typename CGAL::Polyhedron_incremental_builder_3<HDS> Builder; const char* pcszfileName; const aiScene* scene; public: Builder_obj(const char* pFilename) { pcszfileName = pFilename; } ~Builder_obj() {} void operator()(HDS& hds) { Builder builder(hds,true); Assimp::Importer importer; scene = importer.ReadFile(pcszfileName, aiProcessPreset_TargetRealtime_Quality); if (!scene) { fprintf (stderr, "ERROR: reading mesh %s\n", pcszfileName); return; } builder.begin_surface(3,1,6); for (unsigned int m_i = 0; m_i < scene->mNumMeshes; m_i++) { const aiMesh* mesh = scene->mMeshes[m_i]; for (unsigned int v_i = 0; v_i < mesh->mNumVertices; v_i++) { if (mesh->HasPositions()) { const aiVector3D* vp = &(mesh->mVertices[v_i]); //printf (" vp %i (%f,%f,%f)\n", v_i, vp->x, vp->y, vp->z); builder.add_vertex(Point(vp->x, vp->y, vp->z)); } } for (unsigned int j = 0; j < mesh->mNumFaces; ++j) { const aiFace &face = mesh->mFaces[j]; assert(face.mNumIndices == 3); builder.begin_facet(); builder.add_vertex_to_facet(face.mIndices[0]); builder.add_vertex_to_facet(face.mIndices[1]); builder.add_vertex_to_facet(face.mIndices[2]); builder.end_facet(); } } builder.end_surface(); } }; typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel; typedef CGAL::Polyhedron_3<Kernel> Polyhedron; typedef Polyhedron::HalfedgeDS HalfedgeDS; typedef Polyhedron::Facet Facet; typedef Kernel::Vector_3 Vector; //typedef Polyhedron::Vertex_handle Vertex_handle; //typedef Polyhedron::Point_3 Point_3; //typedef Polyhedron::Halfedge_around_facet_circulator Halfedge_facet_circulator; int _tmain(int argc, _TCHAR* argv[]) { Polyhedron P; Builder_obj<HalfedgeDS> Mesh("C:/Users/niewenchao/Desktop/obj模型/memento.obj"); P.delegate(Mesh);
这样经过上一步我们用了多面体类Polyhedron,现在计算SDF值
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h> #include <CGAL/boost/graph/graph_traits_Polyhedron_3.h> #include <CGAL/IO/Polyhedron_iostream.h> #include <CGAL/mesh_segmentation.h> #include <CGAL/property_map.h> #include <iostream> #include <fstream> typedefCGAL::Exact_predicates_inexact_constructions_kernelKernel; typedef CGAL::Polyhedron_3<Kernel> Polyhedron; int main() { // create and read Polyhedron Polyhedron mesh; std::ifstream input("data/cactus.off"); if ( !input || !(input >> mesh) || mesh.empty() ) { std::cerr << "Not a valid off file." << std::endl; return EXIT_FAILURE; } // create a property-map typedef std::map<Polyhedron::Facet_const_handle, double> Facet_double_map; Facet_double_map internal_map; boost::associative_property_map<Facet_double_map> sdf_property_map(internal_map); // compute SDF values std::pair<double, double> min_max_sdf = CGAL::sdf_values(mesh, sdf_property_map); // It is possible to compute the raw SDF values and post-process them using // the following lines: // const std::size_t number_of_rays = 25; // cast 25 rays per facet // const double cone_angle = 2.0 / 3.0 * CGAL_PI; // set cone opening-angle // CGAL::sdf_values(mesh, sdf_property_map, cone_angle, number_of_rays, false); // std::pair<double, double> min_max_sdf = // CGAL::sdf_values_postprocessing(mesh, sdf_property_map); // print minimum & maximum SDF values std::cout << "minimum SDF: " << min_max_sdf.first << " maximum SDF: " << min_max_sdf.second << std::endl; // print SDF values for(Polyhedron::Facet_const_iterator facet_it = mesh.facets_begin(); facet_it != mesh.facets_end(); ++facet_it) { std::cout << sdf_property_map[facet_it] << " "; } std::cout << std::endl; }
现在计算得到了模型的片元SDF值,一个片元散点值是片元中心点沿着模型片元的法向量方向移动一半的SDF值得到的点。
//得到片元中心点 VPoint3 point; CGAL_For_all(he,end) { Polyhedron::Point_3 point_temp = he->vertex()->point(); point = point + VPoint3(point_temp.x(),point_temp.y(),point_temp.z()); } point = point/3;
//片元中心点转化为散点 VPoint3 point_projection = get_projection(point,nor,sdf_property_map[facet_it]*min_max_sdf.second);
三维空间中一个点沿着法线方向移动一段距离方法是:该点坐标+单位化法向量*移动距离
//片元中心点转散点函数 VPoint3 get_projection(VPoint3 pt,VPoint3 no, float fsdf){ no = FNormalize(no); VPoint3 propt = pt - no*fsdf/2.0000; return propt; }
7、实验结果
Armadillo原图
Armadillo散点图
dog原图
dog散点图