第 2 章:离散几何量和运算符
本章说明了 libigl 可以在网格上计算的一些离散量以及构建流行的离散微分几何算子的 libigl 函数。它还介绍了观众的基本绘画和着色例程。
一、Normals法线
表面法线是渲染表面所需的基本量。有多种方法可以计算和存储三角形网格上的法线。示例 201 演示了如何使用 libigl 计算和可视化法线。
每面 ¶
网格的每个三角形上的法线都被明确定义为与三角形平面正交的向量。这些分段常数法线产生分段平坦渲染:表面看起来不光滑并揭示了其底层的离散化。
每个顶点 ¶
可以在顶点上计算和存储法线,并在三角形内部进行插值以产生平滑的渲染(Phong 着色)。大多数计算每个顶点法线的技术都会取入射面法线的平均值。这些技术之间的主要区别在于它们的加权方案:均匀加权因离散化选择而严重偏差,而基于区域或基于角度的加权则更为宽容。
每个角 ¶
存储每个角的法线是支持平滑和锐利(例如折痕和角)渲染的有效且方便的方法。此格式是 OpenGL 和 .obj 网格文件格式所共有的。通常,此类法线由网格设计者调整,但折痕和角也可以自动计算。Libigl 实现了一个简单的方案,它将角法线计算为入射在相应顶点上的面法线的平均值,该顶点的偏差不超过指定的二面角(例如 20°)。
Normals
示例计算每个面(左)、每个顶点(中)和每个角(右)的法线
这是一段C++代码,使用了libigl图形库和Eigen数学库来处理和显示三维网格。以下是其主要功能:
代码包含了libigl库中读取三角网格、处理OpenGL视窗、计算多种法线的头文件,并引入了iostream库用于命令行输出信息。
定义全局变量
V
(顶点位置矩阵)、F
(面索引矩阵)、N_vertices
(顶点法线)、N_faces
(面法线)、N_corners
(角点法线)来存储网格数据及其计算结果。定义函数
key_down
,它是一个键盘事件响应函数,用来根据用户输入的按键改变网格的法线显示方式。按'1'显示面法线,按'2'显示顶点法线,按'3'显示角点法线。在
main
主函数中,使用函数igl::read_triangle_mesh
读取指定路径或通过命令行参数提供的OFF格式的网格文件到V
和F
变量中。使用函数
igl::per_face_normals
、igl::per_vertex_normals
和igl::per_corner_normals
来分别计算和存储面法线、顶点法线和基于角点法线的阈值的法线(角点法线通过一个限定的二面角进行计算,如代码中的20度)。初始化
igl::opengl::glfw::Viewer
对象,配置键盘回调函数,设置网格和默认的法线(这里设为面法线)。在控制台中输出用户提示信息,告知用户可以按'1'、'2'或者'3'来切换不同类型的法线可视化。
启动视窗,进行渲染循环,显示三维网格模型。用户在视窗中交互时,按下相应按键会切换法线的显示方式。
整体而言,此代码是一个交云的示例,展示了如何加载、处理和渲染三维网格,以及如何通过按键输入来在不同法线可视化模式间切换。
#include <igl/read_triangle_mesh.h> // 包含用于读取三角形网格的功能
#include <igl/opengl/glfw/Viewer.h> // 包含用于窗口查看者的功能
#include <igl/per_vertex_normals.h> // 包含用于计算每个顶点法线的功能
#include <igl/per_face_normals.h> // 包含用于计算每个面法线的功能
#include <igl/per_corner_normals.h> // 包含用于计算每个角法线的功能
#include <iostream> // 包含标准输入输出流的功能
// 定义矩阵存储顶点,面和不同类型的法线
Eigen::MatrixXd V; // 顶点矩阵,V中的每一行代表一个顶点的x,y,z坐标
Eigen::MatrixXi F; // 面矩阵,F中的每一行代表一个面,是三个顶点索引的集合
Eigen::MatrixXd N_vertices; // 存储每个顶点法线的矩阵
Eigen::MatrixXd N_faces; // 存储每个面法线的矩阵
Eigen::MatrixXd N_corners; // 存储每个角法线的矩阵
// 当按下键盘按钮时调用此函数
bool key_down(igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier) {
switch(key) {
case '1': // 如果按下'1'
viewer.data().set_normals(N_faces); // 设置为每个面的法线显示
return true;
case '2': // 如果按下'2'
viewer.data().set_normals(N_vertices); // 设置为每个顶点的法线显示
return true;
case '3': // 如果按下'3'
viewer.data().set_normals(N_corners); // 设置为每个角的法线显示
return true;
default: break;
}
return false;
}
int main(int argc, char *argv[]) {
// 读取OFF格式的网格文件
igl::read_triangle_mesh(
argc>1?argv[1]: TUTORIAL_SHARED_PATH "/fandisk.off",V,F); // 如果命令行参数数量大于1,则读取第二个参数所指的文件,否则读取默认路径下的fandisk.off文件
// 计算每个面的法线
igl::per_face_normals(V,F,N_faces); // V, F作为输入,N_faces作为输出存储了每个面的法线
// 计算每个顶点的法线
igl::per_vertex_normals(V,F,N_vertices); // V, F作为输入,N_vertices作为输出存储了每个顶点的法线
// 计算每个角的法线,|二面角| > 20 度时识别为棱
igl::per_corner_normals(V,F,20,N_corners); // V, F作为输入,20是二面角阈值,N_corners作为输出存储了每个角的法线
// 绘制网格
igl::opengl::glfw::Viewer viewer; // 创建窗口查看者
viewer.callback_key_down = &key_down; // 设置键盘按下事件的回调函数
viewer.data().show_lines = false; // 设置查看者中不显示网格线
viewer.data().set_mesh(V, F); // 在查看者中设置网格为V和F指定的网格
viewer.data().set_normals(N_faces); // 默认显示为每个面的法线
std::cout<<
"Press '1' for per-face normals."<<std::endl<<
"Press '2' for per-vertex normals."<<std::endl<<
"Press '3' for per-corner normals."<<std::endl; // 控制台输出提示信息
viewer.launch(); // 启动查看者窗口
}
二、 GaussianCurvature 高斯曲率
连续表面上的高斯曲率定义为主曲率的乘积:
kG=k1k2.��=�1�2.
作为一种内在的度量,它取决于度量而不是表面的嵌入。
直观上,高斯曲率表明表面的局部球形或椭圆形 ( kG>0��>0 )、表面的局部鞍形或双曲形 ( kG<0��<0 ) 或局部圆柱形或抛物线形 ( kG<0��<0 ) kG=0��=0 )表面是。
在离散设置中,三角形网格上“离散高斯曲率”的一种定义是通过顶点的角度赤字:
kG(vi)=2π−∑j∈N(i)θij,��(��)=2π−∑�∈�(�)θ��,
其中 N(i)�(�) 是入射在顶点 i� 上的三角形, θijθ�� 是三角形 j� 中顶点 i� 处的角度 3 。
就像连续模拟一样,我们的离散高斯曲率揭示了域上的椭圆形、双曲形和抛物线顶点,如示例 202 中所示。
GaussianCurvature
示例计算离散高斯曲率并以伪彩色将其可视化。
这段代码演示了如何使用 libigl 库计算并显示一个三维模型表面的高斯曲率。主要步骤如下:
包含必要的头文件,引入 libigl 功能模块和 Eigen 库。
在
main
函数中读取模型文件(这里假定为 "bumpy.off")到顶点和面的矩阵中。计算高斯曲率向量
K
。创建质量矩阵
M
并取其对角线的逆矩阵Minv
。质量矩阵与模型的面积有关。将高斯曲率除以对应的曲面区域(质量矩阵元素表示区域),以获得积分平均的高斯曲率。
使用 libigl 的 Viewer 类来显示模型,将计算出的高斯曲率映射到模型的顶点上,形成伪色彩效果展示高斯曲率的分布。
这个程序的作用是,帮助用户直观地理解和分析模型表面的几何特性,尤其是高斯曲率的分布情况。高斯曲率是微分几何中描述表面弯曲程度的一个重要量。
// 包含 libigl 库和相关组件的头文件
#include <igl/gaussian_curvature.h>
#include <igl/massmatrix.h>
#include <igl/invert_diag.h>
#include <igl/readOFF.h>
#include <igl/opengl/glfw/Viewer.h>
// 程序主函数入口
int main(int argc, char *argv[])
{
// 使用 Eigen 命名空间
using namespace Eigen;
// 使用标准库命名空间
using namespace std;
// 定义顶点和面的矩阵
MatrixXd V;
MatrixXi F;
// 从 OFF 文件中读取模型顶点和面
igl::readOFF(TUTORIAL_SHARED_PATH "/bumpy.off", V, F);
// 定义高斯曲率向量
VectorXd K;
// 计算高斯曲率积分
igl::gaussian_curvature(V, F, K);
// 计算质量矩阵
SparseMatrix<double> M, Minv;
igl::massmatrix(V, F, igl::MASSMATRIX_TYPE_DEFAULT, M);
// 对质量矩阵对角线元素取逆
igl::invert_diag(M, Minv);
// 通过面积平均,得到平均积分曲率
K = (Minv * K).eval();
// 创建 Viewer 对象,并设置模型的顶点和面
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
// 将高斯曲率数据绑定到模型上,进行颜色映射
viewer.data().set_data(K);
// 启动 Viewer 窗口
viewer.launch();
}
三、CurvatureDirections 曲率方向
表面上一点的两个主曲率 (k1,k2)(�1,�2) 测量表面在不同方向上弯曲的程度。最大和最小(带符号)弯曲的方向称为主方向,并且始终正交。
平均曲率定义为主曲率的平均值:
H=12(k1+k2).�=12(�1+�2).
提取平均曲率的一种方法是检查应用于表面位置的 Laplace-Beltrami 算子。结果是所谓的平均曲率法线:
−Δx=Hn.−Δ�=��.
使用余切 Laplace-Beltrami 算子 3 在 libigl 中的离散三角形网格上很容易计算出这一点。
结合离散高斯曲率的角度缺陷定义,可以定义主曲率并使用最小二乘拟合来找到方向 3 。
或者,确定主曲率的稳健方法是通过二次拟合 5 。在每个顶点周围的邻域中,找到最佳拟合二次曲面,并在该二次曲面上分析计算主曲率值和方向(示例 203)。
CurvatureDirections
示例通过二次拟合计算主曲率,并使用交叉场以伪彩色和主方向可视化平均曲率。
这段代码主要展示了如何使用 libigl 库来计算和可视化三维模型的主曲率。程序执行以下步骤: