CMake 与 VTK:第三方库的管理与集成
CMake 与 VTK:第三方库的管理与集成
VTK是一个开源的C++库(https://github.com/Kitware/VTK),可以用于三维图形渲染和可视化。科研领域常用的开源可视化软件 ParaView 的底层就是 VTK,很多开源 CAE 软件的建模和后处理也是基于 VTK 库进行的开发。假如我们想做一个需要附带三维建模仿真可视化的应用,一个好的选择就是把 VTK 库集成到自己的项目中,利用 VTK 的功能去实现可视化的部分。这篇文章首先整理一下在 C++ 项目中处理第三方库的一些方式,之后整理一下 VTK 库的安装和简单使用。
如何处理第三方库
在自己的 C++ 项目中,总会遇到需要使用第三方库的情况。比如用 GoogleTest
做测试,用 Eigen
做数值计算,或者用 pybind11
把 C++ 类或函数封装出一个 Python 接口。假如我们用 CMake 构建整个项目,不同的第三方库可以有不同的组织和集成方式。比较简单常见的有以下三种处理方式:
include_directories()
适用于纯头文件库(Header-only)。add_subdirectory()
适用于小型 CMake 项目(源码可用)。find_package()
适用于已安装的库(标准 CMake 库)。
小型第三方库
对于一些小型的第三方库,比如:
- Header-only 库:库本身只包含头文件,像
pybind11
、Eigen
、fmt
、json
、Catch2
。 - 小型 CMake 项目:库本身包含源文件,需要编译但体积较小,像
GoogleTest
。
对于这些小型项目,我们可以直接在项目根目录创建 external/
目录,并用 git submodule
添加这些库:
1 | git submodule add https://github.com/eigen/eigen.git external/eigen |
git submodule
会自动、独立地管理这些子项目的代码库,比如我们把整个 Eigen
库放到了 external/
目录里,我们原本的 Git 项目记录中不会突然出现一大堆新增文件变动。当我们把这些项目直接拉到自己的项目里面后,也可以有不同的处理方式,这里有三个具体的示例。
Eigen (Header-only)
Eigen
是一个矩阵运算库,实现了一系列线性代数和矩阵运算相关的算法。Eigen
库只包含头文件,不需要编译,因此可以直接 include_directories()
:
1 | include_directories(${CMAKE_SOURCE_DIR}/external/eigen) |
或者使用现代 CMake:
1 | target_include_directories(my_target PRIVATE ${CMAKE_SOURCE_DIR}/external/eigen) |
在这里 include_directories
是指将对应目录添加到编译器的头文件搜索路径中。CMAKE_SOURCE_DIR
是 CMake 项目的根目录,也就是最顶层的 CMakeLists.txt
所在的目录。$
是用于引用 CMake 变量的符号。target_include_directories
就是只对特定目标才将目录添加到编译器的头文件搜索路径中。这里我之前会有一些关于编译时路径和运行时路径的疑惑,但实际上忘记了 Eigen
只是一个头文件库,而仅有在编译时才需要头文件,编译后的可执行文件不依赖头文件路径。
GoogleTest (需要编译的小型 CMake 项目)
GoogleTest
是一个测试框架,它有源文件需要编译。但它的体积小,编译不会显著影响主项目,而且 CMake 结构良好,可以直接用 add_subdirectory()
:
1 | add_subdirectory(external/googletest) |
add_subdirectory()
用于直接构建子项目,比如对于上面的命令,CMake 会直接进入 external/googletest
目录,执行目录中的 CMakeLists.txt
。子项目中的 CMakeLists.txt
会定义子项目的 CMake 目标,比如 googletest
定义了 gtest
目标。主项目可以直接通过 target_link_libraries(my_target PRIVATE gtest)
使用生成的 gtest
。CMake 会自动处理目标依赖,将 googletest
生成的 gtest
目标正确链接到主项目。
Pybind11 (Header-only + CMake 支持)
Pybind11
是一个用于将 C++ 代码封装 Python 库的库,它也是 header-only,仅由头文件组成。但是不同的是 Pybind11
提供了 CMake 目标,会额外定义一些 CMake 方法,比如通过 pybind11_add_module(my_module my_module.cpp)
可以直接将 C++ 代码自动设置 Python 绑定模块,不需要我们再去手动处理头文件等。因此,虽然它也是头文件库,但是最好使用 add_subdirectory()
或 find_package()
来将 Pybind
集成到我们的项目中。
使用 add_subdirectory()
适用于 Pybind11 作为 Git Submodule 的情况:
1 | git submodule add https://github.com/pybind/pybind11.git external/pybind11 |
在 CMakeLists.txt
中:
1 | add_subdirectory(external/pybind11) |
使用 find_package()
在 CMakeLists.txt
中:
1 | find_package(pybind11 REQUIRED) |
find_package()
主要用于查找已经安装的库,并将它们的头文件路径、库文件路径以及 CMake 目标导入当前 CMake 项目。本质上,它查找的是该库的 CMake 配置文件(通常是 <package>Config.cmake
或 Find<package>.cmake
),并执行这个文件,从而引入库的构建信息。find_package()
的查找路径包括:
显式设置变量提供路径
1
2set(GTest_DIR /path/to/gtest)
find_package(GTest REQUIRED)<package>_DIR
是一个特殊的变量,CMake 会自动把<package>
的部分提取出来用于优先查找对应包的路径。查找
CMAKE_PREFIX_PATH
、CMAKE_FRAMEWORK_PATH
、CMAKE_APPBUNDLE_PATH
、PATH
找到根目录后,CMake 会检查这些目录下的
1
2
3<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)cmake找到这些目录后,会开始依次找
<package>Config.cmake
或Find<package>.cmake
文件。找到后即可执行该文件并生成相关链接信息。
在找到了对应库的配置文件之后,CMake 通常会按照这些文件的指示生成一些变量或者 XXX::YYY
形式的 CMake 目标,比如常见的可能有
1 | <Package>_INCLUDE_DIRS # 头文件路径 |
XXX::YYY
这样形式的 CMake 目标是现代 CMake 的做法,现代 CMake 一般不推荐再使用传统的手动设置库文件和链接文件了。在<package>Config.cmake
中,可能会这样定义一个 CMake 目标:
1 | add_library(Boost::system UNKNOWN IMPORTED) |
在这里,CMake 会自动管理 Boost::system
的头文件路径和库文件路径,不需要手动 include_directories()
或 target_include_directories()
。假如我们自己的目标依赖于库的话,直接使用
1 | find_package(Boost REQUIRED COMPONENTS system filesystem) |
构建即可,CMake 会自动添加 INTERFACE_INCLUDE_DIRECTORIES
(不需要 include_directories()
);同时自动添加 IMPORTED_LOCATION
作为链接库(不需要 target_link_libraries(my_project ${Boost_LIBRARIES})
);此外,CMake 还可能自动处理 Boost 可能的额外依赖(如 boost_system
可能依赖 pthread
,CMake 会自动处理)。对于一些像 VTK 这样更复杂的库,手动管理 include_directories()
和 link_libraries()
可能会遗漏一些关键组件,但是使用 VTK::XXX
目标这样的方式则非常简单:
1 | find_package(VTK REQUIRED) |
当然,如果库没有提供这些目标,就需要手动设置头文件目录和需要链接的库文件了。
什么叫安装(install)一个库
在 CMake 中,安装(install()
)一个库指的是:
- 将编译好的库文件(.so / .a / .lib / .dll)复制到指定的安装路径(如
/usr/local/lib/
等)。 - 将头文件复制到安装路径(如
/usr/local/include/
)。 - 生成
packageConfig.cmake
等 CMake 配置文件,供find_package()
查找。 - 清理
build/
目录的临时文件,使项目结构更清晰。
而 build()
则只是编译,目录中可能包含一系列的中间文件,而且生成的目录结构可能并不是规范的。如果不执行 install()
操作,库文件会留在 build/
目录里,无法被其他项目使用,find_package()
也无法找到编译好的库。
在 CMake 中,可以在项目根目录运行如下命令来执行 build()
操作:
1 | mkdir build |
在上面的命令里,新生成了 build 目录。选择 Ninja
作为构建工具,将生成的 Ninja
构建规则文件放置到 build 目录中。最后,执行 Ninja
构建,并使用8线程并行编译,将编译好的文件放置到 build 目录中。在 build()
后,则可以执行安装操作,复制必要的文件到指定路径:
1 | cmake --install build --prefix /usr/local |
大型第三方库
小型第三方库我们还可以直接用 git submodule
把项目直接拉取到 external/
目录中直接构建,对于有着复杂依赖、编译时间非常长的库,这样做是不现实的,比如 VTK、OpenCV 、Qt 。这种大型项目只能自己先进行安装,然后在 CMakeLists.txt
中用 find_package()
进行查找:
1 | find_package(VTK REQUIRED) |
当然,对于大部分流行的大型库,官方都会提供预编译库,不需要我们手动构建。或者像 Qt 这样的,官方直接提供了一个安装包,我们直接用安装包安装就可以了。预编译的意思就是官方已经直接把源码编译成了可以直接使用的二进制文件,比如.so
、.dll
、.a
、.lib
等,只需要安装就可以直接使用了。预编译的库通常使用包管理器进行安装,比如在 Linux 系统中可以使用 apt
或者 yum
,在 macOS 系统中可以使用 brew
。一行命令搞定,我们也不需要定制什么编译参数了,也能减少出错的概率。
小结
方式 | 适用库 | 方式 | 说明 |
---|---|---|---|
include_directories() |
Header-only(Eigen, fmt, json) | include_directories(external/eigen) |
仅头文件,简单高效 |
add_subdirectory() |
小型 CMake 库(GoogleTest, Pybind11) | add_subdirectory(external/googletest) |
需要编译,但体积小 |
find_package() |
大型库(VTK, OpenCV, Boost) | find_package(VTK REQUIRED) |
已安装库,避免编译 |
VTK的安装和使用
VTK库的安装
vcpkg 安装
vcpkg 是 Microsoft 和 C++ 社区维护的开源 跨平台 C/C++ 包管理器,能够自动下载、编译和管理 C++ 库,vcpkg 的安装和配置非常方便:
1 | git clone https://github.com/microsoft/vcpkg |
运行 bootstrap-vcpkg.bat
即可生成 vcpkg.exe
可执行文件。同时这个脚本还可以用于更新 vcpkg ,让 vcpkg 重新构建自身,以获得最新版本。执行:
1 | vcpkg.exe install vtk --triplet x64-windows |
vcpkg 就会自动下载和编译 VTK 及其依赖项(如 hdf5
, glew
, jsoncpp
),--triplet
指定了系统架构。vcpkg 安装库按照以下流程:
- 在
packages/
目录创建一个临时的vtk_x64-windows/
目录。 - 在
packages/vtk_x64-windows/
里下载、解压、构建 VTK。 - 构建完成后,把最终的
.lib/.dll/.h
复制到installed/x64-windows/
。
在用 vcpkg 安装完成 VTK 之后,CMake 自己项目的时候可以使用如下的指令:
1 | mkdir build |
在 CMake 中,命令行中的 -D
选项代表 Define,用于定义 CMake 变量,相当于在 CMakeLists.txt
中使用 set()
设定变量。这里的 -DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake
指定了 Vcpkg 的 CMake 工具链文件。CMake 工具链文件可以自动配置 find_package()
,使 CMake 能找到相应安装的库。Vcpkg 提供了这个工具链文件,让 CMake 可以自动找到 Vcpkg 里的库。
源代码编译
也可以直接通过源码编译 VTK :
1 | git clone https://github.com/Kitware/VTK.git |
CMAKE_BUILD_TYPE
用于指定编译版本,Debug 是调试版本,不做任何优化方便调试。Release 是发布版本,编译时对程序进行优化,降低代码大小提高执行速度。Debug 和 Release 没有本质的区别,只是不同编译选项的配置;CMAKE_INSTALL_PREFIX
用于设置安装目录。在这里只设置了很少的选项,实际上 VTK 的构建有着大量的选项,比如 Python 的支持、Qt 的支持等等,可以根据需要查询手册。VTK 库在 External
目录里放置了它所需的第三方库的源码,VTK_USE_EXTERNAL=OFF
是指让 VTK 用这些文件从头开始构建自己所需要的所有第三方库,确保它们与 VTK 配合使用,不依赖系统安装的版本,这样我们就不需要手动安装第三方库了。
在手动构建安装完成 VTK 之后,CMake 自己项目的时候可以使用如下的指令:
1 | mkdir build |
这里把 VTK 的安装目录放到 CMAKE_PREFIX_PATH
中,方便 find_package()
可以找到 VTK 和它所依赖的包。注意只设置 -DVTK_DIR=/VTK/install
这样的方式构建会出现找不到第三方库的错误。这是因为 -DVTK_DIR
只告诉了 CMake 如何查找 VTK 的安装路径,无法找到 VTK 安装时同时安装的第三方库的 <Package>Config.cmake
文件。
可执行文件与 VTK 动态库
在默认配置下,VTK 会编译为动态库(.dll
/ .so
/ .dylib
),但如果运行时找不到 VTK 的动态库,程序将无法启动。这是因为 可执行文件不会将动态库路径硬编码到程序中,而是依赖系统的动态库加载机制在运行时找到并加载它们。如果 VTK 的动态库不在系统的默认查找路径中,加载将失败,导致程序无法运行。
当一个可执行文件运行时,它会尝试找到并加载所有依赖的动态库(例如 VTK 的 .dll
/ .so
/ .dylib
),但系统的默认查找路径通常不会自动包含 VTK 的安装目录。操作系统主要从以下位置查找动态库:
操作系统 | 默认动态库查找路径 |
---|---|
Windows | 1️⃣ 可执行文件所在目录 2️⃣ PATH 环境变量 |
Linux | 1️⃣ 可执行文件所在目录 2️⃣ /lib 或 /usr/lib 3️⃣ LD_LIBRARY_PATH 环境变量 |
macOS | 1️⃣ 可执行文件所在目录 2️⃣ /usr/lib 或 /usr/local/lib 3️⃣ DYLD_LIBRARY_PATH 环境变量 |
如果 VTK 的动态库不在这些默认路径中,程序就会报错 "找不到动态库"。最简单、最可靠的方法是直接把所有 VTK 相关的动态库复制到可执行文件所在的目录,这样系统会优先查找可执行文件所在目录,保证程序能正常运行。或者把安装好的 VTK 的 bin
目录放到环境变量里,也可以让自己的程序正常运行。在使用 vcpkg 安装的 VTK 时,vcpkg 会自动在 CMake 构建时调用 applocal.ps1
脚本,这个脚本会自动查找所用到的库的 DLL 并将它们复制到构建目录,确保可执行文件在运行时可以找到这些动态库,不需要我们手动处理了。
这里再额外补充一下,对于一个依赖动态库的程序,对于 Linux/macOS 系统,编译时需要这个动态库的 .h + .so
文件,运行时需要 .so
文件;对于 Windows MSVC,运行时需要这个动态库的 .h + .lib
文件,运行时需要 .dll
文件。这里的 .lib
不是静态库,而是这个动态库的导入库,包含一个符号表,用来告诉编译器的 .dll
里有哪些函数。对于 MinGW 编译时则不需要 .lib
,只需要 .dll
文件即可。
VTK库的使用
VTK 模块
VTK 是一个非常庞大且复杂的库,几乎涵盖了所有 3D 渲染、科学计算可视化、医学影像处理、并行计算、甚至物理仿真等领域。VTK 采用了模块化的设计,将不同的功能分散到了不同的模块中,使用时只需要加载需要的模块就可以了。大体上,VTK 包含以下几个常见的模块:
数据处理相关模块(VTK Filters 系列,负责数据过滤和转换)
这些模块负责处理数据,包括数据过滤、几何变换、平滑、插值等。
- FiltersCore:数据转换的核心,例如
vtkTransformFilter
(变换)、vtkThreshold
(阈值过滤)。 - FiltersGeneral:通用数据处理,比如
vtkWarpScalar
(根据标量变形网格)。 - FiltersGeometry:处理几何数据,如
vtkFeatureEdges
(提取边界)。 - FiltersModeling:建模处理,如
vtkDelaunay2D
(三角网格化)。 - FiltersSources:几何数据源。
- FiltersTexture:纹理处理,如
vtkTextureMapToPlane
(将纹理映射到平面)。
渲染和可视化相关模块(VTK Rendering 系列,负责渲染)
这些模块负责 3D 渲染,包括光照、阴影、材质、体渲染等。
- RenderingCore:渲染核心,如
vtkActor
、vtkRenderer
。 - RenderingOpenGL2:基于 OpenGL 2.0 的渲染。
- RenderingVolume:体渲染,如
vtkVolume
(体数据渲染)。 - RenderingAnnotation:标注和注释,如
vtkCornerAnnotation
(显示角落信息)。 - RenderingLabel:处理文本标签,如
vtkLabeledDataMapper
(数据标签映射)。 - RenderingLOD:细节层次(Level of Detail,LOD)管理,加速大数据渲染。
- RenderingRayTracing:光线追踪,如
vtkOSPRayRenderer
(支持 OSPRay 渲染)。 - RenderingGL2PS:支持矢量输出,如 PDF 或 EPS。
交互和 UI 相关模块(VTK Interaction 系列,用户交互)
这些模块控制交互方式,如鼠标、键盘、触摸输入等。
- InteractionStyle:支持鼠标旋转、缩放、平移。
- InteractionWidgets:提供交互 UI 控件,如
vtkSliderWidget
(滑块)。 - InteractionImage:专门用于图像交互,如
vtkImageViewer2
(交互式图像查看器)。 - GUISupportQt / GUISupportMFC:分别支持 Qt 和 MFC 界面集成。
计算与数学相关模块(VTK Common 系列,数学和数据结构)
提供 VTK 的基本计算功能,如矩阵运算、数学工具等。
- CommonCore:VTK 的核心数据结构、内存管理等。
- CommonDataModel:数据模型,如
vtkPolyData
(几何数据)、vtkImageData
(图像数据)。 - CommonMath:数学计算模块,如
vtkMatrix4x4
(4×4 矩阵运算)。 - CommonTransforms:变换模块,如
vtkTransform
(仿射变换)。
体数据与医学影像(VTK Imaging 系列,图像处理)
这些模块处理 2D/3D 图像数据,可用于医学影像(DICOM)、卫星图像等。
- ImagingCore:基本图像处理,如
vtkImageData
。 - ImagingHybrid:高级图像处理,如
vtkImageReslice
(图像重采样)。 - ImagingGeneral:常见图像滤波,如
vtkImageThreshold
(图像二值化)。 - ImagingFourier:傅立叶变换,如
vtkImageFFT
(快速傅立叶变换)。 - ImagingStatistics:统计计算,如
vtkImageAccumulate
(计算直方图)。 - IOImage:支持医学图像(DICOM、JPEG、TIFF等)。
I/O 数据读取与写入(VTK IO 系列,数据输入输出)
VTK 支持多种文件格式,如 STL、PLY、VTK、DICOM、CSV、HDF5 等。
- IOCore:基础 I/O,如
vtkDataReader
(读取 VTK 文件)。 - IOXML:XML 格式,如
vtkXMLPolyDataReader
(读取 XML 结构的几何数据)。 - IOPLY:读取和写入 PLY 文件(常用于 3D 扫描数据)。
- IOSTL:读取和写入 STL 文件(3D 打印常用)。
- IODICOM:医学影像读取(DICOM)。
- IOHDF5:支持 HDF5 数据格式(用于大规模科学数据)。
物理仿真和计算(VTK Physics 和 Parallel 系列,物理仿真和并行计算)
这些模块用于模拟物理现象,如流体力学、刚体碰撞等。
- PhysicsRigidBody:刚体物理,如
vtkRigidBodyMotion
(刚体运动)。 - ParallelCore:并行计算核心,适用于大数据可视化。
- ParallelMPI:支持 MPI 并行计算(超算集群)。
- ParallelDIY:用于分布式数据处理,如
vtkDIYExplicitAssigner
。
科学可视化(VTK Views 和 Charts 系列)
这些模块用于科学数据可视化,如图表、统计数据等。
- ViewsCore:通用视图管理,如
vtkView
(视图基类)。 - ViewsInfovis:信息可视化,如
vtkGraphLayoutView
(图数据可视化)。 - ChartsCore:绘制 2D/3D 图表,如
vtkChartXY
(折线图)。
VTK 数据结构和智能指针
在 VTK 中,所有的数据处理和可视化都围绕着 VTK 的数据结构展开,VTK 的核心数据对象可以分成以下几类:
- 表面几何数据(vtkPolyData)
- 体数据(vtkImageData)
- 结构化网格数据(vtkStructuredGrid,vtkRectilinearGrid)
- 非结构化网格数据(vtkUnstructuredGrid)
- 图数据(vtkGraph,vtkTree)
- 表格数据(vtkTable)
所有 VTK 的数据结构都继承自 vtkDataObject 基类:
1 | vtkDataObject <-- 所有数据的基类 |
另外,在 VTK 中,所有的对象都是基于引用计数的对象,必须通过 vtkSmartPointer
进行管理,以避免内存泄漏。vtkSmartPointer
会自动管理 VTK 对象的引用计数和生命周期,避免手动 delete,确保正确释放资源。
1 | vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New(); |
等效于
1 | vtkCubeSource* cube = vtkCubeSource::New(); |
这里,vtkSmartPointer<T>
是一个 C++ 泛型(模板)编程,允许创建适用于不同数据类型的类:
1 | template <typename T> |
使用时:
1 | Example<int> intObj; // 适用于 int 类型 |
::
在 C++ 中用于访问类的静态成员或访问某个命名空间中的成员。静态方法属于类,而不是对象,所以必须用 ::
调用。在普通的 C++ 中,创建对象通常使用 new
:
1 | vtkCubeSource* cube = new vtkCubeSource(); |
但是这样创建的对象需要手动 delete,否则会造成内存泄漏。在 VTK 中,使用 vtkSmartPointer<T>::New()
来管理内存:
1 | vtkSmartPointer<vtkCubeSource> cube = vtkSmartPointer<vtkCubeSource>::New(); |
vtkSmartPointer
自动管理对象的生命周期。当 cube
变量超出作用域时,对象会自动销毁,避免内存泄漏。它类似于 VTK 版的 std::shared_ptr
。在 C++ 里,std::shared_ptr
类似 Python 的对象管理方式,它使用引用计数,当没有 shared_ptr
继续引用对象时,自动销毁。自动管理内存,避免 new
和 delete
,防止内存泄漏。
在 C++ 里,如果直接用 new
分配内存,必须手动 delete
,否则会造成内存泄漏:
1 | class MyClass { |
✅ 解决方案:使用 std::shared_ptr
1 |
|
常用的四种数据结构:
1. 表面几何数据(vtkPolyData)
vtkPolyData
是 VTK 最常见的几何数据格式,用于存储 点(Points)、线(Lines)、面(Polygons)、多面体(Polyhedron)。
📌 适用于:STL/PLY/OBJ 3D 模型、点云数据(如 LIDAR 扫描数据)、多边形网格、等值面可视化
📌 数据结构:
- 点(Points)
- 拓扑结构(Cells):存储点如何组成几何形状(线、三角形、四边形等)
- 属性数据(Scalars/Vectors):每个点或单元的颜色、速度、法向量等数据
📌 示例:创建一个三角形
1 | // 创建点集,vtkPoints 用于存储三维点坐标,这里添加了3个点 |
2. 体数据(vtkImageData)
vtkImageData
代表 规则网格的 3D 体数据,通常用于医学影像(CT, MRI)或流体计算。
📌 适用于:医学 DICOM 数据(CT, MRI)、流体力学(CFD)计算结果、体渲染(Volume Rendering)
📌 数据结构:
- 规则网格(Structured Grid):每个体素(Voxel)按 x, y, z 规则排列
- 标量(Scalars):存储体素的颜色、密度等数据
📌 示例:创建一个 3D 体数据
1 | // vtkImageData 是 VTK 用于存储结构化图像数据的核心数据结构。它用于表示规则网格(structured grid),可以存储 2D 或 3D 体素数据 (volumetric data) |
3. 结构化网格(vtkStructuredGrid, vtkRectilinearGrid)
vtkStructuredGrid
:适用于 规则但不均匀间距的网格,如 CFD 数据。vtkRectilinearGrid
:适用于 X、Y、Z 轴分别均匀间隔的网格。
📌 适用于:流体力学、有限元分析(FEA)
📌 示例:创建结构化网格
1 | vtkSmartPointer<vtkStructuredGrid> structuredGrid = vtkSmartPointer<vtkStructuredGrid>::New(); |
4. 非结构化网格(vtkUnstructuredGrid)
vtkUnstructuredGrid
适用于 复杂的网格结构,包括:四面体、六面体、金字塔、棱柱等,我们需要 手动添加点坐标 和 单元 (Cell) 数据 来填充它。
📌 适用于:有限元分析(FEA)、复杂网格 CFD 计算
📌 示例:创建一个四面体网格
1 | // 1. 创建 vtkUnstructuredGrid 对象,用于存储非结构化网格数据 |
VTK 可视化管线
VTK 的可视化管线(Visualization Pipeline)定义了数据从输入到最终渲染的整个流程,是 VTK 后处理的一般流程。在 VTK 中,可视化管线由以下五个核心极端组成:
数据源(Source) -> 数据过滤(Filter) -> 数据映射(Mapper) -> 渲染管理(Renderer) -> 交互控制(Interactor)
1. 数据源(Source)
数据源是 VTK 可视化管线的起点,它负责 生成数据或读取外部数据。数据可以来自:几何数据(点云、网格、曲面);医学影像数据(DICOM, NIfTI);科学计算数据(CFD, FEA, VTK 文件);分子动力学轨迹(如 GROMACS .xtc
, LAMMPS .dump
)。常用的 VTK 数据源类包括:
类型 | VTK 类 | 说明 |
---|---|---|
基础几何体 | vtkCubeSource , vtkSphereSource |
生成立方体、球体等基本几何体 |
点云数据 | vtkPointSource |
生成随机点云数据 |
网格数据 | vtkStructuredGrid , vtkUnstructuredGrid |
处理结构化/非结构化网格 |
体数据 | vtkImageData , vtkRectilinearGrid |
适用于医学影像、流体计算数据 |
文件输入 | vtkSTLReader , vtkPLYReader , vtkXMLPolyDataReader |
读取 .stl , .ply , .vtk 文件 |
示例:创建立方体数据
1 | vtkSmartPointer<vtkCubeSource> cubeSource = vtkSmartPointer<vtkCubeSource>::New(); |
💡 cubeSource->Update();
强制执行数据更新,确保数据正确传递给后续的管线。
2. 数据过滤(Filter)
数据经过数据源生成后,通常需要进行处理或转换,这就是数据过滤(Filter)的作用。常用的 VTK 过滤类包括:
过滤器类型 | VTK 类 | 作用 |
---|---|---|
变换 | vtkTransformFilter |
旋转、缩放、平移 |
平滑 | vtkSmoothPolyDataFilter |
平滑几何表面 |
裁剪 | vtkClipPolyData |
根据平面裁剪 |
等值面 | vtkContourFilter |
计算等值面(用于流体/医学影像) |
采样 | vtkDecimatePro |
降低多边形数 |
网格化 | vtkDelaunay2D |
生成三角网格 |
示例:平滑几何体
1 | vtkSmartPointer<vtkSmoothPolyDataFilter> smoothFilter = vtkSmartPointer<vtkSmoothPolyDataFilter>::New(); |
3. 数据映射(Mapper)
数据处理完成后,需要将其转换为 图形系统可识别的格式,这就是数据映射(Mapper)的作用。常用的 Mapper 类包括:
Mapper 类型 | VTK 类 | 作用 |
---|---|---|
多边形数据 | vtkPolyDataMapper |
映射几何体,如点、线、三角面 |
体数据 | vtkVolumeMapper |
体渲染(如医学 CT 扫描数据) |
矢量数据 | vtkGlyph3DMapper |
处理流体/场数据(如速度场、磁场) |
示例:创建 Mapper
1 | vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); |
4. 渲染管理(Renderer)
VTK 使用 Renderer 管理 3D 场景,包括:添加 Actor(3D 物体);设置背景颜色;管理光照。VTK 渲染核心类包括:
类型 | VTK 类 | 作用 |
---|---|---|
场景管理 | vtkRenderer |
管理 3D 物体、光照 |
渲染窗口 | vtkRenderWindow |
显示渲染内容 |
物体 | vtkActor |
代表一个 3D 物体 |
示例:创建 Renderer
1 | vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New(); |
5. 交互控制(Interactor)
VTK 提供交互工具 vtkRenderWindowInteractor
,可以用鼠标旋转、缩放、平移 3D 视图。VTK 交互核心类包括:
交互类型 | VTK 类 | 作用 |
---|---|---|
交互窗口 | vtkRenderWindowInteractor |
允许鼠标/键盘交互 |
交互样式 | vtkInteractorStyleTrackballCamera |
轨迹球交互模式 |
UI 控件 | vtkSliderWidget |
创建滑块控制 |
示例:创建交互器
1 | vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); |
绘制一个立方体
这里是一个基本的 VTK 库的使用范例,用于绘制一个立方体。从例子也可以看到,VTK 的渲染管线是层层封装的,每一层都包含并管理更基础的组件:
1 | vtkRenderWindowInteractor |
交互器(Interactor)
vtkRenderWindowInteractor
是 用户交互控制的入口,它控制 鼠标旋转、缩放、键盘输入等。- 它管理 vtkRenderWindow,使其可以响应用户输入。
渲染窗口(Render Window)
vtkRenderWindow
是 真正的 3D 窗口,它 显示所有渲染内容。- 它管理 vtkRenderer,可以包含多个 Renderer(类似于多个视口)。
渲染器(Renderer)
vtkRenderer
负责管理 3D 场景,包括:vtkActor
(物体)、光照、背景颜色、摄像机。一个
vtkRenderWindow
可以有多个vtkRenderer
演员(Actor)
vtkActor
代表 3D 物体,它是最终显示在屏幕上的可见对象。vtkActor
不存储几何数据,它只是一个 外观控制器,管理:Mapper(数据映射);颜色、透明度;变换(缩放、旋转、平移)。
数据映射(Mapper)
vtkMapper
负责 将几何数据转换为渲染数据。- 它接受 PolyData、Volume、Grid 作为输入,并将数据转换为可显示的格式。
- 常见的
Mapper
:vtkPolyDataMapper
→ 处理几何数据vtkVolumeMapper
→ 体渲染vtkDataSetMapper
→ 处理网格数据
数据(PolyData, ImageData, Grid)
vtkPolyData
→ 几何数据(点、线、三角形)vtkImageData
→ 体数据(医学 CT、MRI、流体数据)vtkUnstructuredGrid
→ 非结构化网格(CFD、有限元计算)
1 | // main.cpp |
1 | # CMakeLists.txt |
构建后,可以得到如下的结果:
