VTK+Qt+Cmake+VS的环境搭建
本文的主要内容:简单介绍如何使用Cmake编译安装VTK源代码;如何配置VTK在Qt中的使用环境;如何以VS作为IDE在C++下使用Qt+VTK。
哪些人适合阅读本文:有一定Cmake、Qt和VS的基础,但是初次使用VTK的人。
一、准备工作
- 安装VS 2022
- 安装Cmake 3.28.3
- 安装Qt 5.15.2
- VTK 9.3.0 源代码,去官网下载
二、VTK源码安装过程
- 如果需要用Qt作为GUI,建议先把Qt装好,在Cmake Config的时候会自动识别出Qt的安装情况。
- 解压VTK源代码,在同级目录下新建两个文件夹,其中vtk-build用于存放编译文件,安装完之后可以删除,避免占用空间。vtk-install文件夹用于存放安装之后的库文件。
- 打开cmake-gui,源文件夹选择VTK-9.3.0,Build文件夹选择vtk-build,然后点Configure,选择对应的VS和X64。
- 在接下来出现的配置窗口里,有几个需要注意的地方,可以通过Search搜索栏直接找到需要修改的配置条目。
- BUILD_SHARED_LIBS -- 默认是勾选的,表示是动态编译。如果把勾选去掉表示VTK是静态编译。如果是动态编译,vtk-install/bin文件夹下会生成dll动态库,需要把这个文件夹添加到环境变量,应用程序运行时会自动从环境变量找到这个库;或者在写应用程序时复制需要用到的dll文件到exe文件同目录,否则运行会报错。静态库则不用。
- "静态编译"与"动态编译"的区别 :静态编译就是在编译的时候把所有的模块都编译进可执行文件(exe)里去,当启动这个可执行文件时所有的模块都已加载进来。动态编译则是编译的时候需要的模块都没有编译进去,一般情况下可以把这些模块都编译成动态链接库DLL,启动程序(初始化)的时候这些模块不会被加载,运行的时候用到那个模块就调用哪个DLL文件。静态链接库编译相当于你带着一个工具包到处跑,遇到有需要的地方不需要周围的环境提供相应的工具,自己用自己工具包的工具就行了,所以当环境发生变化可以尽可能的无视;动态链接库编译相当于不带任何东西,走到哪是哪。这两者的区别显然就是前者重量增加了,即程序的体积会比后者的大。所以,究竟是用"静态编译"还是"动态编译",关键看自己的需要。对于VTK初学者而言,所涉及到的工程可能都比较小,建议用"静态编译",也方便把VTK程序移植到其他没有安装VTK的计算机上运行。本书的VTK类库是采用静态编译。-- 引用自CSDN博客
- CMAKE_INSTALL_PREFIX -- 这个选项表示VTK的安装路径,默认的路径是:C:/Program Files/VTK。将这个路径改成刚刚新建的vtk-install路径,安装完之后会在这个路径生成库文件。
- 改完上述配置之后,再点一下Configure,然后Generate,然后OpenProject将在VS中打开工程。
- 将Debug改为Release,在ALL BUILD右键->生成,等待编译完成。
- 完成之后在INSTALL右键->生成,完成之后会把需要的库文件复制到vtk-install文件夹。
如果配置的是动态库,则bin文件夹下会有dll文件,如果是静态库则没有。
至此,安装完成。如果想节约硬盘空间,可以将VTK9.3.0和vtk-build删除,只保留安装后的库文件vtk-install就可以了。
三、错误排查
- 使用VTK-9.3.0在VS2019里面编译会报错:
vtkCommonCore-9.3d.lib(vtkSMPToolsAPI.obj) : error LNK2019: unresolved external symbol "public: bool __cdecl vtk::detail::smp::vtkSMPToolsImpl<1>::IsParallelScope(void)" (?IsParallelScope@?$vtkSMPToolsImpl@$00@smp@detail@vtk@@QEAA_NXZ) referenced in function "public: bool __cdecl vtk::detail::smp::vtkSMPToolsAPI::IsParallelScope(void)" (?IsParallelScope@vtkSMPToolsAPI@smp@detail@vtk@@QEAA_NXZ)
这个时候将下面这个选项勾选去掉重新编译安装一遍就可以了:
- 在结合Qt使用时会出现QVTKOpenGLNativeWidget.h找不到
这两个选项要改成yes:
四、Cmake中引用VTK
-
上面已经说了,如果是动态编译,则要把vtk-install/bin这个文件夹添加到环境变量。如果是静态编译则不用。
-
然后在应用程序的CmakeLists.txt里面加入:
set(VTK_DIR "D:/Software/VTK-install/lib/cmake/vtk-9.3")
FIND_PACKAGE(VTK REQUIRED)
target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})vtk_module_autoinit is needed
vtk_module_autoinit(TARGETS ${PROJECT_NAME} MODULES ${VTK_LIBRARIES})
-
如果在CmakeLists.txt里面没有设置VTK_DIR,在通过Cmake配置应用程序时就会出现VTK_DIR NOT FOUND,将vtk-install/lib/cmake/vtk-9.3目录添加进去就可以了。
五、代码示例
接下来写一个最简单的VTK+Qt+Cmake+VS的工程示例。
工程的目录如下,build里面是编译输出的文件。
src文件夹里只有三个文件:
CMakeLists.txt
cpp
# 设置cmake版本号
cmake_minimum_required(VERSION 3.10)
# 设置moc rcc uic
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
# 设置工程名称
project(VTKTest)
# 使用VTK
set(VTK_DIR "D:/Software/VTK/vtk-install/lib/cmake/vtk-9.3")
FIND_PACKAGE(VTK REQUIRED)
# 使用QT
set(Qt5_DIR D:/Software/Qt/Qt5.12.10/5.12.10/msvc2017_64/lib/cmake/Qt5/)
find_package(Qt5 COMPONENTS Widgets Core Gui Network OpenGL REQUIRED) # 查找Qt库文件
# 查找源码
include_directories("src/")
FILE(GLOB SRC_FILES "src/*.cpp")
FILE(GLOB HEAD_FILES "src/*.h")
source_group("src" FILES ${SRC_FILES} ${HEAD_FILES})
# 添加可执行文件
add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES} )
# 设置工程包含当前目录
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# 添加依赖项
target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::Core Qt5::Gui Qt5::Network Qt5::OpenGL ${VTK_LIBRARIES})
# vtk_module_autoinit is needed
vtk_module_autoinit(
TARGETS ${PROJECT_NAME}
MODULES ${VTK_LIBRARIES}
)
main.cpp
cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Widget.cpp
cpp
#include "widget.h"
#include <QHBoxLayout>
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkCubeSource.h>
#include <vtkProperty.h>
#include <vtkNamedColors.h>
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
this->setGeometry(640, 360, 640, 360);
QHBoxLayout* layout = new QHBoxLayout(this);
qVTKWidget = new QVTKOpenGLStereoWidget(this);
layout->addWidget(qVTKWidget);
vtkShow();
}
Widget::~Widget()
{
}
void Widget::vtkShow()
{
// 创建一个球体源
vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
sphereSource->SetCenter(0, 0, 0);
sphereSource->SetRadius(0.5);
sphereSource->SetThetaResolution(20);
sphereSource->SetPhiResolution(20);
// 创建一个正方体源
vtkSmartPointer<vtkCubeSource> cubeSource = vtkSmartPointer<vtkCubeSource>::New();
cubeSource->SetCenter(1.5, 0, 0);
cubeSource->SetXLength(1);
cubeSource->SetYLength(1);
cubeSource->SetZLength(1);
// 创建一个多边形数据映射器
vtkSmartPointer<vtkPolyDataMapper> sphereMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
sphereMapper->SetInputConnection(sphereSource->GetOutputPort());
vtkSmartPointer<vtkPolyDataMapper> cubeMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
cubeMapper->SetInputConnection(cubeSource->GetOutputPort());
vtkSmartPointer<vtkNamedColors> colors = vtkSmartPointer<vtkNamedColors>::New();
// 创建一个演员
vtkSmartPointer<vtkActor> sphereActor = vtkSmartPointer<vtkActor>::New();
sphereActor->SetMapper(sphereMapper);
sphereActor->GetProperty()->SetColor(colors->GetColor3d("red").GetData());
vtkSmartPointer<vtkActor> cubeActor = vtkSmartPointer<vtkActor>::New();
cubeActor->SetMapper(cubeMapper);
cubeActor->GetProperty()->SetColor(colors->GetColor3d("green").GetData());
// 创建一个渲染器
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(sphereActor);
renderer->AddActor(cubeActor);
renderer->ResetCamera();
qVTKWidget->renderWindow()->AddRenderer(renderer);
qVTKWidget->renderWindow()->Render();
}
Widget.h
cpp
#pragma once
#include <QWidget>
#include <QVTKOpenGLStereoWidget.h>
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
void vtkShow();
private:
QVTKOpenGLStereoWidget* qVTKWidget;
};
运行之后输出的效果如下: