【本节对应原书中的第19页至第25页】
本章旨在介绍VTK系统的总体概述,并讲解运用C++、Java、Tcl和Python等语言进行VTK应用程序开发时所需掌握的基本知识。首先我们从VTK系统的基本概念和对象模型抽象开始进行介绍,并在本章最后通过例子演示这些概念以及介绍一下在构建VTK工程时所需要掌握的知识。
3.1系统结构
VTK系统由两个子系统组成:一个是编译的C++类库,另一个是解释性语言的封装层,以供Java、Tcl和Python等语言来操作该C++类库,系统结构如图3-1所示。
图3-1VTK系统由C++类库内核以及解释性语言(Java、Tcl、Python)封装层所组成。
该结构的优点在于保持解释性语言快速开发特性(避免编译、链接流程,工具简单而强大,同时又易于使用的GUI工具)的同时,用户可以利用编译的C++语言开发高效的算法(无论是CPU利用还是内存的利用)。当然,对于熟练掌握C++语言或者使用相应开发工具的用户来说,VTK应用程序可以完全采用C++语言进行开发。
由于VTK系统采用了面向对象的思想,因此利用VTK进行高效开发的关键在于深入理解系统内部的对象模型,以便消除用户在使用大量的VTK系统对象时的迷惑,并能更有效的对这些对象进行组合创建应用程序。此外,还需要了解系统中许多对象的功能,而这只能通过阅读例子代码和在线文档来获得。这本《VTK用户指南》中,我们将尽力介绍一些有用的VTK对象的组合以便用户可以将其应用到自己的应用程序中。
本章接下来将介绍VTK中的两个重要组件:可视化管线【译者:Visualization Pipeline,有些翻译成“可视化管道”,本书我们翻译成“可视化管线”】和渲染引擎。可视化管线主要负责数据获取或者创建,数据处理,然后将数据写入文件或者传递至渲染引擎中进行显示。而渲染引擎负责创建传递过来的数据的一个可视表达。注意这里所提到的组件并非VTK系统结构中具体的组件,而是一个抽象的概念组件。本章是在比较高的层次上进行阐述,但是当你将本章的内容和下一章节中具体的例子或者是VTK源文件中所提供的大量的示例程序结合在一起时,你将会对本章所介绍的概念有更深刻的理解。
底层对象模型
vtkObject是VTK对象继承关系树的根结点,几乎所有的VTK对象都继承自该类,除了一部分特殊对象继承自vtkObject的父类vtkObjectBase。所有的VTK对象都必须由对象的New()方法创建,并由对象的Delete()方法销毁。由于VTK对象中的构造函数被声明为受保护类型,因此VTK对象不能够在栈中分配空间。另外,VTK对象采用了共同基类和统一的创建、销毁方法,可以实现许多基本的面向对象操作。
引用计数。VTK对象内部显式地记录了引用本身的指针的个数。当一个对象通过静态的New()函数创建时,该对象内部的初始引用计数即为1,因为所创建的对象会有一个原始指针(Raw Pointer)指向该对象。
vtkObjectBase*obj = vtkExampleClass::New();
当指向某个对象的其他对象创建或者销毁时,引用计数会通过Register()和Unregister()函数进行相应的增加和减少。通常情况下系统会通过对象的API函数“Set()”自动完成。
otherObject->SetExample(obj);
这时对象obj的引用计数值为2。因为除了原始指针之外,还有另外一个对象otherObject内部的指针指向它。当原始存储对象的指针不再需要时,通过Delete()函数可以删除该引用,即:
obj->Delete();
这样再利用原始指针去访问该对象时将不再是安全的,因为该指针已经不再拥有对象的引用。因此为了保证对对象引用的有效管理,每次调用New()后都要进行Delete(),确保没有泄漏引用。
另外一种避免引用泄漏的方法是通过类模板vtkSmartPointer<>提供的智能指针来简化对象的管理操作,上述例子可以重写为如下代码:
vtkSmartPointer<vtkObjectBase>obj = vtkSmartPointer<vtkObjectBase>::New();
otherOject->SetExample(obj);
该例中,智能指针会自动管理对象的引用。当智能指针变量超出其作用域并且不再使用时,例如当其为函数内部局部变量,函数返回时,智能指针会自动通知对象减少引用计数。由于智能指针提供了内部静态New()函数,因此不需要原始指针来保存对象的引用,因此也不需要再调用Delete()函数。
运行时类型信息。在C++中对象的实际类型可能与该对象的索引指针的类型不一致。VTK中所有的类都有一个接口函数提供类名标识符,因此一个string类型足以标识这些类。运行时对象的实际类型可以通过GetClassName()函数获得:
constchar* type = obj->GetClassName();
通过IsA()函数可以判断一个对象是否是某一个类的实例,或者是该类的某个子类的实例:
if(obj->IsA(“vtkExampleClass”)) { … }
父类类型的指针可以通过静态函数SafeDownCast()安全地强制转换为子类类型的指针:
vtkExampleClass*example = vtkExampleClass::SafeDownCast(obj);
上述代码只有当对象example是obj的子类实例时,类型转换才成功;否则返回空指针。
对象状态显示。容易理解的对象的当前状态信息对于程序的调试是十分有用的。VTK对象的状态可以通过输出函数Print()获得:
obj->Print(cout);
渲染引擎
组成VTK渲染引擎的类主要负责接收可视化管线(Visualization Pipeline)的输出数据并将结果渲染到窗口中。该过程主要涉及到下述一些组件。注意,这些只是VTK渲染系统中比较常用的组件,并非全部。而每个子标题仅仅是代表一个对象类型的最高层VTK超类【译者:或者称为“父类”】,在许多情况下,这些超类只是定义了基本API函数的抽象类,而真正的实现则由其子类来完成。
vtkProp。渲染场景中数据的可视表达(Visible Depictions)是由vtkProp的子类负责。三维空间中渲染对象最常用的vtkProp子类是vtkActor和vtkVolume,其中vtkActor用于表示场景中的几何数据(Geometry Data),vtkVolume表示场景中的体数据(Volumetric Data)。vtkActor2D常用来表示二维空间中的数据。vtkProp的子类负责确定场景中对象的位置、大小和方向信息。控制Prop【译者:Prop,Actor, Mapper, Property等词,本书不作翻译。】位置信息的参数依赖于对象是否在渲染场景中,比如一个三维物体或者二维注释,它们的位置信息控制方式是有所区别的。三维的Prop如vtkActor和vtkVolume(vtkActor和vtkVolume都是vtkProp3D的子类,而vtkProp3D继承自vtkProp),既可以直接控制对象的位置、方向和放缩信息,也可以通过一个4×4的变换矩阵来实现。而对于二维注释功能的Props如vtkScalarBarActor,其大小和位置有许多的定义方式,其中包括指定相对于视口的位置、宽度和高度。Prop除了提供对象的位置信息控制之外,Prop内部通常还有两个对象,一个是Mapper对象,负责存放数据和渲染信息,另一个是Property(属性)对象,负责控制颜色、不透明度等参数。
VTK中定义了大量的功能细化的Prop(超过50个),如vtkImageActor(负责图像显示)和vtkPieChartActor(用于创建数组数据的饼图可视表示)。其中的有些Props内部直接包括了控制显示的参数和待渲染数据的索引,因此并不需要额外的Property和Mapper对象。vtkActor的子类vtkFollower可以自动的更新方向信息保持自身始终面向一个特定的相机,这样无论如何旋转渲染场景中的对象,vtkFellower对象都是可见的,适用于三维场景中的广告板(Billboards)或者是文本。vtkActor的子类vtkLodActor可以自动改变自身的几何表示来实现所要求的交互帧率,vtkProp3D的子类vtkLODProp3D则是通过从许多Mapper中进行选择来实现不同的交互性(可以是Volumetric Mapper和GeometricMapper的集合)。vtkAssembly建立了Actor的等级结构以便在整个结构平移、旋转或者放缩时能够更合理的控制变换。
vtkAbsractMapper。许多Props如vtkActor和vtkVolume利用vtkAbstractMapper的子类来保存输入数据的引用以及提供真正的渲染功能。vtkPolyDataMapper是渲染多边形几何数据主要的Mapper类。而对于体数据,VTK提供了多种渲染技术。例如,vtkFixedPointVolumeRayCastMapper用来渲染vtkImageData类型的数据,vtkProjectedTetrahedraMapper则是用来渲染vtkUnstructuredGrid类型的数据。
vtkProperty和vtkVolumeProperty。某些Props采用单独的属性对象来存储控制数据外观显示的参数,这样不同的对象可以轻松的实现外观参数的共享。vtkActor利用vtkProperty对象存储外观(属性)参数,如颜色、不透明度、材质的环境光(Ambient)系数、散射光(Diffuse)系数和反射光(Specular)系数等。而vtkVolume则是采用vtkVolumeProperty对象来获取体对象的绘制参数,如将标量值映射为颜色和不透明度的传输函数(Transfer Function)【译者:也有译成“传递函数”】。另外,一些vtkMapper提供相应的函数设置裁剪面以便显示对象的内部结构。
vtkCamera。vtkCamera存储了场景中的摄像机参数,换言之,如何来“看”渲染场景里的对象,主要参数是摄像机的位置、焦点、和场景中的上方向向量。其他参数可以控制视图变换,如平行投影或者透视投影,图像的尺度或者视角,以及视景体的远近裁剪平面等。
vtkLight。vtkLight对象主要用于场景中的光照计算。vtkLight对象中存储了光源的位置和方向,以及颜色和强度等。另外,还需要一个类型来描述光源相对于摄像机的运动。例如,HeadLight始终位于摄像机处,并照向焦点方向;而SceneLight则始终固定在场景中的某个位置。
vtkRenderer。组成场景的对象包括Prop,Camara和Light都被集中在一个vtkRenderer对象中。vtkRenderer负责管理场景的渲染过程。一个vtkRenderWindow中可以有多个vtkRenderer对象,而这些vtkRenderer可以渲染在窗口中不同的矩形区域中(视口),甚至可以是覆盖的区域。
vtkRendererWindow。vtkRendererWindow将操作系统与VTK渲染引擎连接到一起。不同平台下的vtkRendererWindow子类负责本地计算机系统中窗口创建和渲染过程的管理。当使用VTK开发应用程序时,只需要使用平台无关的vtkRendererWindow类,程序运行时,系统会自动替换为平台相关的vtkRendererWindow子类。vtkRendererWindow中包含了vtkRenderer的集合,以及控制渲染的参数,如立体显示(Stereo)、反走样、运动模糊(Motion Blur)和焦点深度(FocalDepth)。
vtkRenderWindowInteractor。vtkRenderWindowInteractor负责监听鼠标、键盘和时钟消息,并通过VTK中的Command/Observer设计模式进行相应的处理。vtkInteractorStyle监听这些消息并进行处理以完成旋转、拉伸和放缩等运动控制。vtkRenderWindowInteractor自动建立一个默认的3D场景交互器样式(InteractorStyle),当然你也可以选择一个二维图像浏览的交互器样式,或者是创建自定义的交互器样式。
vtkTransform。场景中的许多对象,如Prop、光源Light、照相机Camera等都需要在场景中合理的放置,它们通过vtkTransform参数可以方便的控制对象的位置和方向。vtkTransform能够描述三维空间中的线性坐标变换,其内部表示为一个4×4的齐次变换矩阵。vtkTransform对象初始化为一个单位矩阵,你可以通过管线连接的方式将变换进行组合来完成复杂的变换。管线方式能够确保当其中任一个变换被修改时,其后续的变换都会相应的进行更新。
vtkLookupTable,vtkColorTransferFunction和vtkPiecewiseFunction。标量数据可视化经常需要定义一个标量数据到颜色和不透明度的映射。在几何面绘制中用不透明度定义表面的透明程度,而体绘制中不透明度表示光线穿透物体时不透明度沿着光线的累积效果,两者都需要定义不透明度的映射。对于几何渲染可以使用vtkLookupTable来创建映射,体绘制中需要使用vtkColorTransferFunction和vtkPiecewiseFunction来建立映射。
一个简单的例子。下面的例子(摘自./VTK/Examples/Rendering/CXX/Cylinder.cxx)演示了怎样利用上述对象来指定和渲染场景。
- vtkCylinderSource*cylinder = vtkCylinderSource::New();
- vtkPolyDataMapper*cylinderMapper = vtkPolyDataMapper::New();
- cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
- vtkActor*cylinderActor = vtkActor::New();
- cylinderActor->SetMapper(cylinderMapper);
- vtkRenderer*ren1 = vtkRenderer::New();
- ren1->AddActor(cylinderActor);
- vtkRenderWindow*renWin = vtkRenderWindow::New();
- renWin->AddRenderer(ren1);
- vtkRenderWindowInteractor*iren = vtkRenderWindowInteractor::New();
- iren->SetRenderWindow(renWin);
- renWin->Render();
- iren->Start();
例子中我们直接创建了一个vtkActor,vtkPolyDataMapper,vtkRenderer,vtkRendererWindow和vtkRendererWindowInteractor。注意,vtkProperty会由vtkActor自动创建,而vtkLight和vtkCamera会由vtkRenderer自动创建。