官方教程地址:
本章中内容:
进入示例之前,我们总结了 libigl 的主要设计原则:
libigl 使用特征库对向量和矩阵进行编码。我们建议您在阅读本教程中的示例时保留密集且稀疏的快速参考指南。
1. 三角形网格编码为一对矩阵:
Eigen::MatrixXd V;
Eigen::MatrixXi F;
2. Libigl 提供输入 [输出] 函数来读取 [写入]许多常见的网格格式:
示例101
包含从OFF到OBJ格式的简单网格转换器
3. 文件格式介绍
3.1 物体文件格式(.off)文件
Banchmark中的.off文件遵循以下标准:
3.2 OBJ文件格式
OBJ文件是Alias|Wavefront公司为它的一套基于工作站的3D建模和动画软件"Advanced Visualizer"开发的一种标准3D模型文件格式,很适合用于3D软件模型之间的互导,也可以通过Maya读写。
OBJ文件基本结构:
g 组名称(Group name)
s 光滑组(Smoothing group)
mg 合并组(Merging group)
o 对象名称(Object name)
OBJ文件示例:
创建一个OBJ文件,内容为一个四边形
Libigl 提供了一个基于 glfw 的 OpenGL 3.2 查看器,用于可视化表面、其属性和其他调试信息。
示例 102:是本教程中将使用的所有示例的基本框架
#include
#include
Eigen::MatrixXd V;
Eigen::MatrixXi F;
int main(int argc, char *argv[])
{
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F);
// Plot the mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
viewer.launch();
}
viewer.core().set_rotation_type(igl::opengl::ViewerCore::ROTATION_TYPE_TRACKBALL);
键盘和鼠标事件触发可在查看器中注册的回调
查看器支持以下回调:
bool (*callback_pre_draw)(Viewer& viewer);
bool (*callback_post_draw)(Viewer& viewer);
bool (*callback_mouse_down)(Viewer& viewer, int button, int modifier);
bool (*callback_mouse_up)(Viewer& viewer, int button, int modifier);
bool (*callback_mouse_move)(Viewer& viewer, int mouse_x, int mouse_y);
bool (*callback_mouse_scroll)(Viewer& viewer, float delta_y);
bool (*callback_key_down)(Viewer& viewer, unsigned char key, int modifiers);
bool (*callback_key_up)(Viewer& viewer, unsigned char key, int modifiers);
键盘回调可用于可视化多个网格或算法的不同阶段
示例 103 所示键盘回调根据按下的键更改可视化网格:
#include
#include
#include
Eigen::MatrixXd V1,V2;
Eigen::MatrixXi F1,F2;
// This function is called every time a keyboard button is pressed
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier)
{
std::cout<<"Key: "<<key<<" "<<(unsigned int)key<<std::endl;
if (key == '1')
{
// Clear should be called before drawing the mesh
viewer.data().clear();
// Draw_mesh creates or updates the vertices and faces of the displayed mesh.
// If a mesh is already displayed, draw_mesh returns an error if the given V and
// F have size different than the current ones
viewer.data().set_mesh(V1, F1);
viewer.core().align_camera_center(V1,F1);
}
else if (key == '2')
{
viewer.data().clear();
viewer.data().set_mesh(V2, F2);
viewer.core().align_camera_center(V2,F2);
}
return false;
}
int main(int argc, char *argv[])
{
// Load two meshes
igl::readOFF(TUTORIAL_SHARED_PATH "/bumpy.off", V1, F1);
igl::readOFF(TUTORIAL_SHARED_PATH "/fertility.off", V2, F2);
std::cout<<R"(
1 Switch to bump mesh
2 Switch to fertility mesh
)";
igl::opengl::glfw::Viewer viewer;
// Register a keyboard callback that allows to switch between
// the two loaded meshes
viewer.callback_key_down = &key_down;
viewer.data().set_mesh(V1, F1);
viewer.launch();
}
viewer.callback_key_down = &key_down;
请注意,在使用set_mesh之前会清除网格:
可以使用插件扩展查看器:
可以使用以下 set_colors 函数将颜色关联到面或顶点:
viewer.data().set_colors(C);
C 是一个 #C x 3 矩阵,每行一种 RGB 颜色
在示例 104 中,网格顶点的颜色根据其笛卡尔坐标进行设置:
#include
#include
Eigen::MatrixXd V;
Eigen::MatrixXi F;
Eigen::MatrixXd C;
int main(int argc, char *argv[])
{
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/screwdriver.off", V, F);
// Plot the mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
// Use the (normalized) vertex positions as colors
C =
(V.rowwise() - V.colwise().minCoeff()).array().rowwise()/
(V.colwise().maxCoeff() - V.colwise().minCoeff()).array();
// Add per-vertex colors
viewer.data().set_colors(C);
// Launch the viewer
viewer.launch();
}
每个顶点标量字段可以使用以下功能直接 set_data 可视化:
viewer.data().set_data(D);
viewer.data().add_points(P,Eigen::RowVector3d(r,g,b));
viewer.data().add_edges(P1,P2,Eigen::RowVector3d(r,g,b));
viewer.data().add_label(p,str);
Eigen::Vector3d m = V.colwise().minCoeff();
Eigen::Vector3d M = V.colwise().maxCoeff();
#include
#include
#include
#include
#include
Eigen::MatrixXd V;
Eigen::MatrixXi F;
int main(int argc, char *argv[])
{
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F);
// Find the bounding box
Eigen::Vector3d m = V.colwise().minCoeff();
Eigen::Vector3d M = V.colwise().maxCoeff();
// Corners of the bounding box
Eigen::MatrixXd V_box(8,3);
V_box <<
m(0), m(1), m(2),
M(0), m(1), m(2),
M(0), M(1), m(2),
m(0), M(1), m(2),
m(0), m(1), M(2),
M(0), m(1), M(2),
M(0), M(1), M(2),
m(0), M(1), M(2);
// Edges of the bounding box
Eigen::MatrixXi E_box(12,2);
E_box <<
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
7 ,3;
// Plot the mesh
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
// Plot the corners of the bounding box as points
viewer.data().add_points(V_box,Eigen::RowVector3d(1,0,0));
// Plot the edges of the bounding box
for (unsigned i=0;i<E_box.rows(); ++i)
viewer.data().add_edges
(
V_box.row(E_box(i,0)),
V_box.row(E_box(i,1)),
Eigen::RowVector3d(1,0,0)
);
// Plot labels with the coordinates of bounding box vertices
std::stringstream l1;
l1 << m(0) << ", " << m(1) << ", " << m(2);
viewer.data().add_label(m+Eigen::Vector3d(-0.007, 0, 0),l1.str());
std::stringstream l2;
l2 << M(0) << ", " << M(1) << ", " << M(2);
viewer.data().add_label(M+Eigen::Vector3d(0.007, 0, 0),l2.str());
// activate label rendering
viewer.data().show_custom_labels = true;
// Rendering of text labels is handled by ImGui, so we need to enable the ImGui
// plugin to show text labels.
igl::opengl::glfw::imgui::ImGuiPlugin plugin;
viewer.plugins.push_back(&plugin);
igl::opengl::glfw::imgui::ImGuiMenu menu;
plugin.widgets.push_back(&menu);
menu.callback_draw_viewer_window = [](){};
// Launch the viewer
viewer.launch();
}
从最新版本开始,查看器使用新菜单,并将AntTweakBar和nanogui 完全替换为Dear ImGui
// Add content to the default menu window
menu.callback_draw_viewer_menu = [&]()
{
// Draw parent menu content
menu.draw_viewer_menu();
// Add new group
if (ImGui::CollapsingHeader("New Group", ImGuiTreeNodeFlags_DefaultOpen))
{
// Expose variable directly ...
ImGui::InputFloat("float", &floatVariable, 0, 0, 3);
// ... or using a custom callback
static bool boolVariable = true;
if (ImGui::Checkbox("bool", &boolVariable))
{
// do something
std::cout << "boolVariable: " << std::boolalpha << boolVariable << std::endl;
}
// Expose an enumeration type
enum Orientation { Up=0, Down, Left, Right };
static Orientation dir = Up;
ImGui::Combo("Direction", (int *)(&dir), "Up\0Down\0Left\0Right\0\0");
// We can also use a std::vector defined dynamically
static int num_choices = 3;
static std::vector<std::string> choices;
static int idx_choice = 0;
if (ImGui::InputInt("Num letters", &num_choices))
{
num_choices = std::max(1, std::min(26, num_choices));
}
if (num_choices != (int) choices.size())
{
choices.resize(num_choices);
for (int i = 0; i < num_choices; ++i)
choices[i] = std::string(1, 'A' + i);
if (idx_choice >= num_choices)
idx_choice = num_choices - 1;
}
ImGui::Combo("Letter", &idx_choice, choices);
// Add a button
if (ImGui::Button("Print Hello", ImVec2(-1,0)))
{
std::cout << "Hello\n";
}
}
};
// Draw additional windows
menu.callback_draw_custom_window = [&]()
{
// Define next window position + size
ImGui::SetNextWindowPos(ImVec2(180.f * menu.menu_scaling(), 10), ImGuiSetCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiSetCond_FirstUseEver);
ImGui::Begin(
"New Window", nullptr,
ImGuiWindowFlags_NoSavedSettings
);
// Expose the same variable directly ...
ImGui::PushItemWidth(-80);
ImGui::DragFloat("float", &floatVariable, 0.0, 0.0, 3.0);
ImGui::PopItemWidth();
static std::string str = "bunny";
ImGui::InputText("Name", str);
ImGui::End();
};
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
Eigen::MatrixXd V;
Eigen::MatrixXi F;
// Load a mesh in OFF format
igl::readOFF(TUTORIAL_SHARED_PATH "/bunny.off", V, F);
// Init the viewer
igl::opengl::glfw::Viewer viewer;
// Attach a menu plugin
igl::opengl::glfw::imgui::ImGuiPlugin plugin;
viewer.plugins.push_back(&plugin);
igl::opengl::glfw::imgui::ImGuiMenu menu;
plugin.widgets.push_back(&menu);
// Customize the menu
double doubleVariable = 0.1f; // Shared between two menus
// Add content to the default menu window
menu.callback_draw_viewer_menu = [&]()
{
// Draw parent menu content
menu.draw_viewer_menu();
// Add new group
if (ImGui::CollapsingHeader("New Group", ImGuiTreeNodeFlags_DefaultOpen))
{
// Expose variable directly ...
ImGui::InputDouble("double", &doubleVariable, 0, 0, "%.4f");
// ... or using a custom callback
static bool boolVariable = true;
if (ImGui::Checkbox("bool", &boolVariable))
{
// do something
std::cout << "boolVariable: " << std::boolalpha << boolVariable << std::endl;
}
// Expose an enumeration type
enum Orientation { Up=0, Down, Left, Right };
static Orientation dir = Up;
ImGui::Combo("Direction", (int *)(&dir), "Up\0Down\0Left\0Right\0\0");
// We can also use a std::vector defined dynamically
static int num_choices = 3;
static std::vector<std::string> choices;
static int idx_choice = 0;
if (ImGui::InputInt("Num letters", &num_choices))
{
num_choices = std::max(1, std::min(26, num_choices));
}
if (num_choices != (int) choices.size())
{
choices.resize(num_choices);
for (int i = 0; i < num_choices; ++i)
choices[i] = std::string(1, 'A' + i);
if (idx_choice >= num_choices)
idx_choice = num_choices - 1;
}
ImGui::Combo("Letter", &idx_choice, choices);
// Add a button
if (ImGui::Button("Print Hello", ImVec2(-1,0)))
{
std::cout << "Hello\n";
}
}
};
// Draw additional windows
menu.callback_draw_custom_window = [&]()
{
// Define next window position + size
ImGui::SetNextWindowPos(ImVec2(180.f * menu.menu_scaling(), 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(200, 160), ImGuiCond_FirstUseEver);
ImGui::Begin(
"New Window", nullptr,
ImGuiWindowFlags_NoSavedSettings
);
// Expose the same variable directly ...
ImGui::PushItemWidth(-80);
ImGui::DragScalar("double", ImGuiDataType_Double, &doubleVariable, 0.1, 0, 0, "%.4f");
ImGui::PopItemWidth();
static std::string str = "bunny";
ImGui::InputText("Name", str);
ImGui::End();
};
// Plot the mesh
viewer.data().set_mesh(V, F);
viewer.data().add_label(viewer.data().V.row(0) + viewer.data().V_normals.row(0).normalized()*0.005, "Hello World!");
viewer.launch();
}
Libigl为渲染多个网格 igl::opengl::glfw::Viewer 提供了基本支持
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
igl::opengl::glfw::Viewer viewer;
const auto names =
{"cube.obj","sphere.obj","xcylinder.obj","ycylinder.obj","zcylinder.obj"};
std::map<int, Eigen::RowVector3d> colors;
int last_selected = -1;
for(const auto & name : names)
{
viewer.load_mesh_from_file(std::string(TUTORIAL_SHARED_PATH) + "/" + name);
colors.emplace(viewer.data().id, 0.5*Eigen::RowVector3d::Random().array() + 0.5);
}
viewer.callback_key_down =
[&](igl::opengl::glfw::Viewer &, unsigned int key, int mod)
{
if(key == GLFW_KEY_BACKSPACE)
{
int old_id = viewer.data().id;
if (viewer.erase_mesh(viewer.selected_data_index))
{
colors.erase(old_id);
last_selected = -1;
}
return true;
}
return false;
};
// Refresh selected mesh colors
viewer.callback_pre_draw =
[&](igl::opengl::glfw::Viewer &)
{
if (last_selected != viewer.selected_data_index)
{
for (auto &data : viewer.data_list)
{
data.set_colors(colors[data.id]);
}
viewer.data_list[viewer.selected_data_index].set_colors(Eigen::RowVector3d(0.9,0.1,0.1));
last_selected = viewer.selected_data_index;
}
return false;
};
viewer.launch();
return EXIT_SUCCESS;
}
例107) igl::opengl::glfw::Viewer 可以渲染多个网格,每个网格都有自己的属性,如颜色。
Libigl 为 igl::opengl::glfw::Viewer 渲染具有多个视图的网格提供了基本支持
可以使用该方法 Viewer::append_core() 将新的视图核心添加到查看器中
viewer.callback_post_resize = [&](igl::opengl::glfw::Viewer &v, int w, int h) {
v.core( left_view).viewport = Eigen::Vector4f(0, 0, w / 2, h);
v.core(right_view).viewport = Eigen::Vector4f(w / 2, 0, w - (w / 2), h);
return true;
};
viewer.data(mesh_id).set_visible(false, view_id);
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
igl::opengl::glfw::Viewer viewer;
viewer.load_mesh_from_file(std::string(TUTORIAL_SHARED_PATH) + "/cube.obj");
viewer.load_mesh_from_file(std::string(TUTORIAL_SHARED_PATH) + "/sphere.obj");
unsigned int left_view, right_view;
int cube_id = viewer.data_list[0].id;
int sphere_id = viewer.data_list[1].id;
viewer.callback_init = [&](igl::opengl::glfw::Viewer &)
{
viewer.core().viewport = Eigen::Vector4f(0, 0, 640, 800);
left_view = viewer.core_list[0].id;
right_view = viewer.append_core(Eigen::Vector4f(640, 0, 640, 800));
return false;
};
viewer.callback_key_down = [&](igl::opengl::glfw::Viewer &, unsigned int key, int mod)
{
if(key == GLFW_KEY_SPACE)
{
// By default, when a core is appended, all loaded meshes will be displayed in that core.
// Displaying can be controlled by calling viewer.data().set_visible().
viewer.data(cube_id).set_visible(false, left_view);
viewer.data(sphere_id).set_visible(false, right_view);
}
return false;
};
viewer.callback_post_resize = [&](igl::opengl::glfw::Viewer &v, int w, int h) {
v.core( left_view).viewport = Eigen::Vector4f(0, 0, w / 2, h);
v.core(right_view).viewport = Eigen::Vector4f(w / 2, 0, w - (w / 2), h);
return true;
};
viewer.launch();
return EXIT_SUCCESS;
}
例108)可以使用 igl::opengl::glfw::Viewer 多个视图渲染同一场景,每个视图都有自己的属性,如颜色和单独的网格可见性
#include
// ImGuizmoPlugin replaces the ImGuiMenu plugin entirely
igl::opengl::glfw::imgui::ImGuizmoPlugin plugin;
vr.plugins.push_back(&plugin);
// Initialize ImGuizmo at mesh centroid
plugin.T.block(0,3,3,1) =
0.5*(V.colwise().maxCoeff() + V.colwise().minCoeff()).transpose().cast<float>();
// Update can be applied relative to this remembered initial transform
const Eigen::Matrix4f T0 = plugin.T;
// Attach callback to apply imguizmo's transform to mesh
plugin.callback = [&](const Eigen::Matrix4f & T)
{
const Eigen::Matrix4d TT = (T*T0.inverse()).cast<double>().transpose();
vr.data().set_vertices(
(V.rowwise().homogeneous()*TT).rowwise().hnormalized());
vr.data().compute_normals();
};
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
// Load a mesh from file
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(argc>1?argv[1]: TUTORIAL_SHARED_PATH "/cow.off",V,F);
// Set up viewer
igl::opengl::glfw::Viewer vr;
vr.data().set_mesh(V,F);
igl::opengl::glfw::imgui::ImGuiPlugin imgui_plugin;
vr.plugins.push_back(&imgui_plugin);
// Add a 3D gizmo plugin
igl::opengl::glfw::imgui::ImGuizmoWidget gizmo;
imgui_plugin.widgets.push_back(&gizmo);
// Initialize ImGuizmo at mesh centroid
gizmo.T.block(0,3,3,1) =
0.5*(V.colwise().maxCoeff() + V.colwise().minCoeff()).transpose().cast<float>();
// Update can be applied relative to this remembered initial transform
const Eigen::Matrix4f T0 = gizmo.T;
// Attach callback to apply imguizmo's transform to mesh
gizmo.callback = [&](const Eigen::Matrix4f & T)
{
const Eigen::Matrix4d TT = (T*T0.inverse()).cast<double>().transpose();
vr.data().set_vertices(
(V.rowwise().homogeneous()*TT).rowwise().hnormalized());
vr.data().compute_normals();
};
// Maya-style keyboard shortcuts for operation
vr.callback_key_pressed = [&](decltype(vr) &,unsigned int key, int mod)
{
switch(key)
{
case ' ': gizmo.visible = !gizmo.visible; return true;
case 'W': case 'w': gizmo.operation = ImGuizmo::TRANSLATE; return true;
case 'E': case 'e': gizmo.operation = ImGuizmo::ROTATE; return true;
case 'R': case 'r': gizmo.operation = ImGuizmo::SCALE; return true;
}
return false;
};
igl::opengl::glfw::imgui::ImGuiMenu menu;
imgui_plugin.widgets.push_back(&menu);
std::cout<<R"(
W,w Switch to translate operation
E,e Switch to rotate operation
R,r Switch to scale operation
)";
vr.launch();
}
([示例 109](https://github.com/libigl/libigl/tree/main/tutorial/109_ImGuizmo/main.cpp))Libigl Viewer与ImGuizmo集成以提供转换小部件
Libigl 可以读取以 Gmsh.msh 版本 2 文件格式存储的混合网格
Eigen::MatrixXd X; // Vertex coorinates (Xx3)
Eigen::MatrixXi Tri; // Triangular elements (Yx3)
Eigen::MatrixXi Tet; // Tetrahedral elements (Zx4)
Eigen::VectorXi TriTag; // Integer tags defining triangular submeshes
Eigen::VectorXi TetTag; // Integer tags defining tetrahedral submeshes
std::vector<std::string> XFields; // headers (names) of fields defined on vertex level
std::vector<std::string> EFields; // headers (names) of fields defined on element level
std::vector<Eigen::MatrixXd> XF; // fields defined on vertex
std::vector<Eigen::MatrixXd> TriF; // fields defined on triangular elements
std::vector<Eigen::MatrixXd> TetF; // fields defined on tetrahedral elements
// loading mixed mesh from Gmsh file
igl::readMSH("hand.msh", X, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF);
#include
#include
#include
#include
#include
Eigen::MatrixXd X,B;
Eigen::MatrixXi Tri;
Eigen::MatrixXi Tet;
Eigen::VectorXi TriTag;
Eigen::VectorXi TetTag;
Eigen::VectorXd D;
std::vector<std::string> XFields;
std::vector<std::string> EFields;
std::vector<Eigen::MatrixXd> XF;
std::vector<Eigen::MatrixXd> TriF;
std::vector<Eigen::MatrixXd> TetF;
// 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 && v(i)>(t-0.1)) // select a thick slab
s.push_back(i);
MatrixXd V_temp(s.size()*4,3);
MatrixXi F_temp(s.size()*4,3);
VectorXd D_temp(s.size()*4);
for (unsigned i=0; i<s.size();++i)
{
V_temp.row(i*4+0) = X.row(Tet(s[i],0));
V_temp.row(i*4+1) = X.row(Tet(s[i],1));
V_temp.row(i*4+2) = X.row(Tet(s[i],2));
V_temp.row(i*4+3) = X.row(Tet(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;
D_temp(i*4+0) = D(s[i]);
D_temp(i*4+1) = D(s[i]);
D_temp(i*4+2) = D(s[i]);
D_temp(i*4+3) = D(s[i]);
}
viewer.data().clear();
viewer.data().set_mesh(V_temp, F_temp);
Eigen::MatrixXd C;
igl::colormap(igl::COLOR_MAP_TYPE_VIRIDIS, D_temp, true, C);
viewer.data().set_face_based(true);
viewer.data().set_colors(C);
}
return false;
}
int main(int argc, char *argv[])
{
using namespace Eigen;
using namespace std;
igl::readMSH(argc > 1 ? argv[1] : TUTORIAL_SHARED_PATH "/hand.msh", X, Tri, Tet, TriTag, TetTag, XFields, XF, EFields, TriF, TetF);
for(auto i:EFields)
std::cout<<i<<"\t";
std::cout<<std::endl;
// search for a predefined field name "E"
for(int i=0;i<EFields.size();++i)
{
if(EFields[i]=="E")
D = TetF[i].rowwise().norm(); // take a row-wise norm
}
std::cout<<"D:"<<D.rows()<<"x"<<D.cols()<<std::endl;
// generate fake data
if(D.rows()==0)
D = TetTag.cast<double>();
// Compute barycenters
igl::barycenter(X, Tet, B);
// Plot the generated mesh
igl::opengl::glfw::Viewer viewer;
viewer.callback_key_down = &key_down;
key_down(viewer,'5',0);
viewer.launch();
}
MatCaps(材质捕获),也称为环境贴图,是一种简单的基于图像的渲染技术,无需复杂的着色器程序即可实现复杂的照明
使用离线渲染甚至绘画程序,可以创建渲染单位球体的图像,例如在工作室照明下查看的带有玉石材质的球体图像:
-
渲染非球形形状时:
#include
#include
#include
int main(int argc, char *argv[])
{
igl::opengl::glfw::Viewer v;
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/armadillo.obj",V,F);
Eigen::Matrix<unsigned char,Eigen::Dynamic,Eigen::Dynamic> R,G,B,A;
igl::png::readPNG(argc>2?argv[2]: TUTORIAL_SHARED_PATH "/jade.png",R,G,B,A);
v.data().set_mesh(V,F);
v.data().set_texture(R,G,B,A);
v.data().use_matcap = true;
v.data().show_lines = false;
v.launch();
}
viewer.data().set_texture(R,G,B,A);
viewer.data().use_matcap = true;
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
// Inline mesh of a cube
Eigen::MatrixXd V;
Eigen::MatrixXi F;
igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/armadillo.obj",V,F);
// Plot the mesh
igl::opengl::glfw::Viewer vr;
igl::opengl::glfw::imgui::ImGuiPlugin imgui_plugin;
vr.plugins.push_back(&imgui_plugin);
igl::opengl::glfw::imgui::SelectionWidget widget;
imgui_plugin.widgets.push_back(&widget);
Eigen::VectorXd W = Eigen::VectorXd::Zero(V.rows());
Eigen::Array<double,Eigen::Dynamic,1> and_visible =
Eigen::Array<double,Eigen::Dynamic,1>::Zero(V.rows());
const Eigen::MatrixXd CM = (Eigen::MatrixXd(2,3)<<
0.3,0.3,0.5,
255.0/255.0,228.0/255.0,58.0/255.0).finished();
bool only_visible = false;
const auto update = [&]()
{
const bool was_face_based = vr.data().face_based;
Eigen::VectorXd S = W;
if(only_visible) { S.array() *= and_visible; }
vr.data().set_data(S,0,1,igl::COLOR_MAP_TYPE_PLASMA,2);
vr.data().face_based = was_face_based;
vr.data().set_colormap(CM);
};
igl::AABB<Eigen::MatrixXd, 3> tree;
tree.init(V,F);
widget.callback = [&]()
{
screen_space_selection(V,F,tree,vr.core().view,vr.core().proj,vr.core().viewport,widget.L,W,and_visible);
update();
};
vr.callback_key_pressed = [&](decltype(vr) &,unsigned int key, int mod)
{
switch(key)
{
case ' ': only_visible = !only_visible; update(); return true;
case 'D': case 'd': W.setZero(); update(); return true;
}
return false;
};
std::cout<<R"(
Usage:
[space] Toggle whether to take visibility into account
D,d Clear selection
)";
vr.data().set_mesh(V,F);
vr.data().set_face_based(true);
vr.core().background_color.head(3) = CM.row(0).head(3).cast<float>();
vr.data().line_color.head(3) = (CM.row(0).head(3)*0.5).cast<float>();
vr.data().show_lines = F.rows() < 20000;
update();
vr.launch();
}