vcglib 说明(转载)

来源:互联网 发布:提高淘宝店铺关注率 编辑:程序博客网 时间:2024/06/04 23:38

先来看看 VCGlib 能做什么

  • 最基本的,它提供 Mesh(triangular mesh,tetrahedralmesh,三角网格或四面体网格)数据结构的定义,该数据结构支持对 Mesh数据的快速访问(拓扑信息、空间查询等)以及高效执行网格上算法;
  • 在 Mesh数据结构基础上,实现大量高效的网格算法,如网格修补、平滑、变形、曲率计算、细分、泊松盘采样、等值面计算等;
  • IO 支持,读写 PLY、OBJ、STL、3DS、OFF、DXF 等格式网格文件;
  • UI 支持,如 OpenGL 网格显示,Trackball 交互等。

VCGlib 的文档很简陋, 在线文档 并不是很全,可以自己用 Doxygen从下载的源代码生成 html API 文档 ,为此只需要(Windows 用户):

  1. 安装 Doxygen (Doxywizard) 和 Graphviz,将“Graphviz安装目录\Graphviz2.36\bin”添加到环境变量Path;
  2. 将本博客所附程序中的 “OpenGL Shadow\_Libs\vcglib\docs\doxyfile-all”文件拷贝到下载的 VCGlib 文件夹下的 “vcglib\docs\Doxygen” 下,这个文件是我配置好的 Doxygen配置文件;
  3. 用 Doxywizard 打开上一步拷贝的文件,点击“Run” 选项卡下的 “Run doxygen” 按钮,生成的 API 文档将位于“vcglib\docs\Doxygen\html-all” 下(html-all 文件夹将有 129M 大小)。

VCGlib 是纯头文件库,要 安装 只需将下载 VCGlib库目录添加到程序的头文件包含路径(有些IO函数如读写PLY需要包含相应.cpp文件)。

后面按照如下步骤讲解:

  1. 定义 Mesh 类型;
  2. 访问及指定 Mesh 的顶点、三角形(对三角网格,如果是四面体网格则是四面体,这里默认只讲三角网格)等数据;
  3. IO,读写 PLY、OBJ 等网格文件;
  4. 构造网格的拓扑信息,如顶点或三角形面法向量、三角形相邻三角形、边连接的三角形等信息;
  5. 网格处理,如法向量平滑、网格修补等。

定义 Mesh 类型的典型代码如下(API 文档主页 Basic Concepts,在线版 ):

#include "vcg/complex/complex.h"// 类型声明class MyVertex;class MyEdge;class MyFace;typedef vcg::UsedTypes<</FONT>  vcg::Use::AsVertexType,  vcg::Use  ::AsEdgeType,  vcg::Use  ::AsFaceType >MyUsedTypes;// 顶点类型class MyVertex : public vcg::Vertex<</FONT>MyUsedTypes,  vcg::vertex::Coord3f,  vcg::vertex::Normal3f,  vcg::vertex::BitFlags > { };// 边类型class MyEdge : public vcg::Edge<</FONT>MyUsedTypes,  vcg::edge::VertexRef,  vcg::edge::EFAdj,  vcg::edge::BitFlags > { };// 面类型,三角形class MyFace : public vcg::Face<</FONT>MyUsedTypes,  vcg::face::VertexRef,  vcg::face::Normal3f,  vcg::face::FFAdj,  vcg::face::BitFlags > { };// 网格类型typedef vcg::tri::TriMesh<</FONT>  std::vector,  std::vector,  std::vector >GLMesh;

抛开 MyUseTypes 不看,上面代码定义的网格类型为:

  • 网格包含属性:顶点、边、三角形数组(std::vector<>);
  • 每个顶点包含属性:空间坐标(3个float表示)、顶点法向量、标志位;
  • 每个边包含属性:顶点指针(指向该边的两个顶点)、边-面邻接信息、标志位;
  • 每个三角形面包含属性:顶点指针(指向该三角形的三个顶点)、面法向量、面-面邻接信息、标志位。

VCGlib 使用 Reference 数据结构,对每个边、面用指针记录其顶点、邻接面等信息,其他网格数据结构见wikipedia Polygon Mesh 条目。

为了做到足够通用, VCGlib 使用了C++ templatemetaprogramming(模板元编程)方法 。上面代码中的MyVertex、MyEdge、MyFace、GLMesh等类型包含哪些属性(模板参数)、属性的顺序(模板参数顺序)都是可以根据需要随意指定的(当然,必须包含足够的属性以执行相应网格算法),一般来说,最好使顶点、边、面包含标志位属性(BitFlags),BitFlags指示该顶点、边、面是否可写、可读、已删除(为了效率,例如,删除顶点操作可能并不立即删除顶点数据,而仅仅打个标志位,待所有操作完成再更新顶点数据)等。不去深入VCGlib元编程机理(说实话我还没弄清楚),可选个数模板参数是通过默认模板参数实现的,vcg::Vertex/Edge/Face<>将继承其模板参数。

下面列举所有可选的模板参数:

  • 网格 vcg::tri::TriMesh<>最多可有四个参数:顶点容器、边容器、面容器、半边容器(vcg::HEdge<>);
  • 顶点 vcg::Vertex<>  可以包含的属性有:坐标、法向量、颜色、纹理坐标、标志位、网格质量(网格在该点出优劣评价指标)、曲率、半径、顶点-边邻接信息、顶点-面邻接信息、顶点-半边邻接信息,等(API文档 Modules 选项卡 Vertex Components,在线版 );
  • 边 vcg::Edge<>  可以包含的属性有:顶点指针、颜色、标志位、网格质量、边-顶点邻接信息、边-边邻接信息、边-面邻接信息、边-半边邻接信息,等(API文档 Modules 选项卡 Edge Components,在线版 );
  • 面 vcg::Face<>  可以包含的属性有:顶点指针、法向量、颜色、标志位、网格质量、顶点-面邻接信息、面-边邻接信息、面-面邻接信息,等(API 文档Modules 选项卡 Face Components,在线版 )。

访问 Mesh 数据示例代码如下:

  // load mesh ...int i=0, j=0;// 见 vcg::tri::TriMesh<> -------------------------------------------------------------mesh.VN(); mesh.EN(); mesh.FN(); // 顶点、边、面个数,可能小于 vs/es/fs.size()  // 因为有些元素被删除时仅仅打了标志位而并未删除存储数据std::vector& vs = mesh.vert; // 顶点数组std::vector&   es = mesh.edge; // 边数组std::vector&   fs = mesh.face; // 面数组// 见 vcg::Vertex<> 及其 模板参数 -------------------------------------------------------GLMesh::VertexType& v = mesh.vert[i]; // 第 i 个顶点,假设 v.isD()==false,即未标志为已删除v.P().Z(); v.P().V(j);    // 顶点坐标,其xyz分量v.N().X(); // 顶点法向,其x分量// 见 vcg::Edge<> 及其 模板参数 ---------------------------------------------------------GLMesh::EdgeType& e = mesh.edge[i]; // 第 i 个边,假设 e.isD()==falseGLMesh::VertexType* pve = e.V(j);   // j=0,1,边的两个端点顶点的指针GLMesh::FaceType*   pfa = e.EFp();  // 边-面邻接信息,该边连接的第一个面// 见 vcg::Face<> 及其 模板参数 ---------------------------------------------------------GLMesh::FaceType& f = mesh.face[i]; // 第 i 个面(三角形),假设 f.isD()==falseGLMesh::VertexType* pvf = f.V(j);   // j=0,1,2,三角形面的三个顶点的指针f.N(); // 面的法向量GLMesh::FaceType* pfb = f.FFp(j); // 面-面邻接信息,j=0,1,2,面 f 通过其第j个边连接的第一个面// 可以通过返回的引用(左值)修改数据,但不要随便修改,见下文 ------------------------------------v.P().Y() += 3.2f;e.V(j) = &v;f.V(j) = &v;// 遍历所有顶点、边、面需要跳过标记为已删除的元素 ---------------------------------------------for(size_t i=0; ii){    if(vs[i].IsD()) continue;    // do some thing for each vertex vs[i] ...}    // 除非已经删除了所有标记为已删除元素的存储数据,比如:vcg::tri::Allocator::CompactVertexVector(mesh);vcg::tri::Allocator::CompactEdgeVector(mesh);vcg::tri::Allocator::CompactFaceVector(mesh);for(size_t i=0; ii){    // do some thing for each face fs[i] ...}

填充(Fill)Mesh 数据的示例代码如下 (API 文档主页 Creating and destroyingelements,在线版 ,代码摘自那里):

// VCGlib Reference 数据结构,依赖于指针,直接操作顶点、边、面数组 mesh.vert/edge/face 可能// 产生 std::vector<> 存储重新分配,此时,相关指针将失效,vcg::tri::Allocator<> 处理这些问题GLMesh m;GLMesh::VertexIterator vi = vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertices(m, 3);GLMesh::FaceIterator fi = vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFaces(m, 1);GLMesh::VertexPointer ivp[4];ivp[0]=&*vi; vi->P()=GLMesh::CoordType(0.0f,0.0f,0.0f); ++vi;ivp[1]=&*vi; vi->P()=GLMesh::CoordType(1.0f,0.0f,0.0f); ++vi;ivp[2]=&*vi; vi->P()=GLMesh::CoordType(0.0f,1.0f,0.0f); ++vi;fi->V(0)=ivp[0]; fi->V(1)=ivp[1]; fi->V(2)=ivp[2];// Alternative, more compact, method for adding a single vertexivp[3]= &*vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertex(m,GLMesh::CoordType(1.0f,1.0f,0.0f));// Alternative, method for adding a single face (once you have the vertex pointers)vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFace(m, ivp[1],ivp[0],ivp[3]);// 同理,如果自己保存了顶点等数据指针,需要在修改顶点、边、面数组后更新该指针 --------------------// a potentially dangerous pointer to a mesh elementGLMesh::FacePointer fp = &m.face[0];vcg::tri::Allocator<<SPAN class=title>GLMesh>::PointerUpdater<<SPAN class=title>GLMesh::FacePointer> pu;// now the fp pointer could be no more valid due to eventual re-allocation of the m.facevcg::tri::Allocator<<SPAN class=title>GLMesh>::AddVertices(m,3);vcg::tri::Allocator<<SPAN class=title>GLMesh>::AddFaces(m,1,pu);// check if an update of the pointer is needed and do it.if(pu.NeedUpdate()) pu.Update(fp);// 删除元素的代码如下 --------------------------------------------------------------------vcg::tri::Allocator<<SPAN class=title>GLMesh>::DeleteFace(m,m.face[0]);// 拷贝网格的代码如下,GLMesh 没有拷贝构造函数,也没有 operator= -----------------------------GLMesh m2;vcg::tri::Append<<SPAN class=title>GLMesh,GLMesh>::MeshCopy(m2,m);

IO,读写网格文件示例代码如下(API 文档主页 Loading and savingmeshes, 在线版 ):

// Mesh 文件一般至少包含顶点数组信息,还可以包含连接信息(三角形)、顶点法向量、顶点颜色、面颜色、// 面法向量、纹理坐标等等属性,用 mask 的二进制位来标记或控制读取或写入了 Mesh 文件的哪些属性// 见 vcg::tri::io::Mask,读取 PLY 需要包含文件 "vcglib/wrap/ply/plylib.cpp"(见这里)// 头文件包含:#include "wrap/io_trimesh/import.h" #include "wrap/io_trimesh/export.h"GLMesh m; int mask;// 读取 PLY 文件,并检查返回值,参数 mask 为可选,mask 是返回参数:读入了哪些属性if( vcg::tri::io::ImporterPLY::Open(m, "file_to_open.ply", mask)  != vcg::ply::E_NOERROR ) {  std::cout <</FONT>< "Load PLY file ERROR\n";}  // some modification to m and mask ...// 保存 PLY 文件,mask 是输入参数,控制 m 的哪些属性被写入到文件vcg::tri::io::ExporterPLY<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.ply", mask);// 读取或写入 OBJ 文件的代码,mask 作用同上if( vcg::tri::io::ImporterOBJ<<SPAN class=title>GLMesh>::Open(m, "file_to_open.obj", mask)  != vcg::tri::io::ImporterOBJ<<SPAN class=title>GLMesh>::E_NOERROR ) {    std::cout <<SPAN class=title><</SPAN> "Load OBJ file ERROR\n";}  // some modification to m and mask ...vcg::tri::io::ExporterOBJ<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.obj", mask);// 读取、写入网格文件,将根据文件扩展名自动匹配文件格式 ---------------------------------------int oerr = vcg::tri::io::Importer<<SPAN class=title>GLMesh>::Open(m, "file_to_open.off", mask);if( oerr != 0 ){  std::cout <<SPAN class=title><</SPAN> "Load mesh file ERROR: "    << vcg::tri::io::Importer<<SPAN class=attribute>GLMesh>::ErrorMsg(oerr) <<SPAN class=title><</SPAN> '\n';}  // some modification to m and mask ...int serr = vcg::tri::io::Exporter<<SPAN class=attribute>GLMesh>::Save(m, "file_to_save.3ds", mask);if( serr != 0 ){  std::cout <<SPAN class=title><</SPAN> "Save mesh file ERROR: "    << vcg::tri::io::Exporter<<SPAN class=attribute>GLMesh>::ErrorMsg(oerr) <<SPAN class=title><</SPAN> '\n';}

构造网格拓扑信息示例代码如下 (API 文档主页 Adjacency andTopology, 在线版 ):

  // load mesh ...vcg::tri::UpdateNormal<<SPAN class=title>GLMesh>::PerFaceNormalized(mesh); // 计算顶点法向量,并单位化vcg::tri::UpdateNormal<<SPAN class=title>GLMesh>::PerVertexNormalized(mesh); // 计算面法向量,并单位化vcg::tri::UpdateTopology<<SPAN class=title>GLMesh>::FaceFace(mesh); // 计算面-面邻接信息vcg::tri::UpdateTopology<<SPAN class=title>GLMesh>::AllocateEdge(mesh); // 计算边-面邻接信息,需要面-面信息vcg::Matrix44f mat(&glm::translate(glm::vec3(1,2,3))[0][0]);vcg::tri::UpdatePosition<<SPAN class=title>GLMesh>::Matrix(mesh, mat, true); // 更新顶点位置,并更新法向量// 在调用 UpdateTopology<>::FaceFace() 和 UpdateTopology<>::AllocateEdge() 后就构造了边到面// 的信息,对于 manifold 网格,每个边必连接两个三角形面,下面代码对边 i 查找其连接的面 fa 和 fbint i=0; GLMesh::EdgeType& e = mesh.edge[i];GLMesh::FaceType* fa = e.EFp();GLMesh::FaceType* fb = fa->FFp(e.EFi());

在准备这篇博客之初,研究 VCGlib 时,发现了 VCGlib 的一个 BUG,已经报告给开发者并得到确认( 见这里 ,看看时间,发现这篇博客因为一些原因拖了20多天...)。

网格处理示例代码如下:

vcg::tri::Clean<<SPAN class=title>GLMesh>::RemoveDuplicateVertex(mesh); // 去除重合的顶点vcg::tri::Smooth<<SPAN class=title>GLMesh>::VertexNormalLaplacian(mesh, 5); // 平滑顶点法向量float maxSizeHole = 2.0f; // fill 所有直径小于 maxSizeHole 的洞vcg::tri::Hole<<SPAN class=title>GLMesh>::EarCuttingIntersectionFill    <<SPAN class=title>vcg::tri::SelfIntersectionEar>>(mesh, maxSizeHole, false);

进一步学习的资源:

  • 下载的 VCGlib 源代码 “vcglib\apps\sample” 下的官方示例代码;
  • 源代码,结合 API 文档;
  • 基于 VCGlib 的软件 MeshLab ,可以用于网格文件处理。
0 0