VTK用户指南3:第一部分vtk简介_3系统概述

3.第三章:系统概述

本章的目的是为您提供可视化工具包系统的概述,并向您展示在C++、java、TCL和Python中创建应用程序所需的基本信息。我们首先介绍基本的系统概念和对象模型抽象。我们通过演示这些概念并描述构建应用程序所需的知识来结束本章。

3.1 系统架构

可视化工具包由两个基本子系统组成:编译的C++类库和一个“解释”的包装层,允许使用java、TCL和Python语言来处理编译的类。参见图3-1。
在这里插入图片描述

图 3-1

这种架构的优点是,您可以在编译的C++语言中建立高效的(CPU和内存使用)算法,并保留解释语言的快速代码开发特性(避免编译/链接周期,简单但强大的工具,以及访问GUI工具)。当然,对于精通C++的人和有这样做的工具,应用程序可以完全在C++中构建。
可视化工具包是一个面向对象的系统。有效使用VTK的关键是充分理解底层对象模型。这样做将消除围绕系统中数百个对象使用的许多谜团。有了这种理解,组合对象来构建应用程序就容易多了。您还需要了解系统中许多对象的功能;这只需要查看代码示例和在线文档。在本用户指南中,我们试图为您提供VTK对象的有用组合,您可以根据自己的应用程序进行调整。
在本节的剩余部分中,我们将介绍可视化工具包的两个主要组件:可视化管道和渲染引擎。可视化管道用于获取或创建数据、处理数据,并将结果写入文件或将结果传递给渲染引擎进行显示。渲染引擎负责创建数据的可视化表示。请注意,这些不是VTK真正严格的体系结构组件,而是概念组件。本章中的讨论将是相当高的层次,但当你结合本章和下一章中的具体示例,以及VTK源代码发行版中的数百个可用示例时,你将对这些组件有很好的理解。

3.1.1 低级对象模型

VTK对象模型可以被视为植根于超类vtkObject。几乎所有的VTK类都是从这个类派生的,或者在某些特殊情况下是从它的超类vtkObjectBase派生的。所有VTK都必须使用对象的New()方法创建,并且必须使用对象的Delete()方法。无法在堆栈上分配VTK对象,因为构造函数是受保护的方法。使用一个通用的超类和一种创建和销毁对象的统一方法,VTK能够提供几个基本的面向对象操作。
参考计数。对象显式存储引用它们的指针数。
通过类的static New()方法创建对象时,其初始引用计数为1,因为必须使用原始指针来引用新对象:

vtkObjectBase* obj = vtkExampleClass::New();

当创建或销毁对该对象的其他引用时,使用Register()和UnRegister()方法递增或递减引用计数。通常,这是由对象的API中提供的各种“set”方法自动处理的

otherObject->SetExample(obj);

引用计数现在是2,因为原始指针和存储在另一个对象中的指针都引用它。当不再需要最初存储对象的原始指针时,使用Delete()方法删除引用:

obj->Delete();

从现在开始,使用原始指针访问对象不再安全,因为指针没有对它的引用。为了确保正确管理对象引用,每次对New()的调用必须与以后对Delete()的调用配对,以确保没有引用泄漏。
类模板vtkSmartPointer提供了“智能指针”实现,简化了对象管理。上面的例子可以重写:

vtkSmartPointer<vtkObjectBase> obj =
vtkSmartPointer<vtkExampleClass>::New();
otherObject->SetExample(obj);

在这种情况下,智能指针会自动管理它所拥有的引用。当智能指针变量超出范围且不再使用时,例如当它是局部变量的函数返回时,它会通过减少引用计数自动通知对象。通过使用智能指针提供的静态New()方法没有原始指针需要保存对对象的引用,因此不需要调用Delete()。
运行时类型信息。在C++中,对象的实际类型可能与引用它的指针类型不同。VTK公共接口中的所有类都有简单的类名标识符(没有模板),所以一个字符串就足以识别它们。VTK对象的类型可能是使用GetClassName()方法在运行时获取:

const char* type = obj->GetClassName();

可以使用IsA()方法测试对象是特定类的实例还是其子类的实例:

if(obj->IsA("vtkExampleClass")) { ... }

使用派生类型的类提供的静态SafeDownCast()方法,可以将超类类型的指针安全地转换为派生类型

vtkExampleClass* example = vtkExampleClass::SafeDownCast(obj)

只有当对象确实是派生子类的实例,否则将返回空指针时,此操作才会在运行时成功。
对象状态显示。调试时,显示对象当前状态的可读描述通常很有用。这可以通过Print()方法获取VTK对象的当前状态:

obj->Print(cout);

3.1.2 渲染引擎

VTK渲染引擎由VTK中的类组成,这些类负责获取可视化管道的结果并将其显示到窗口中。这涉及以下组件。请注意,这并不是一个详尽的列表,而是渲染引擎中最常用的对象。这里使用的副标题是VTK中代表这种类型对象的最高级别超类,在许多情况下,如果有多种选择,这些是抽象类,定义了实现该功能的各种具体子类的基本API。。
vtkProp。场景中存在的数据的可见描述由vtkProp的一个子类表示。
vtkProp最常用的用于在3D中显示对象的子类是vtkActor(用于表示场景中的几何数据)和vtkVolume(用于表示场景中的体积数据)。
还有一些用2D表示数据的道具,比如vtkActor2D。vtkProp子类通常负责了解其在场景中的位置、大小和方向。用于控制道具放置的参数通常取决于道具是场景中的3D对象还是2D注释。对于vtkActor和vtkVolume等3D道具(vtkProp3D的两个子类,vtkProp3D本身就是vtkProp的一个子类),您可以直接控制以下参数:对象的三维位置、方向和比例,也可以使用4×4变换矩阵。对于提供注释的2D道具(如vtkScalarBarActor),可以通过多种方式定义注释的大小和位置,包括指定相对于整个视口大小的位置、宽度和高度。除了提供放置控制之外,道具通常还有一个mapper对象,用于保存数据并知道如何渲染数据,还有一个property对象,用于控制颜色和不透明度等参数。

有大量(超过50个)专门的道具,例如vtkImageActor(用于显示图像)和vtkPieChartActor(用于创建一系列数据值的饼图视觉表示)。
其中一些专用道具直接包含控制外观的参数,并直接引用要渲染的输入数据,因此不需要使用属性或映射器。vtkFollower道具是vtkActor的一个特殊子类,它将自动更新其方向,以便持续面对指定的相机。这对于在3D场景中显示广告牌或文本,并在用户旋转时使其保持可见非常有用。vtkLODActor也是vtkActor的一个子类,它会自动更改其几何表示以保持交互式帧速率,而vtkLODProp3D是vtkProp3D的一个子类,它可以在多个不同的映射器(甚至可能是体积映射器和几何映射器的混合体)之间进行选择,以提供交互性。VTKAAssembly当层次结构被平移、旋转或缩放时,允许参与者的层次结构可以正确地管理转换。

vtkAbstractMapper。有些道具,如vtkActor和vtkVolume,使用vtkAbstractMapper的子类来保存对输入数据的引用,并提供实际的渲染功能。vtkPolyDataMapper是渲染多边形几何体的主要映射器。对于体积对象,VTK提供了几种渲染技术,包括可用于渲染vtkImageData的vtkFixedPointVolumeRayCastMapper,以及可用于渲染vtkUnstructuredGrid数据的vtkProjectedTetrahedra mapper。

vtkProperty和vtkVolumeProperty。有些道具使用单独的属性对象来保存控制数据外观的各种参数。这使您可以更轻松地在场景中的不同对象之间共享外观设置。vtkActor对象使用vtkProperty来存储诸如颜色、不透明度以及材质的环境光、漫反射和镜面反射系数等参数。vtkVolume对象改为使用vtkVolumeProperty来捕获适用于体积对象的参数,例如将标量值映射到颜色和不透明度的传递函数。许多mappers还提供了设置可用于显示内部结构的剪裁平面的功能。

vtkCamera。vtkCamera包含控制如何查看场景的参数。vtkCamera有一个位置、一个焦点和一个定义场景中“向上”方向的向量。其他参数控制特定的查看变换(平行或透视)、图像的比例或视角,以及视锥体的近剪裁平面和远剪裁平面。

vtkLight。为场景计算照明时,需要一个或多个vtkLight对象。vtkLight对象存储灯光的位置和方向,以及颜色和强度。灯光还有一种类型,描述灯光将如何相对于相机移动。例如,前照灯始终位于相机的位置,并照亮相机的焦点,而场景灯位于场景中的静止位置。

vtkrender。构成场景的对象(包括道具、相机和灯光)被收集在一个VTKRender中。vtkRender负责管理场景的渲染过程。多个VTKrender对象可以在一个VTKrender窗口中一起使用。这些渲染器可以渲染到渲染窗口的不同矩形区域(称为视口),也可以重叠。

3.1.3 可视化管道

3.2 创建一个应用

本节介绍了在四种编程语言TCL、C++、java和Python中开发VTK应用程序所需的基本信息。读完本简介后,你应该跳到讨论你感兴趣的语言的小节。除了向您提供如何创建和运行简单应用程序的说明外,每个部分还将向您展示如何利用该语言中的回调。

3.2.1用户方法、观察者和命令

回调(或用户方法)在VTK中使用Subject/Observer和Command设计模式实现。这意味着几乎VTK中的每个类(vtkObject的每个子类)都有一个AddObserver()方法,可以用来设置VTK的回调。观察者会查看在对象上调用的每个事件,如果它与观察者正在监视的事件之一匹配,则会调用相关的命令(即回调)。例如,所有VTK过滤器在开始执行之前都会调用StartEvent。如果您添加了一个监视StartEvent的观察者,那么它将在每次过滤器开始执行时被调用。考虑下面的TCL脚本,该脚本创建vtkElevationFilter实例,并为StartEvent添加一个观察器来调用过程PrintStatus。

proc PrintStatus {} {
puts "Starting to execute the elevation filter"
}
vtkElevationFilter foo
foo AddObserver StartEvent PrintStatus

这种类型的功能(即回调)在VTK支持的所有语言中都可用。下面的每一节都将简要介绍如何使用它。关于用户方法的进一步讨论,请参见第421页的“与窗口系统集成”。(本节还讨论了用户界面集成问题。)

要创建自己的应用程序,我们建议从VTK附带的一个示例开始。它们可以在源分布的VTK/Examples中找到。在源代码分发中,示例首先按主题组织,然后按语言组织。在VTK/Examples下,你可以找到不同主题的目录,在这些目录下,你可以找到不同语言的子目录,比如Tcl。

3.2.2 Tcl

3.2.3 C++

使用C++作为开发语言通常会比任何其他语言都产生更小、更快、更容易部署的应用程序。C++开发也有一个优点,即不需要编译任何额外的支持TCL、java或Python。本节将介绍如何使用微软Visual C++ C++为PC创建简单的VTK C++应用程序,并使用适当的编译器为UNIX创建一个简单的VTK C++应用程序。我们将从一个名为Cone.cxx的简单示例开始,可以在Examples/Tutorial/Step1/cxx中找到。对于Windows和UNIX,您可以使用VTK的源代码安装或已安装的二进制文件。这些例子对两者都适用。
建立C++程序的第一步是使用CMake生成一个Maxfile或WorkSpace文件,这取决于编译器。CMakeList.txt文件来自Cone.cxx(如下所示)使用FindVTK和UseVTK CMake模块。这些模块试图定位VTK,然后设置包含路径和链接线来构建C++程序。如果他们未能成功找到VTK,则必须手动指定适当的CMake参数,并根据需要重新运行CMake。

PROJECT (Step1)
FIND_PACKAGE(VTK REQUIRED)
IF(NOT VTK_USE_RENDERING)
 MESSAGE(FATAL_ERROR
 "Example ${PROJECT_NAME} requires VTK_USE_RENDERING.")
ENDIF(NOT VTK_USE_RENDERING)
INCLUDE(${VTK_USE_FILE})
ADD_EXECUTABLE(Cone Cone.cxx)
TARGET_LINK_LIBRARIES(Cone vtkRendering)

VTK 7.7版本已经改为:

cmake_minimum_required(VERSION 2.8.5 FATAL_ERROR)
if(POLICY CMP0025)
  cmake_policy(SET CMP0025 NEW) # CMake 3.0
endif()
if(POLICY CMP0053)
  cmake_policy(SET CMP0053 NEW) # CMake 3.1
endif()

project (Step1)

find_package(VTK REQUIRED)
vtk_module_config(VTK
  vtkCommonCore
  vtkFiltersSources
  vtkFiltersSources
  vtkInteractionStyle
  vtkRendering${VTK_RENDERING_BACKEND}
)
include(${VTK_USE_FILE})
add_executable(Cone MACOSX_BUNDLE Cone.cxx)
target_link_libraries(Cone ${VTK_LIBRARIES})

微软Visual C++。一旦你为圆锥示例运行了CGUT,你就可以开始启动微软Visual C++并加载生成的解决方案文件。目前的情况。NET版本的编译将命名为Cone。sln。现在,您可以选择一种构建类型(如Release或Debug)并构建应用程序。如果要将VTK集成到不使用CMake的现有项目中,可以将此简单示例中的设置复制到现有工作区中。

现在考虑一个真正的Windows应用程序的例子。这个过程与我们上面所做的非常相似,只是我们创建了一个windows应用程序,而不是控制台应用程序,如下所示。大部分代码都是标准的Windows代码,任何Windows开发人员都会很熟悉。这个例子可以在VTK/Examples/GUI/Win32/SimpleCxx/Win32Cone.cxx中找到。请注意,CmakeList.txt的唯一显著变化是CMakeLists.txt文件是在ADD_EXECUTABLE命令中添加的WIN32参数。


#include "windows.h"

// first include the required header files for the vtk classes we are using
#include "vtkConeSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"

static HANDLE hinst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// define the vtk part as a simple c++ class
class myVTKApp
{
public:
  myVTKApp(HWND parent);
  ~myVTKApp();
private:
  vtkRenderWindow *renWin;
  vtkRenderer *renderer;
  vtkRenderWindowInteractor *iren;
  vtkConeSource *cone;
  vtkPolyDataMapper *coneMapper;
  vtkActor *coneActor;
};

我们首先包含所需的VTK包含文件。接下来,我们有两个标准windows原型,后面是一个名为myVTKApp的小类定义。在C++开发时,应该尝试使用面向对象的方法来代替许多TCL示例中发现的脚本编程风格。在这里,我们将应用程序的VTK组件封装到一个小类中。
这是myVTKApp的构造函数。正如您所见,它分配所需的VTK对象,设置它们的实例变量,然后将它们连接起来,形成可视化管道。除了vtkrender窗口之外,大部分都是简单的VTK代码。此构造函数接受父窗口的HWND句柄,该父窗口应包含VTK渲染窗口。然后,我们在vtkRenderWindow的SetParentId()方法中使用它,以便它将其窗口创建为传递给构造函数窗口hand的子窗口

myVTKApp::myVTKApp(HWND hwnd)
{
  // Similar to Examples/Tutorial/Step1/Cxx/Cone.cxx
  // We create the basic parts of a pipeline and connect them
  this->renderer = vtkRenderer::New();
  this->renWin = vtkRenderWindow::New();
  this->renWin->AddRenderer(this->renderer);

  // setup the parent window
  this->renWin->SetParentId(hwnd);
  this->iren = vtkRenderWindowInteractor::New();
  this->iren->SetRenderWindow(this->renWin);

  this->cone = vtkConeSource::New();
  this->cone->SetHeight( 3.0 );
  this->cone->SetRadius( 1.0 );
  this->cone->SetResolution( 10 );
  this->coneMapper = vtkPolyDataMapper::New();
  this->coneMapper->SetInputConnection(this->cone->GetOutputPort());
  this->coneActor = vtkActor::New();
  this->coneActor->SetMapper(this->coneMapper);

  this->renderer->AddActor(this->coneActor);
  this->renderer->SetBackground(0.2,0.4,0.3);
  this->renWin->SetSize(400,400);

  // Finally we start the interactor so that event will be handled
  this->renWin->Render();
}

析构函数只是释放构造函数中分配的所有VTK对象。

myVTKApp::~myVTKApp()
{
    renWin->Delete();
    renderer->Delete();
    iren->Delete();
    cone->Delete();
    coneMapper->Delete();
    coneActor->Delete();
}

这里的WinMain代码都是标准windows代码,其中没有VTK引用。正如您所见,应用程序可以控制事件循环。事件由本节后面介绍的WndProc处理。

int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR /* lpszCmdParam */, int nCmdShow)
{
  static char szAppName[] = "Win32Cone";
  HWND        hwnd ;
  MSG         msg ;
  WNDCLASS    wndclass ;

  if (!hPrevInstance)
  {
    wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wndclass.lpfnWndProc   = WndProc ;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclass.lpszMenuName  = NULL;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndclass.lpszClassName = szAppName;
    RegisterClass (&wndclass);
  }

  hinst = hInstance;
  hwnd = CreateWindow ( szAppName,
                        "Draw Window",
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        400,
                        480,
                        NULL,
                        NULL,
                        hInstance,
                        NULL);
  ShowWindow (hwnd, nCmdShow);
  UpdateWindow (hwnd);
  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }
  return msg.wParam;
}

这个WndProc是一个非常简单的事件处理程序。对于一个完整的应用程序来说,这将非常复杂,但关键的集成问题是相同的。在这个函数的顶部,我们声明了一个对myVTKApp实例的静态引用。在处理WM_CREATE方法时,我们创建一个Exit按钮,然后构造一个myVTKApp实例,将句柄传递给当前窗口。vtkRenderWindowInteractor将处理vtkRenderWindow的所有事件,因此您不需要在这里处理它们。您可能需要添加代码来处理大小调整事件,以便渲染窗口根据您的整体用户界面适当调整大小。如果不设置vtkRenderWindow的ParentId,它将显示为顶级独立窗口。其他一切都应该和以前一样。

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  static HWND ewin;
  static myVTKApp *theVTKApp;

  switch (message)
  {
    case WM_CREATE:
    {
      ewin = CreateWindow("button","Exit",
                          WS_CHILD | WS_VISIBLE | SS_CENTER,
                          0,400,400,60,
                          hwnd,(HMENU)2,
                          (HINSTANCE)vtkGetWindowLong(hwnd,vtkGWL_HINSTANCE),
                          NULL);
      theVTKApp = new myVTKApp(hwnd);
      return 0;
    }

    case WM_COMMAND:
      switch (wParam)
      {
        case 2:
          PostQuitMessage (0);
          delete theVTKApp;
          theVTKApp = NULL;
          break;
      }
      return 0;

    case WM_DESTROY:
      PostQuitMessage (0);
      delete theVTKApp;
      theVTKApp = NULL;
      return 0;
  }
  return DefWindowProc (hwnd, message, wParam, lParam);
}

3.2.4 Java

3.2.5 Python

3.3 语言之间的转换

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片