Libigl学习笔记——二、第六章——外部库
- 第六章
- 6.1 状态序列化 State Serialization
- 6.2 混合 Matlab 代码 Mixing Matlab Code
- 6.3 从 Matlab 调用 Libigl 函数 Calling Libigl Functions From Matlab
- 6.4 闭多边形的三角测量 Triangulation Of Closed Polygons
- 6.5 闭合曲面的四面体化 Tetrahedralization Of Closed Surfaces
- 6.6 烘焙环境光遮蔽 Baking Ambient Occlusion
- 6.7 截屏 Screen Capture
- 6.8 使用 Embree 中的光线追踪进行屏幕外渲染 Off-screen rendering using ray tracing with Embree
- 6.9 网格上的布尔运算 Boolean Operations On Meshes
- 6.10 Csg 树 Csg Tree
第六章
使用矩阵作为基本类型的另一个积极副作用是,在libigl和其他软件和库之间交换数据很容易。
6.1 状态序列化 State Serialization
- 几何处理应用程序通常需要大量的计算时间和/或手动输入。序列化应用程序的状态是大大提高开发效率的简单策略。它允许在崩溃发生之前快速开始调试,避免每次都等待预计算发生,并且还使您的实验可重现,允许在同一输入数据上快速测试算法变体。
- 由于序列化基于指针的数据结构(例如半边数据结构(OpenMesh,CGAL)或基于指针的索引结构(VCG)非常困难,因此在几何处理中通常不考虑序列化。
- 在 libigl 中,序列化要简单得多,因为大多数函数使用基本类型,并且在极少数情况下使用指针(通常用于与外部库接口)。Libigl 捆绑了一个简单且独立的二进制和 XML 序列化框架,可大大减少向应用程序添加序列化所需的开销。
- 若要取消/序列化一组变量,请使用以下方法:
#include "igl/serialize.h"
bool b = true;
unsigned int num = 10;
std::vector<float> vec = {0.1,0.002,5.3};
// use overwrite = true for the first serialization to create or overwrite an
// existing file
igl::serialize(b,"B","filename",true);
// append following serialization to existing file
igl::serialize(num,"Number","filename");
igl::serialize(vec,"VectorName","filename");
// deserialize back to variables
igl::deserialize(b,"B","filename");
igl::deserialize(num,"Number","filename");
igl::deserialize(vec,"VectorName","filename");
- 目前支持所有基本数据类型(bool,int,float,double,…),以及std::string,基本 STL 容器,密集和稀疏的特征矩阵以及它们的嵌套。一些限制适用于指针。目前,循环或多对一种类型的链接结构未正确处理。假定每个指针指向不同的独立对象。未初始化的指针必须设置为 nullptr 在反序列化之前,以避免内存泄漏。目前不支持跨平台问题,例如小端、大端。若要使用户定义的类型可序列化,只需从该方法派 igl::Serializable 生并简单实现 InitSerialization 该方法即可。
- 假设应用程序的状态是一个网格和一组整数 id:
#include "igl/serialize.h"
struct State : public igl::Serializable
{
Eigen::MatrixXd V;
Eigen::MatrixXi F;
std::vector<int> ids;
void InitSerialization()
{
this->Add(V , "V");
this->Add(F , "F");
this->Add(ids, "ids");
}
};
- 如果需要对类型的序列化进行更多控制,可以重写以下函数或直接从接口 igl::SerializableBase 继承。
bool Serializable::PreSerialization() const;
void Serializable::PostSerialization() const;
bool Serializable::PreDeserialization();
void Serializable::PostDeserialization();
- 或者,如果您想要一种非侵入式的方式来序列化状态,则可以重载以下函数:
namespace igl
{
namespace serialization
{
template <> inline void serialize(const State& obj,std::vector<char>& buffer)
{
::igl::serialize(obj.V,std::string("V"),buffer);
::igl::serialize(obj.F,std::string("F"),buffer);
::igl::serialize(obj.ids,std::string("ids"),buffer);
}
template <> inline void deserialize(State& obj,const std::vector<char>& buffer)
{
::igl::deserialize(obj.V,std::string("V"),buffer);
::igl::deserialize(obj.F,std::string("F"),buffer);
::igl::deserialize(obj.ids,std::string("ids"),buffer);
}
}
}
- 等效地,您可以使用以下宏:
SERIALIZE_TYPE(State,
SERIALIZE_MEMBER(V)
SERIALIZE_MEMBER(F)
SERIALIZE_MEMBER_NAME(ids,"ids")
)
- 所有以前的代码都用于二进制序列化,如果您必须处理加载和保存时间变得更加重要的较大数据,这将特别有用。对于您想要手动读取和编辑序列化数据的情况,我们提供基于 tinyxml2 库的 XML 文件的序列化。在那里,您还可以选择使用二进制参数创建数据的部分二进制序列化,该参数在函数 serialize_xml() 中公开:
#include "igl/xml/serialize_xml.h"
int number;
// binary = false, overwrite = true
igl::serialize_xml(vec,"VectorXML",xmlFile,false,true);
// binary = true, overwrite = true
igl::serialize_xml(vec,"VectorBin",xmlFile,true,true);
igl::deserialize_xml(vec,"VectorXML",xmlFile);
igl::deserialize_xml(vec,"VectorBin",xmlFile);
- 对于用户定义的类型,派生自 XMLSerializable 。
- 上面的代码片段摘自示例 601。我们强烈建议您使应用程序的整个状态始终可序列化,因为在为科学报告准备图表时,这将为您省去很多麻烦。必须对数字进行小的更改是很常见的,并且能够在截屏之前序列化整个状态将在提交截止日期之前为您节省许多痛苦的时间。
- 601案例完整展示:
#include <igl/readOFF.h>
#include <igl/serialize.h>
#include <igl/xml/serialize_xml.h>
#include <iostream>
Eigen::MatrixXd V;
Eigen::MatrixXi F;
// derive from igl::Serializable to serialize your own type
struct State : public igl::Serializable
{
Eigen::MatrixXd V;
Eigen::MatrixXi F;
std::vector<int> ids;
// You have to define this function to
// register the fields you want to serialize
virtual void InitSerialization()
{
this->Add(V , "V");
this->Add(F , "F");
this->Add(ids, "ids");
}
};
alternatively you can do it like the following to have
a non-intrusive serialization:
//struct State
//{
// Eigen::MatrixXd V;
// Eigen::MatrixXi F;
// std::vector<int> ids;
//};
//
//
//namespace igl
//{
// namespace serialization
// {
// // the `template <>` is essential
// template <> inline void serialize(const State& obj,std::vector<char>& buffer){
// ::igl::serialize(obj.V,std::string("V"),buffer);
// ::igl::serialize(obj.F,std::string("F"),buffer);
// ::igl::serialize(obj.ids,std::string("ids"),buffer);
// }
// template <> inline void deserialize(State& obj,const std::vector<char>& buffer){
// ::igl::deserialize(obj.V,std::string("V"),buffer);
// ::igl::deserialize(obj.F,std::string("F"),buffer);
// ::igl::deserialize(obj.ids,std::string("ids"),buffer);
// }
// }
//}
//
OR:
//
//SERIALIZE_TYPE(State,
// SERIALIZE_MEMBER(V)
// SERIALIZE_MEMBER(F)
// SERIALIZE_MEMBER_NAME(ids,"ids")
//)
int main(int argc, char *argv[])
{
std::string binaryFile = "binData";
std::string xmlFile = "data.xml";
bool b = true;
unsigned int num = 10;
std::vector<float> vec = {0.1f,0.002f,5.3f};
// use overwrite = true for the first serialization to create or overwrite an existing file
igl::serialize(b,"B",binaryFile,true);
// append following serialization to existing file
igl::serialize(num,"Number",binaryFile);
igl::serialize(vec,"VectorName",binaryFile);
// deserialize back to variables
igl::deserialize(b,"B",binaryFile);
igl::deserialize(num,"Number",binaryFile);
igl::deserialize(vec,"VectorName",binaryFile);
State stateIn, stateOut;
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/2triangles.off",stateIn.V,stateIn.F);
// Save some integers in a vector
stateIn.ids.push_back(6);
stateIn.ids.push_back(7);
// Serialize the state of the application
igl::serialize(stateIn,"State",binaryFile,true);
// Load the state from the same file
igl::deserialize(stateOut,"State",binaryFile);
// Plot the state
std::cout << "Vertices: " << std::endl << stateOut.V << std::endl;
std::cout << "Faces: " << std::endl << stateOut.F << std::endl;
std::cout << "ids: " << std::endl
<< stateOut.ids[0] << " " << stateOut.ids[1] << std::endl;
// XML serialization
// binary = false, overwrite = true
igl::xml::serialize_xml(vec,"VectorXML",xmlFile,false,true);
// binary = true, overwrite = false
igl::xml::serialize_xml(vec,"VectorBin",xmlFile,true,false);
igl::xml::deserialize_xml(vec,"VectorXML",xmlFile);
igl::xml::deserialize_xml(vec,"VectorBin",xmlFile);
}
6.2 混合 Matlab 代码 Mixing Matlab Code
- Libigl 可以与 Matlab 接口,将数值繁重的计算卸载到 Matlab 脚本中。这种方法的主要优点是,您将能够在C++中开发高效而复杂的用户界面,同时探索 matlab 的语法和快速原型功能。特别是,在 libigl 应用程序中使用外部 Matlab 脚本允许在 C++ 应用程序运行时更改 Matlab 代码,从而大大提高了编码效率。
- 我们在示例 602 中演示了如何将 Matlab 集成到 libigl 应用程序中。该示例使用 Matlab 计算离散拉普拉斯算子的特征函数,依靠 libigl 进行网格 IO、可视化和计算拉普拉斯算子。
- Libigl 可以使用以下命令连接到现有的 Matlab 实例(或在 Linux/MacOSX 上启动一个新实例):
igl::mlinit(&engine);
- 余切拉普拉斯使用 igl::cotmatrix 计算并上传到 Matlab 工作区:
igl::cotmatrix(V,F,L);
igl::mlsetmatrix(&engine,"L",L);
- 现在可以对数据使用任何 Matlab 函数。例如,我们可以使用 spy 看到 L 的稀疏模式:
igl::mleval(&engine,"spy(L)");
Matlab spy函数是从基于 libigl 的应用程序调用的
- Matlab 计算的结果可以返回到C++应用程序
igl::mleval(&engine,"[EV,~] = eigs(-L,10,'sm')");
igl::mlgetmatrix(&engine,"EV",EV);
- 并使用 Libigl 查看器绘制
在 Matlab 中计算的拉普拉斯特征函数,在 libigl 查看器中绘制
6.2.1 保存 Matlab 工作区 Saving A Matlab Workspace
- 为了帮助调试,libigl 还提供了编写 Matlab .mat “Workspaces” 的函数。这个C++片段将网格及其稀疏拉普拉斯矩阵保存到文件中:
igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off", V, F);
igl::cotmatrix(V,F,L);
igl::MatlabWorkspace mw;
mw.save(V,"V");
mw.save_index(F,"F");
mw.save(L,"L");
mw.write("fertility.mat");
- 然后,可以将此工作区加载到 Matlab IDE 中:
load fertility.mat
- 依赖于 igl::MatlabWorkspace Matlab 库来编译和运行,但与上面的引擎例程相反,它将避免在执行时启动 Matlab 实例。
6.2.2 转储特征矩阵以复制并粘贴到 Matlab 中 Dumping Eigen Matrices To Copy And Paste Into Matlab
- Eigen 提供了一个复杂的 API,用于将其矩阵类型打印到屏幕上。Libigl 打包了一个特别有用的格式,这使得将标准输出从C++程序复制到 Matlab IDE 变得简单。代码:
igl::readOFF(TUTORIAL_SHARED_PATH "/2triangles.off", V, F);
igl::cotmatrix(V,F,L);
std::cout<<igl::matlab_format(V,"V")<<std::endl;
std::cout<<igl::matlab_format((F.array()+1).eval(),"F")<<std::endl;
std::cout<<igl::matlab_format(L,"L")<<std::endl;
- produces the output: 生成输出:
V = [
0 0 0
1 0 0
1 1 1
2 1 0
];
F = [
1 2 3
2 4 3
];
LIJV = [
1 1 -0.7071067811865476
2 1 0.7071067811865475
3 1 1.570092458683775e-16
1 2 0.7071067811865475
2 2 -1.638010440969447
3 2 0.6422285251880865
4 2 0.2886751345948129
1 3 1.570092458683775e-16
2 3 0.6422285251880865
3 3 -0.9309036597828995
4 3 0.2886751345948129
2 4 0.2886751345948129
3 4 0.2886751345948129
4 4 -0.5773502691896258
];
L = sparse(LIJV(:,1),LIJV(:,2),LIJV(:,3));
- 很容易复制并粘贴到 Matlab 中进行调试等。
6.3 从 Matlab 调用 Libigl 函数 Calling Libigl Functions From Matlab
- 也可以从 matlab 调用 libigl 函数,将它们编译为 MEX 函数。这可用于卸载 Matlab 应用程序的计算密集型部分以C++编码。
- 我们在示例 603 igl::readOBJ 中提供了一个包装器。我们计划将来为所有功能提供包装器,如果您对此功能感兴趣(或者如果您想帮助实现它),请告诉我们。
6.4 闭多边形的三角测量 Triangulation Of Closed Polygons
- 生成高质量的三角形和四面体网格是几何体加工中非常常见的任务。我们提供从三角形到三角形和泰特根的包装纸。
- 具有给定边界的三角形网格可以通过以下方式创建:
igl::triangulate(V,E,H,V2,F2,"a0.005q");
- 其中 E 是一组边界边 (#E x 2),是三角测量孔中包含的一组 2D 位置的点 (#H x 2), H ( V2 , F2 ) 是生成的三角测量。
- 可以将其他参数传递给 triangle 以控制质量: “a0.005q” 对三角形的最大面积和 20 度的最小角度强制实施边界。
- 在示例 604 中,正方形的内部(不包括其内部较小的正方形)是三角测量的。
- 604案例完整展示:
#include <igl/opengl/glfw/Viewer.h>
#include <igl/triangle/triangulate.h>
// Input polygon
Eigen::MatrixXd V;
Eigen::MatrixXi E;
Eigen::MatrixXd H;
// Triangulated interior
Eigen::MatrixXd V2;
Eigen::MatrixXi F2;
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
// Create the boundary of a square
V.resize(8,2);
E.resize(8,2);
H.resize(1,2);
// create two squares, one with edge length of 4,
// one with edge length of 2
// both centered at origin
V << -1,-1, 1,-1, 1,1, -1, 1,
-2,-2, 2,-2, 2,2, -2, 2;
// add the edges of the squares
E << 0,1, 1,2, 2,3, 3,0,
4,5, 5,6, 6,7, 7,4;
// specify a point that is inside a closed shape
// where we do not want triangulation to happen
H << 0,0;
// Triangulate the interior
// a0.005 means that the area of each triangle should
// not be greater than 0.005
// q means that no angles will be smaller than 20 degrees
// for a detailed set of commands please refer to:
// https://www.cs.cmu.edu/~quake/triangle.switch.html
igl::triangle::triangulate(V,E,H,"a0.005q",V2,F2);
// Plot the generated mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V2,F2);
viewer.launch();
}
多边形内部的三角化
6.5 闭合曲面的四面体化 Tetrahedralization Of Closed Surfaces
- 类似地,封闭歧管表面的内部可以使用包裹 Tetgen 库的函数 igl::tetrahedralize 进行四面体化(示例 605):
igl::tetrahedralize(V,F,"pq1.414", TV,TT,TF);
- 605案例完整展示:
#include <igl/opengl/glfw/Viewer.h>
#include <igl/copyleft/tetgen/tetrahedralize.h>
#include <igl/readOFF.h>
#include <igl/barycenter.h>
// Input polygon
Eigen::MatrixXd V;
Eigen::MatrixXi F;
Eigen::MatrixXd B;
// Tetrahedralized interior
Eigen::MatrixXd TV;
Eigen::MatrixXi TT;
Eigen::MatrixXi TF;
// This function is called every time a keyboard button is pressed
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
using namespace std;
using namespace Eigen;
if (key >= '1' && key <= '9')
{
double t = double((key - '1')+1) / 9.0;
VectorXd v = B.col(2).array() - B.col(2).minCoeff();
v /= v.col(0).maxCoeff();
vector<int> s;
for (unsigned i=0; i<v.size();++i)
if (v(i) < t)
s.push_back(i);
MatrixXd V_temp(s.size()*4,3);
MatrixXi F_temp(s.size()*4,3);
for (unsigned i=0; i<s.size();++i)
{
V_temp.row(i*4+0) = TV.row(TT(s[i],0));
V_temp.row(i*4+1) = TV.row(TT(s[i],1));
V_temp.row(i*4+2) = TV.row(TT(s[i],2));
V_temp.row(i*4+3) = TV.row(TT(s[i],3));
F_temp.row(i*4+0) << (i*4)+0, (i*4)+1, (i*4)+3;
F_temp.row(i*4+1) << (i*4)+0, (i*4)+2, (i*4)+1;
F_temp.row(i*4+2) << (i*4)+3, (i*4)+2, (i*4)+0;
F_temp.row(i*4+3) << (i*4)+1, (i*4)+2, (i*4)+3;
}
viewer.data().clear();
viewer.data().set_mesh(V_temp,F_temp);
viewer.data().set_face_based(true);
}
return false;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
// Load a surface mesh
igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off",V,F);
// Tetrahedralize the interior
igl::copyleft::tetgen::tetrahedralize(V,F,"pq1.414Y", TV,TT,TF);
// Compute barycenters
igl::barycenter(TV,TT,B);
// Plot the generated mesh
igl::opengl::glfw::Viewer viewer;
viewer.callback_key_down = &key_down;
key_down(viewer,'5',0);
viewer.launch();
}
曲面网格内部的四面体化
6.6 烘焙环境光遮蔽 Baking Ambient Occlusion
- 环境光遮蔽是一种渲染技术,用于计算表面每个点对环境照明的曝光。它通常编码为与网格顶点关联的标量(在 0 和 1 之间归一化)。
- 从形式上讲,环境光遮蔽定义为:
- 其中 Vp,ω 是 p 处的可见性函数,如果 p 在方向 ω 上被遮挡,则定义为零,否则定义为 1,并且 dω 是积分变量 ω 的无穷小立体角步长。
- 积分通常通过在每个顶点周围以随机方向投射光线来近似。可以使用以下函数计算此近似值:
igl::ambient_occlusion(V,F,V_samples,N_samples,500,AO);
- 给定 中 V 描述的场景 F ,计算其关联法线为 的 N_samples 点 V_samples 的环境光遮蔽。可以控制投射光线的数量(通常至少需要 300-500 条光线才能获得平滑的结果),并将结果作为 AO 每个样本的单个标量返回。
- 环境光遮蔽可用于使表面颜色变暗,如例 606 所示
- 606案例完整展示:
#include <igl/avg_edge_length.h>
#include <igl/per_vertex_normals.h>
#include <igl/readOFF.h>
#include <igl/embree/ambient_occlusion.h>
#include <igl/opengl/glfw/Viewer.h>
#include <iostream>
// Mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;
Eigen::VectorXd AO;
// It allows to change the degree of the field when a number is pressed
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
using namespace Eigen;
using namespace std;
const RowVector3d color(0.9,0.85,0.9);
switch(key)
{
case '1':
// Show the mesh without the ambient occlusion factor
viewer.data().set_colors(color);
break;
case '2':
{
// Show the mesh with the ambient occlusion factor
MatrixXd C = color.replicate(V.rows(),1);
for (unsigned i=0; i<C.rows();++i)
C.row(i) *= AO(i);//std::min<double>(AO(i)+0.2,1);
viewer.data().set_colors(C);
break;
}
case '.':
viewer.core().lighting_factor += 0.1;
break;
case ',':
viewer.core().lighting_factor -= 0.1;
break;
default: break;
}
viewer.core().lighting_factor =
std::min(std::max(viewer.core().lighting_factor,0.f),1.f);
return false;
}
int main(int argc, char *argv[])
{
using namespace std;
using namespace Eigen;
cout<<
"Press 1 to turn off Ambient Occlusion"<<endl<<
"Press 2 to turn on Ambient Occlusion"<<endl<<
"Press . to turn up lighting"<<endl<<
"Press , to turn down lighting"<<endl;
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off", V, F);
MatrixXd N;
igl::per_vertex_normals(V,F,N);
// Compute ambient occlusion factor using embree
igl::embree::ambient_occlusion(V,F,V,N,500,AO);
AO = 1.0 - AO.array();
// Show mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
viewer.callback_key_down = &key_down;
key_down(viewer,'2',0);
viewer.data().show_lines = false;
viewer.core().lighting_factor = 0.0f;
viewer.launch();
}
渲染的没有(左)和(右)环境光遮蔽的网格
6.7 截屏 Screen Capture
- Libigl 支持通过 stb 映像代码读取和写入.png文件。
- 使用本教程中使用的查看器,可以使用以下函数在内存缓冲区中渲染场景: igl::opengl::ViewerCore::draw_buffer
// Allocate temporary buffers for 1280x800 image
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> G(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> B(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> A(1280,800);
// Draw the scene in the buffers
viewer.core.draw_buffer(viewer.data(),false,R,G,B,A);
// Save it to a PNG
igl::png::writePNG(R,G,B,A,"out.png");
- 在示例 607 中,场景以临时 png 呈现,并用于为四边形设置纹理。
- 607案例完整展示:
#include <igl/readOFF.h>
#include <igl/opengl/glfw/Viewer.h>
#include <iostream>
#include <igl/png/writePNG.h>
#include <igl/png/readPNG.h>
// This function is called every time a keyboard button is pressed
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
if (key == '1')
{
// Allocate temporary buffers
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> G(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> B(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> A(1280,800);
// Draw the scene in the buffers
viewer.core().draw_buffer(
viewer.data(),false,R,G,B,A);
// Save it to a PNG
igl::png::writePNG(R,G,B,A,"out.png");
}
if (key == '2')
{
// Allocate temporary buffers
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R,G,B,A;
// Read the PNG
igl::png::readPNG("out.png",R,G,B,A);
// Replace the mesh with a triangulated square
Eigen::MatrixXd V(4,3);
V <<
-0.5,-0.5,0,
0.5,-0.5,0,
0.5, 0.5,0,
-0.5, 0.5,0;
Eigen::MatrixXi F(2,3);
F <<
0,1,2,
2,3,0;
Eigen::MatrixXd UV(4,2);
UV <<
0,0,
1,0,
1,1,
0,1;
viewer.data().clear();
viewer.data().set_mesh(V,F);
viewer.data().set_uv(UV);
viewer.core().align_camera_center(V);
viewer.data().show_texture = true;
// Use the image as a texture
viewer.data().set_texture(R,G,B);
}
return false;
}
int main(int argc, char *argv[])
{
// Load a mesh in OFF format
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F);
std::cerr << "Press 1 to render the scene and save it in a png." << std::endl;
std::cerr << "Press 2 to load the saved png and use it as a texture." << std::endl;
// Plot the mesh and register the callback
igl::opengl::glfw::Viewer viewer;
viewer.callback_key_down = &key_down;
viewer.data().set_mesh(V, F);
viewer.launch();
}
场景以临时 png 呈现,并用于为四边形设置纹理
6.8 使用 Embree 中的光线追踪进行屏幕外渲染 Off-screen rendering using ray tracing with Embree
- 如果在没有 OpenGL 支持的情况下编译 libigl,或者当交互式查看器不实用时,仍然可以使用 Embree 库在内存中呈现视图。目前仅支持三角网格。用法与屏幕捕获教程非常相似。
// Create embree renderer object
igl::embree::EmbreeRenderer er;
// Specify mesh, tell embree to optimize for static scene
er.set_mesh(V,F,true);
// Specify scalar data, use JET color map to convert to colors
er.set_data(K,igl::COLOR_MAP_TYPE_JET);
// Since the render is not interactive, need to specify scene parameters
// the default view is identical to the interactive viewer
Eigen::Matrix3d rot_matrix;
// Specify rotation matrix:
// 10 degrees around X axis
// 5 degrees around Y axis
// 4 degrees around Z axis
rot_matrix = Eigen::AngleAxisd( 10*igl::PI/180.0, Eigen::Vector3d::UnitX())
* Eigen::AngleAxisd( 5*igl::PI/180.0, Eigen::Vector3d::UnitY())
* Eigen::AngleAxisd( 4*igl::PI/180.0, Eigen::Vector3d::UnitZ());
er.set_rot(rot_matrix);
// Specify relative zoom factor
er.set_zoom(1.5);
// Request orthographic projection
er.set_orthographic(false);
// Allocate temporary buffers for 1280x800 image
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> G(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> B(1280,800);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> A(1280,800);
// Render view
er.render_buffer(R,G,B,A);
// Save it to a PNG
igl::png::writePNG(R,G,B,A,png_file);
- 在示例 608 中,场景在内存缓冲区中渲染并另存为 png 文件。
- 608案例完整展示:
#include <igl/gaussian_curvature.h>
#include <igl/massmatrix.h>
#include <igl/invert_diag.h>
#include <igl/read_triangle_mesh.h>
#include <igl/png/writePNG.h>
#include <igl/PI.h>
#include <Eigen/Geometry>
// embree
#include <igl/embree/EmbreeRenderer.h>
#include <iostream>
int main(int argc, char *argv[])
{
int width=640;
int height=480;
Eigen::MatrixXd V;
Eigen::MatrixXi F;
const char *mesh_file=argc > 1 ? argv[1] : TUTORIAL_SHARED_PATH "/fertility.off";
const char *png_file=argc > 2 ? argv[2]: "fertility_curvature.png";
// Load mesh
igl::read_triangle_mesh(mesh_file, V,F);
Eigen::VectorXd K;
// Compute integral of Gaussian curvature
igl::gaussian_curvature(V,F,K);
// Compute mass matrix
Eigen::SparseMatrix<double> M,Minv;
igl::massmatrix(V,F,igl::MASSMATRIX_TYPE_DEFAULT,M);
igl::invert_diag(M,Minv);
// Divide by area to get integral average
K = (Minv*K).eval();
// embree object
igl::embree::EmbreeRenderer er;
er.set_mesh(V,F,true);
//er.set_uniform_color(Eigen::RowVector3d(0.3,0.3,0.3));
er.set_data(K,igl::COLOR_MAP_TYPE_JET);
Eigen::Matrix3d rot_matrix;
// specify rotation
rot_matrix = Eigen::AngleAxisd( 10*igl::PI/180.0, Eigen::Vector3d::UnitX())
* Eigen::AngleAxisd( 5*igl::PI/180.0, Eigen::Vector3d::UnitY())
* Eigen::AngleAxisd( 4*igl::PI/180.0, Eigen::Vector3d::UnitZ());
er.set_rot(rot_matrix);
er.set_zoom(1.5);
er.set_orthographic(false);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R(width, height);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> G(width, height);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> B(width, height);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> A(width, height);
// render view using embree
er.render_buffer(R,G,B,A);
std::cout<<"Rendered scene saved to "<<png_file<<std::endl;
// save to PNG file
igl::png::writePNG(R,G,B,A,png_file);
return 0;
}
生育雕像将曲率显示为标量场,用 embree 渲染
6.9 网格上的布尔运算 Boolean Operations On Meshes
- 构造立体几何 (CSG) 是一种将复杂曲面定义为空间实体区域上许多集合操作的结果的技术:并集、交集差分、集合差分、对称差分、补码。通常,CSG 库隐式表示这些操作的输入和输出:实体 A 被定义为某些函数 a(x) “返回 true”的开放 x 点集。此形状的曲面是 中 A 所有点 x 的闭合。
- 使用这种表示形式,布尔运算就很简单了。例如,固体的并集 A 和 B
- 将许多这些操作串在一起,可以设计出相当复杂的形状。典型的 CSG 库可能只保留规范形状的显式基写表示形式:半空间、二次曲线等。
- 在 libigl 中,我们目前没有隐式曲面表示。相反,我们希望我们的用户使用实体形状的显式三角形网格边界表示。CSG 操作很难通过边界表示进行鲁棒计算,但仍然很有用。
- 为了计算一个带有顶点 VA 和三角形的三角形网格 FA 以及另一个网格 VB 的布尔运算 FB ,libigl 首先计算一个统一的“网格排列”(参见 36 []),其中包含顶点 V 和三角形, F 其中所有三角形-三角形交集都已“解析”。也就是说,边和顶点正好在相交线上添加,因此生成的非流形网格 (V,F) 没有自相交。
- 然后libigl根据其缠绕数向量标记由排列表面包围的每个“单元”:相对于每个输入网格 (wA,wB) 的缠绕数。最后,根据所需的操作(例如并集,交集),提取相应单元格的边界。
- 调用 libigl 的布尔运算很简单。要计算 和 (VB,FB) 到新网格 (VC,FC) 中的 (VA,FA) 并集,请使用:
igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,MESH_BOOLEAN_TYPE_UNION,VC,FC);
-
下图显示了两个网格上的每个布尔运算。
示例布尔值对 Cheburashka(红色)和 Knight(绿色)执行布尔运算。从左到右:并集、交集、集合减去、对称差分 (XOR)、“求解”。底行显示内表面,较深的颜色表示背面三角形
-
并集、对称差分和“决心”具有相同的外观,但对内部结构的处理不同。联合没有内表面:三角形不包含在输出中。对称差值是与“解决”相同的三角形集,但内表面的方向已反转,表明操作的固体结果。“解析”运算并不是真正的布尔运算,它只是解析所有交点并将重合顶点粘合在一起,保持原始三角形方向的结果。
-
Libigl还为软木塞提供了一个包装纸 igl::copyleft::cork::mesh_boolean ,它通常更快,但并不总是坚固的。
-
609案例完整展示:
#include <igl/readOFF.h>
//#undef IGL_STATIC_LIBRARY
#include <igl/copyleft/cgal/mesh_boolean.h>
#include <igl/opengl/glfw/Viewer.h>
#include <Eigen/Core>
#include <iostream>
Eigen::MatrixXd VA,VB,VC;
Eigen::VectorXi J,I;
Eigen::MatrixXi FA,FB,FC;
igl::MeshBooleanType boolean_type(
igl::MESH_BOOLEAN_TYPE_UNION);
const char * MESH_BOOLEAN_TYPE_NAMES[] =
{
"Union",
"Intersect",
"Minus",
"XOR",
"Resolve",
};
void update(igl::opengl::glfw::Viewer &viewer)
{
igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,boolean_type,VC,FC,J);
Eigen::MatrixXd C(FC.rows(),3);
for(size_t f = 0;f<C.rows();f++)
{
if(J(f)<FA.rows())
{
C.row(f) = Eigen::RowVector3d(1,0,0);
}else
{
C.row(f) = Eigen::RowVector3d(0,1,0);
}
}
viewer.data().clear();
viewer.data().set_mesh(VC,FC);
viewer.data().set_colors(C);
std::cout<<"A "<<MESH_BOOLEAN_TYPE_NAMES[boolean_type]<<" B."<<std::endl;
}
bool key_down(igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)
{
switch(key)
{
default:
return false;
case '.':
boolean_type =
static_cast<igl::MeshBooleanType>(
(boolean_type+1)% igl::NUM_MESH_BOOLEAN_TYPES);
break;
case ',':
boolean_type =
static_cast<igl::MeshBooleanType>(
(boolean_type+igl::NUM_MESH_BOOLEAN_TYPES-1)%
igl::NUM_MESH_BOOLEAN_TYPES);
break;
case '[':
viewer.core().camera_dnear -= 0.1;
return true;
case ']':
viewer.core().camera_dnear += 0.1;
return true;
}
update(viewer);
return true;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
igl::readOFF(TUTORIAL_SHARED_PATH "/cheburashka.off",VA,FA);
igl::readOFF(TUTORIAL_SHARED_PATH "/decimated-knight.off",VB,FB);
// Plot the mesh with pseudocolors
igl::opengl::glfw::Viewer viewer;
// Initialize
update(viewer);
viewer.data().show_lines = true;
viewer.callback_key_down = &key_down;
viewer.core().camera_dnear = 3.9;
cout<<
"Press '.' to switch to next boolean operation type."<<endl<<
"Press ',' to switch to previous boolean operation type."<<endl<<
"Press ']' to push near cutting plane away from camera."<<endl<<
"Press '[' to pull near cutting plane closer to camera."<<endl<<
"Hint: investigate _inside_ the model to see orientation changes."<<endl;
viewer.launch();
}
示例布尔值对 Cheburashka(红色)和 Knight(绿色)执行布尔运算。从左到右:并集、交集、集合减去、对称差分 (XOR)、“求解”。底行显示内表面,较深的颜色表示背面三角形
6.10 Csg 树 Csg Tree
- 上一节讨论如何 igl::copyleft::cgal::mesh_boolean 计算两个输入三角形网格上的单个布尔运算的结果。当使用构造实体几何 (CSG) 作为建模范例时,形状表示为许多此类二元运算的结果。序列存储在二叉树中。
- Libigl 在内部使用精确的算法来稳健地构建中间布尔结果。将此结果“舍入”为浮点数(甚至双精度)如果重新注入到进一步的布尔运算中,则会导致问题。为了方便 CSG 树操作并鼓励调用者不要显式调用 igl::copyleft::cgal::mesh_boolean 多次,libigl 实现了一个类 igl::copyleft::cgal::CSGTree 。此类的叶节点只是“实心”网格(否则输入良好 igl::copyleft::cgal::mesh_boolean )。树的内部节点将两个子节点与布尔运算组合在一起。使用初始化器列表构造函数,可以轻松地对特定的树结构进行硬编码。下面是一个立方体 A 和球体 B 的交集减去三个圆柱体并集的示例:
// Compute result of (A ∩ B) \ ((C ∪ D) ∪ E)
igl::copyleft::cgal::CSGTree<MatrixXi> CSGTree =
{ { {VA,FA},{VB,FB},"i"},{ { {VC,FC},{VD,FD},"u"}, {VE,FE},"u"},"m"};
CSG 树将形状表示为二进制布尔运算的组合
- 示例610计算每个中间CSG结果,然后计算最终复合结果。
- 610案例完整展示:
#include <igl/read_triangle_mesh.h>
#include <igl/copyleft/cgal/CSGTree.h>
#include <igl/opengl/glfw/Viewer.h>
#include <igl/jet.h>
#include <Eigen/Core>
int main(int argc, char * argv[])
{
using namespace Eigen;
using namespace igl::copyleft::cgal;
using namespace std;
using namespace igl;
cout<<R"(
[,] Toggle between boolean sub-tree operations
)";
MatrixXi FA,FB,FC,FD,FE;
MatrixXd VA,VB,VC,VD,VE;
// Read in inputs as double precision floating point meshes
read_triangle_mesh(TUTORIAL_SHARED_PATH "/cube.obj" ,VA,FA);
read_triangle_mesh(TUTORIAL_SHARED_PATH "/sphere.obj" ,VB,FB);
read_triangle_mesh(TUTORIAL_SHARED_PATH "/xcylinder.obj",VC,FC);
read_triangle_mesh(TUTORIAL_SHARED_PATH "/ycylinder.obj",VD,FD);
read_triangle_mesh(TUTORIAL_SHARED_PATH "/zcylinder.obj",VE,FE);
igl::opengl::glfw::Viewer viewer;
int num_views = 5+4;
int view_id = num_views-1;
const auto & update = [&]()
{
viewer.data().clear();
// CSGTree templated on type of F
VectorXd I;
const auto & set_mesh =
[&](const MatrixXd & V, const MatrixXi & F, const int i)
{
viewer.data().set_mesh(V,F);
I = VectorXd::Constant(F.rows(),1,i);
};
switch(view_id)
{
case 0:
set_mesh(VA,FA,5);
break;
case 1:
set_mesh(VB,FB,4);
break;
case 2:
set_mesh(VC,FC,3);
break;
case 3:
set_mesh(VD,FD,2);
break;
case 4:
set_mesh(VE,FE,1);
break;
default:
{
CSGTree M;
Matrix<MatrixXi::Index,Dynamic,1> J;
switch(view_id)
{
case 5:
// Compute result of (A ∩ B)
M = {{VA,FA},{VB,FB},"i"};
J = M.J().array()+0;
break;
case 6:
// Compute result of (C ∪ D)
M = {{VC,FC},{VD,FD},"u"};
J = M.J().array()+FA.rows()+FB.rows();
break;
case 7:
// Compute result of (C ∪ D) ∪ E
M = {{{VC,FC},{VD,FD},"u"},{VE,FE},"u"};
J = M.J().array()+FA.rows()+FB.rows();
break;
case 8:
// Compute result of (A ∩ B) \ ((C ∪ D) ∪ E)
M = {{{VA,FA},{VB,FB},"i"},{{{VC,FC},{VD,FD},"u"},{VE,FE},"u"},"m"};
J = M.J().array()+0;
break;
default:
assert(false && "unknown view id");
}
viewer.data().set_mesh(M.cast_V<MatrixXd>(),M.F());
I.resize(M.F().rows(),1);
// Compute colors based on original facets
for(int f = 0;f<M.F().rows();f++)
{
const int j = J(f);
I(f) =
(int)(j<FA.rows())+
(int)(j<FA.rows()+FB.rows())+
(int)(j<FA.rows()+FB.rows()+FC.rows())+
(int)(j<FA.rows()+FB.rows()+FC.rows()+FD.rows())+
(int)(j<FA.rows()+FB.rows()+FC.rows()+FD.rows()+FE.rows());
}
}
}
MatrixXd C;
jet(I,1,5,C);
viewer.data().set_colors(C);
};
update();
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer &viewer, unsigned char key, int mods)->bool
{
switch(key)
{
case ']':
view_id = (view_id+1)%num_views;
break;
case '[':
view_id = (view_id+num_views-1)%num_views;
break;
default:
return false;
}
update();
return true;
};
viewer.launch();
}
示例 610 计算 5 个输入网格上的复杂 CSG 树操作