首页 > 代码库 > 第04章-VTK基础(4)

第04章-VTK基础(4)

【译者:这个系列教程是以Kitware公司出版的《VTK User’s Guide -11th edition》一书作的中文翻译(出版时间2010年,ISBN: 978-1-930934-23-8),由于时间关系,我们不能保证每周都能更新本书内容,但尽量做到一周更新一篇到两篇内容。敬请期待^_^。欢迎转载,另请转载时注明本文出处,谢谢合作!同时,由于译者水平有限,出错之处在所难免,欢迎指出订正!】

【本小节内容对应原书的第52页至第63页】

4.6 控制3D Props

VTK中的渲染窗口渲染的对象通常称之为“Prop”(“Prop”这个词来源于舞台剧,指的是出现在舞台上的东西)。VTK里有几种不同类型的Prop,包括vtkProp3D和vtkActor。其中vtkProp3D是一个抽象父类,表示的是三维场景中的对象;vtkActor是vtkProp3D的子类,用类似多边形(Polygon)和线(Lines)等基本数据来定义它的几何。

指定vtkProp3D的位置

我们已经知道了如何绕着一个对象来移动相机;反过来,也可以保持相机不动,而对Prop进行变换。下面的方法可以用于定义一个vtkProp3D(及其子类)对象的位置。

  • SetPosition(x, y, z)—— 指定vtkProp3D对象在世界坐标系中的位置。
  • AddPosition(deltaX,deltaY, deltaZ) —— 用指定的X、Y、Z三个方向的增量来平移Prop。
  • RotateX(theta),RotateY(theta), RotateZ(theta) —— 分别用指定的角度绕X、Y、Z轴旋转Prop。
  • SetOrientation(x, y,z) —— 通过先绕Z轴,然后绕X轴,最后绕Y轴旋转,从而来确定Prop的方向。
  • AddOrientation(a1, a2,a3) —— 在当前Prop方向增加a1,a2, a3增量。
  • RotateWXYZ(theta, x,y, z) —— 绕x, y, z指定的向量旋转theta角度。
  • SetScale(sx, sy, sz)—— 分别沿X、Y、Z三个方向缩放sx,sy, sz比例。
  • SetOrigin(x, y, z) —— 指定Prop的原点,Prop的原点指的是Prop旋转或缩放时的基准点。

以上这些方法联合使用,可以产生复杂的变换矩阵。最重要的一点是,以上方法使用时要注意它们的调用顺序,不同的顺序对Actor的位置有不同的影响。VTK里是用以下的顺序来应用这些变换的:

1.移动Prop到原点。

2.缩放。

3.绕Y轴旋转。

4.绕X轴旋转。

5.绕Z轴旋转。

6.从原点中移动回原来的位置。

7.平移。

第1步和第6步平移的大小分别是Origin的负值和正值。单纯的平移是由vtkProp3D的Position值来确定的。这些变换中最容易混淆的旋转操作。例如,将一个Prop先绕X旋转,再绕Y轴旋转,它的效果与先绕Y轴旋转,再绕X轴旋转的效果是完全不一样的(图4-4)。要了解更多关于Actor变换内容可以参考《VisualizationToolkit》一书。


图4-4不同旋转顺序的效果。左边是先绕X轴旋转,再绕Y轴旋转的效果;右边是先绕Y轴旋转,再绕X轴旋转的效果

接下来,我们会介绍各种类型的vtkProp3D,其中VTK里最常用的类是vtkActor。在“控制vtkActor2D”一节里会介绍一下2DProp (也就是vtkActor2D),这个类主要应用于标注(Annotation)及其他的二维操作。

Actors

Actor是最常的vtkProp3D类型,像其他的vtkProp3D子类一样,vtkActor提供了一组渲染属性,如表面属性(如环境光、散射光和镜面光颜色),显示形式(Representation)(如表面模型或线框模型),纹理映射以及几何定义(即mapper)。

定义几何。前面的例子我们已经知道,一个Actor的几何是通过SetMapper()方法指定的:

vtkPolyDataMapper mapper
  mapper SetInputConnection [aFilter GetOutputPort]
vtkActor anActor
  anActor SetMapper mapper

在这个例子里,mapper的类型是vtkPolyDataMapper,也就是用类似点、线、多边形(Polygons)和三角形带(Triangle Strips)等几何图元进行渲染的。Mapper会结束可视化管线,在可视化子系统和图形子系统之间起到桥梁的作用。

Actor的属性。Actor里有一个类型为vtkProperty的实例,主要是用来控制Actor的显示属性。常用的属性是Actor的颜色,我们会在后面的内容详细描述。其他的重要属性有显示形式(点模型、线框模型或表面模型)、着色方法(平面着色或Gouraud着色)、Actor的不透明度(相对于透明度)以及环境光、散射光和镜面光颜色等相关参数。下面的脚本程序演示了如何设置这些变量。

vtkActor anActor
  anActor SetMapper mapper
  [anActor GetProperty] SetOpacity 0.25
  [anActor GetProperty] SetAmbient 0.25
  [anActor GetProperty] SetDiffuse 0.6
  [anActor GetProperty] SetSpecular 1.0
  [anActor GetProperty] SetSpecularPower 10.0

注意,我们通过方法GetProperty()间接引用Actor的属性。或者,我们也可以先实例化一个vtkProperty对象,然后把它设置到Actor中。

vtkProperty prop
  prop SetOpacity 0.25
  prop SetAmbient 0.5
  prop SetDiffuse 0.6
  prop SetSpecular 1.0
  prop SetSpecularPower 10.0
vtkActor anActor
  anActor SetMapper mapper
  anActor SetProperty prop

后一种方法的好处是,我们可以把多个Actor设置成同一种属性。

Actor的颜色。颜色可能是Actor里最重要的属性了。设置Actor的颜色最简单的方法莫过于调用SetColor()方法,该方法用RGB值来设置一个Actor的红、绿、蓝分量的颜色,每个分量的取值范围从0到1。

[anActor GetProperty] SetColor 0.1 0.2 0.4

或者,我们也可以通过设置环境光颜色、散射光颜色和镜面光颜色来控制Actor的颜色。

vtkActor anActor
anActor SetMapper mapper
[anActor GetProperty] SetAmbientColor .1 .1 .1
[anActor GetProperty] SetDiffuseColor .1 .2 .4
[anActor GetProperty] SetSpecularColor 1 1 1

以上代码把环境光颜色设置成深灰色,散射光颜色设置成蓝色的阴影,镜面光颜色设置成白色。(注意:SetColor()方法就是用指定的RGB值来设置环境光颜色、散射光颜色和镜面光颜色。)

重要:Actor的属性中关于颜色的设置只有当Actor的Mapper没有标量数据(ScalarData)时才起作用。缺省情况下,Mapper输入的标量数据会对Actor进行着色,而Actor的颜色设置会被忽略。如果要忽略这些标量数据,可以使用方法ScalarVisibilityOff(),如下面的Tcl脚本所示:

vtkPolyDataMapper planeMapper
  planeMapper SetInputConnection [CompPlaneGetOutputPort]
  planeMapper ScalarVisibilityOff
vtkActor planeActor
planeActor SetMapper planeMapper
  [planeActor GetProperty] SetRepresentationToWireframe
  [planeActor GetProperty] SetColor 0 0 0

Actor的透明度。很多情况下,调整Actor的透明度(或者不透明度)是很有用的。比如,如果你想显示一个病人图像的内部器官,而器官的外面包含着皮肤,这时你可以调整皮肤的透明度,使得内部器官可见。可以使用vtkProperty::SetOpacity()方法,如下:

vtkActor popActor
  popActor SetMapper popMapper
  [popActor GetProperty] SetOpacity 0.3
  [popActor GetProperty] SetColor .9 .9 .9

要注意透明度的实现是使用渲染库里的α-Blending处理技术。这种处理技术要求以正确的顺序来渲染多边形(Polygons)。实际上,这是很难做到的,特别是当你有多个透明的Actor需要渲染时。要对多边形排序,应该把透明的Actor加到待渲染的Actor列表的最后。你也可以用vtkDepthSortPolyData这个Filter沿着视向量方向对多边形排序。关于这个Filter用法,可以参考VTK/Examples/VisualizationAlgorithm/Tcl/DepthSort.tcl这个例子。更多关于这方面的内容可以参考本章的“透明多边形几何”(Translucentpolygonal geometry)一节。

其他的属性。Actor还有其他一些重要的属性。你可以用方法VisibilityOn()/VisibilityOff()来控制Actor的可见与不可见。如果在拾取过程中,不想某个Actor被拾取,可以使用方法PickableOff()关闭拾取属性(关于拾取方面的内容,可以参考本章的“拾取”一节)。Actor有一个拾取事件(PickEvent),当它们被拾取时就会调用这个事件。另外,方法GetBounds()可以获取与坐标轴对齐的Actor的包围盒(BoundingBox)。

Level-Of-DetailActors

图形系统一个主要的问题是,交互时有时会变得很慢。为了解决这个问题,VTK使用Level-of-detail技术,在与数据交互时,以低分辨率的显示形式(Representation)来表示Actor以求达到更快的渲染速度。

在本章的“读取源对象”(Reader SourceObject)一节,我们已经使用了vtkLODActor类。基本上,最简单的方法就是用vtkLODActor实例来替代vtkActor实例。另外,你也可以控制Level-of-detail的显示形式(Representation)。vtkLODActor缺省的做法是利用原始的Mapper创建另外两个低分辨率的模型。第一个是从定义Mapper输入的点采样得到的点云。你可以控制点云里点的个数(缺省是150个点),如下所示。

vtkLODActor dotActor
  dotActor SetMapper dotMapper
  dotActor SetNumberOfCloudPoints 1000

Actor最低分辨率的模型就是一个包围盒。其他的level-of-detail也可以通过方法AddLODMapper()加入,由于复杂性问题,一般都没有必要加入。

为了控制渲染时Actor所选的level-of-detail,你可以设置渲染窗口的期望渲染帧率

vtkRenderWindow renWin
  renWin SetDesiredUpdateRate 5.0

这样,就会以每秒5帧的速率进行渲染。vtkLODActor会自动地选择合适的Level-of-detail来达到请求的渲染速率。(注意:类似vtkRenderWindowInteractor等交互器,会自动地控制期望渲染帧率(DesiredUpdate Rate),一般的做法是,当鼠标松开时,会把帧率设置得很低,而鼠标按下时,则会提高相应的帧率。这样就能保证相机运动时产生低分辨率/高帧率,而相机停止时产生高分辨率/低帧率的理想效果。如果你想了解更多关于Level-of-detail的控制方面的内容,可以参考本章的“vtkLODProp3D”一节,通过这个类,可以指定不同的Level。)

Assemblies

Actors有时也会组合在一起形成层次结构,当其中的某个Actor运动时,会影响到其他Actor的位置。例如,一个机械手臂可能由上臂、前臂、手腕和末端等部分通过关节连接起来。当上臂绕着肩关节旋转时,我们希望的是其他部分也会跟着运动。这种行为的实现就要用到Assembly,vtkAssembly是vtkActor的子类。下面的程序演示了如何使用vtkAssembly(摘自VTK/Examples/Rendering/Tcl/assembly.tcl)。

vtkSphereSource sphere
vtkPolyDataMapper sphereMapper
    sphereMapper SetInputConnection [sphere GetOutputPort]
vtkActor sphereActor
    sphereActor SetMapper sphereMapper
    sphereActor SetOrigin 2 1 3
    sphereActor RotateY 6
    sphereActor SetPosition 2.25 0 0
    [sphereActor GetProperty] SetColor 1 0 1
 
vtkCubeSource cube
vtkPolyDataMapper cubeMapper
    cubeMapper SetInputConnection [cube GetOutputPort]
vtkActor cubeActor
    cubeActor SetMapper cubeMapper
    cubeActor SetPosition 0.0 .25 0
    [cubeActor GetProperty] SetColor 0 0 1
 
vtkConeSource cone
vtkPolyDataMapper coneMapper
    coneMapper SetInputConnection [cone GetOutputPort]
vtkActor coneActor
    coneActor SetMapper coneMapper
    coneActor SetPosition 0 0 .25
    [coneActor GetProperty] SetColor 0 1 0
 
#top part of the assembly
vtkCylinderSource cylinder;
vtkPolyDataMapper cylinderMapper
    cylinderMapper SetInputConnection [cylinder GetOutputPort]
    cylinderMapper SetResolveCoincidentTopologyToPolygonOffset
vtkActor cylinderActor
    cylinderActor SetMapper cylinderMapper
    [cylinderActor GetProperty] SetColor 1 0 0
 
#Create the assembly and add the 4 parts to it. Also set the origin, position
#and orientation in space.
vtkAssembly assembly
    assembly AddPart cylinderActor
    assembly AddPart sphereActor
    assembly AddPart cubeActor
    assembly AddPart coneActor
    assembly SetOrigin 5 10 15
    assembly AddPosition 5 0 0
    assembly RotateX 15
 
#Create the Renderer, RenderWindow, and RenderWindowInteractor
#
vtkRenderer ren1
vtkRenderWindow renWin
    renWin AddRenderer ren1
vtkRenderWindowInteractor iren
    iren SetRenderWindow renWin
 
#Add the actors to the renderer, set the background and size
#
ren1 AddActor assembly
ren1 AddActor coneActor

注意是如何使用vtkAssembly里的方法AddPart()来建立层次结构的。只要不是自我嵌套,Assembly可以组合成任意层次深度的结构。vtkAssembly是vtkProp3D的子类,但没有与之相关联的Property以及Mapper。因此,vtkAssembly层次结构里的结点必须要包含关于材料的属性(如颜色等)及其他相关的几何信息。一个Actor可以用于多个Assembly(注意以上例子中的coneActor是如何作为一个单独的Actor以及作为Assembly里的一个节点的)。通过渲染器里的方法AddActor()只要加入最顶层的Assembly,而低层次的Assembly不用加入,因为它们会递归加入到渲染器中。

如果一个Actor加入到多个Assembly时(就像上面的例子),你可以会想到如何去区分不同的Actor。(关于这一点,在类似“拾取”操作时是非常重要的,因为你必须要区分一下到底哪个vtkProp对象是处于选中的姿态。)我们会在后面的“拾取”一节,详细讨论这个问题,以及介绍类vtkAssemblyPath的用法。

Volumes

类vtkVolume主要用于体绘制,这个类与vtkActor非常类似。vtkVolume从vtkProp3D继承了对Volume进行定位和定向的方法。vtkVolume内部也有一个与它本身相关联的Property对象,即vtkVolumeProperty。请参考第七章“体绘制”了解更多的关于vtkVolume的应用以及体绘制方面的内容。

vtkLODProp3D

类vtkLODProp3D与vtkLODActor(参考本章“Level-Of-DetailActors”一节)类似,也是使用不同的显示形式(Representation)来表示它本身,以求达到更佳的交互渲染速率。与vtkLODActor不同的是,vtkLODProp3D只支持体绘制和面绘制。也就是说,你只能够在体绘制应用程序中使用vtkLODProp3D类来获取理想的交互帧率。以下的例子演示了这个类的使用。

vtkLODProp 3Dlod
  set level1 [lod AddLOD volumeMappervolumeProperty2 0.0]
  set level2 [lod AddLOD volumeMappervolumeProperty 0.0]
setlevel3 [lod AddLOD probeMapper_hres probeProperty 0.0]
setlevel4 [lod AddLOD probeMapper_lres probeProperty 0.0]
setlevel5 [lod AddLOD outlineMapper outlineProperty 0.0]

基本上,根据不同的渲染复杂度,会创建不同的Mapper,并把它设置到vtkLODProp3D里。AddLOD()方法可以接收Volume或几何类型的Mapper作为参数,可选的参数包括纹理映射、属性对象等。(根据提供的信息不同,该方法会有不同的函数签名(Signatures)。)AddLOD()方法的最后一个参数是渲染的估计时间。一般情况下都设置为0,即没有针对渲染的初始估计值。该方法返回一个整型的ID值,利用这个值就可以访问对应的LOD(可以用来选择某一Level或者删除某一Level)。

4.7 使用纹理

纹理映射是生成逼真的可视化效果的强大的图形工具。二维纹理映射的基本思想是在渲染过程中,图像可以“贴”到渲染对象的表面上,因此可以创建出细节更加丰富的渲染效果。纹理映射时需要提供三类信息:待贴纹理图的面、纹理映射(在VTK里,其实就是vtkImageData类型的数据,即2D图像)以及纹理坐标(控制纹理图在面上的位置)。

下面的例子演示了如何使用纹理映射(完整的程序代码见VTK/Examples/Rendering/Tcl/TPlane.tcl)。要注意纹理映射(类vtkTexture)是与Actor相联的,而纹理坐标则是由平面来定义(纹理坐标是由类vtkPlaneSource创建的)。


图4-5平面纹理映射

#Load in the texture map.
vtkBMPReader bmpReader
  bmpReader SetFileName"$VTK_DATA_ROOT/Data/masonry.bmp"
vtkTexture atext
  atext SetInputConnection [bmpReader GetOutputPort]
  atext InterpolateOn
 
#Create a plane source and actor.
vtkPlaneSource plane
vtkPolyDataMapper  planeMapper
  planeMapper SetInputConnection [plane GetOutputPort]
vtkActor planeActor
  planeActor SetMapper planeMapper
  planeActor SetTexture atext

很多时候,纹理坐标是获取不到的,因为这些纹理坐标不能在管线中生成。如果你需要生成纹理坐标,可以参考第五章“生成纹理坐标”一节。尽管一些老的图形卡在纹理贴图时会有所限制(比如要求所贴的纹理图必须是二维的,而且每维的大小必须小于1024),但VTK支持任意尺寸的纹理图。程序运行时,VTK会检索图形系统,确定这些图形系统的性能,然后会自动地对所设置的纹理图做采样,以求达到特定图形卡的要求。

4.8 拾取

拾取操作是可视化应用程序中常见的一种功能。拾取主要是用于选择数据和Actor或者获取底层的数据值。在显示位置(以像素为坐标值)中拾取时,就会调用vtkAbstractPicker的Pick()方法。依赖于所用的拾取类不同,拾取时返回的信息也不同,最简单的是返回一个x-y-z的全局坐标值,或者是单元(cell)的ID值,点的ID值,单元参数坐标(CellParametric Coordinates),所拾取的vtkProp实例,以及Assemblypath。拾取方法的原型是:

Pick(selectionX,selectionY, selectionZ, Renderer)

注意Pick()方法需要一个渲染器作为参数。与渲染器相关联的Actor都是拾取的候选对象。另外,selectionZ通常都设置为0.0,它是与Z-buffer相关的值。(一般,Pick()这个方法都不会直接去调用它,用户使用vtkRenderWindowInteractor进行交互时,由这个类来管理拾取操作。这种情况下,用户只要选择一个拾取实例,让这个实例来控制拾取过程即可,后面的例子会演示如何使用。)

VTK支持多种不同功能的拾取类型(请参考图19-16,列出了与拾取相关的类的继承图)。类vtkAbstractPicker是所有拾取类的基类,它定义了一些公用的API,允许用户通过方法GetPickPosition()来获取拾取位置(全局坐标下)。

vtkAbstractPicker有两个直接子类。第一个是vtkWorldPointPicker,这是一种使用Z-buffer快速返回所拾取的位置的x-y-z全局坐标的类(基于硬件的),但不会返回其他的信息(比如到底拾取了哪一个vtkProp实例等)。类vtkAbstractPropPicker是从vtkAbstractPicker中直接派生的另外一个子类。它定义了可以用于拾取某个vtkProp实例的API。下面列出一些比较方便的用于获取vtkProp实例的方法。

  • GetProp() —— 返回拾取的vtkProp实例指针。如果拾取了某个对象,就返回指向该vtkProp对象的指针,否则返回NULL。
  • GetProp3D() —— 如果拾取的是vtkProp3D对象,则返回该vtkProp3D对象的指针。
  • GetActor2D() —— 如果拾取的是vtkActor2D对象,则返回该vtkActor2D对象的指针。
  • GetActor() —— 如果拾取的是vtkActor对象,则返回该vtkActor对象的指针。
  • GetVolume() —— 如果拾取的是vtkVolume对象,则返回该vtkVolume对象的指针。
  • GetAssembly() —— 如果拾取的是vtkAssembly对象,则返回该vtkAssembly对象的指针。
  • GetPropAssembly() —— 如果拾取的是vtkPropAssembly对象,则返回该vtkPropAssembly对象的指针。

使用这些方法时需要特别注意,vtkAbstractPropPicker及其子类拾取时返回的是最顶层的Assembly路径(top level of theassembly path)。因此,如果有一个顶层类型是vtkAssembly的Assembly对象,其叶结点类型是vtkActor时,使用方法GetAssembly()返回的是指向vtkAssembly对象的指针,而使用方法GetActor()返回则是空指针。如果有一个包含Assembly、Actor及其他类型的Prop的复杂场景时,最安全的方法是使用GetProp()来确定所拾取的对象,再使用GetPath()方法。

类vtkAbstractPropPicker有三个直接子类,分别是:vtkPropPicker、vtkAreaPicker及vtkPicker。vtkPropPicker使用硬件拾取的策略来确定所拾取的vtkProp实例,包括拾取点的世界坐标系下的位置坐标。vtkPropPicker通常比vtkAbstractPropPicker的其他子类的速度要快,但是它不能返回所拾取对象的详细信息。

vtkAreaPicker及其基于硬件实现的子类vtkRenderedAreaPicker同样无法确定所拾取对象的详细信息,它们的作用是选择屏幕上的对象。vtkAreaPicker及其子类与其他的拾取类不同,前者可以确定哪些是位于屏幕上矩阵区域的像素的开始位置,而不仅仅确定哪些是位于某个像素的后方。这些类都有方法AreaPick(x_min, y_min,x_max, y_max, Renderer),可以与标准的方法Pick(x,y,z,Renderer)一起使用。如果想获取更多的信息,比如确定位于某个区域后方的单元或点等信息,可以参考本节后续内容的介绍。

vtkPicker是一个基于软件实现的拾取类,具体实现是基于vtkProp对象的包围盒来拾取对象。该类在拾取时,会从相机的当前位置投射一条光线到拾取点,所投射的光线会与某个Prop3D对象的包围盒相交,当然,通过这种方式有可能会有多个的Prop3D对象被拾取到,最后返回的是所投射的光线与对象的包围盒相交最多的Prop3D。而方法GetProp3Ds()可以返回与投射光线相交的所有的Prop3D对象。vtkPicker拾取速度相对较快,但无法获取单一的拾取。

vtkPicker有两个子类,通过这两个子类可以获取所拾取对象更多详细的信息,比如,点的ID,单元的ID等。vtkPointPicker用于拾取单个点,返回其ID值和坐标值。拾取时,vtkPointPicker也是通过从相机当前位置投射一条光线至拾取点,然后将光线周围且位于容差(Tolerance)范围内的点投射至光线上,最后返回的是距离相机最近的点以及该点所对应的Actor对象。(注意:容差是用渲染窗口的对角线的长度作为分数的。)vtkPointPicker比vtkPicker拾取速度要慢,但比vtkCellPicker要快。因为引入的容差,所以vtkPointPicker可以返回单一的拾取对象。

vtkCellPicker用于拾取某个单元,并返回交点的信息,比如,交点所对应的单元ID、全局坐标以及参数化单元坐标(Parametric cellcoordinates)。与vtkPointPicker类似,vtkCellPicker拾取时也是投射一条光线至拾取点,在一定的容差范围内确定光线是否与Actor底层的几何相交,最后返回的就是沿着光线最靠近相机的单元及其对应的对象。(注意:在确定光线是否与单元相交时会使用到容差,可能需要多次实验才能获得满意的结果。)vtkCellPicker是所有拾取类中速度最慢的一个,但是所获取的信息也是最多的。通过指定容差,可以返回单一的拾取对象。

VTK定义了与拾取操作相关的几个事件。拾取操作发生之前会调用StartPickEvent事件,拾取完成后则调用EndPickEvent事件。当对象被拾取时会调用Picker类的PickEvent事件以及Actor类的PickEvent事件。注意:使用vtkWorldPointPicker类时,不会有PickEvent事件发生。

vtkAssemblyPath

在拾取包含不同类型的vtkProp对象的场景时,有必要理解类vtkAssemblyPath,特别是当场景中包含有vtkAssembly对象。vtkAssemblyPath简单地可以理解为包含vtkAssemblyNode的顺序列表,每个结点含有一个指向vtkProp对象的指针以及一个可选的vtkMatrix4x4对象。列表的顺序非常重要,列表的开始是根结点或者说是Assembly层次结构的顶层结点,列表的结尾表示Assembly层次结构的叶结点。结点的顺序会影响到与之关联的矩阵。每个矩阵是列表里结点的Prop所对应的矩阵与前一个矩阵的级联。因此,对于某个给定的vtkAssemblyNode,所关联的vtkMatrix4x4表示的是该结点的vtkProp对象的位置和方向(假设vtkProp对象初始状态是没有经过变换的)。

例子

通常,拾取是由vtkRenderWindowInteractor自动管理的(见“使用VTK交互器”一节了解更多关于交互器的内容)。比如,当按下P键时,vtkRenderWindowInteractor会调用内部的vtkPropPicker实例执行拾取操作。接着,可以通过vtkRenderWindowInteractor访问拾取器(Picker)或者其他信息。也可以给vtkRenderWindowInteractor指定一个从vtkAbstractPicker派生的拾取器。图4-6显示了对数据集拾取的结果,程序代码摘自VTK/Examples/Annotation/Tcl/annotationPick.tcl。


图4-6 带标注信息的拾取操作

vtkCellPicker picker
    picker AddObserver EndPickEvent annotatePick
 
#Create a text mapper and actor to display the results of picking.
vtkTextMapper textMapper
settprop [textMapper GetTextProperty]
    $tprop SetFontFamilyToArial
    $tprop SetFontSize 10
    $tprop BoldOn
    $tprop ShadowOn
    $tprop SetColor 1 0 0
vtkActor2D textActor
    textActor VisibilityOff
    textActor SetMapper textMapper
 
#Create the Renderer, RenderWindow, and RenderWindowInteractor
#
vtkRenderer ren1
vtkRenderWindow renWin
    renWin AddRenderer ren1
vtkRenderWindowInteractor iren
    iren SetRenderWindow renWin
    iren SetPicker picker
 
#Create a Tcl procedure to create the text for the text mapper used to
#display the results of picking.
proc annotatePick {} {
    if { [picker GetCellId] < 0 } {
       textActor VisibilityOff
 
    } else {
       set selPt [picker GetSelectionPoint]
       set x [lindex $selPt 0]
       set y [lindex $selPt 1]
       set pickPos [picker GetPickPosition]
       set xp [lindex $pickPos 0]
       set yp [lindex $pickPos 1]
       set zp [lindex $pickPos 2]
 
       textMapper SetInput "($xp, $yp,$zp)"
       textActor SetPosition $x $y
       textActor VisibilityOn
    }
 
    renWin Render
}
 
#Pick the cell at this location.
picker Pick 85 126 0 ren1

这个例子使用vtkTextMapper在屏幕上绘制拾取点的世界坐标值。(参考“文本标注”一节了解更多信息)。注意到在这个例子中,我们注册了EndPickEvent事件,拾取操作完成后,就会调用annotatePick()过程。

4.9 vtkCoordinate和坐标系统

VTK支持多种不同类型的坐标系统,类vtkCoordinate管理这些坐标系统之间的变换。支持的坐标系统有:

  • DISPLAY —— X-Y坐标值定义在渲染窗口中,以像素为单位(注意vtkRenderWindow是vtkWindow的子类)。原点在窗口的左下角(这一点对于下面的二维坐标系统都是如此)。
  • NORMALIZED DISPLAY —— 窗口的X-Y坐标取值归一化,即(0,1)。
  • VIEWPORT —— X-Y坐标值定义在视口(Viewport)或者渲染器(Renderer,vtkRenderer是vtkViewport的子类)里。
  • NORMALIZED VIEWPORT ——视口里的X-Y坐标取值归一化,即(0, 1)。
  • VIEW —— X-Y-Z坐标值(取值范围为(-1,1))定义在相机坐标系统,Z表示深度。
  • WORLD —— X-Y-Z为全局坐标值。
  • USERDEFINED —— X-Y-Z定义在用户自定义的空间里。用户必须为自定义的坐标系统提供空间变换方法。请参考类vtkCoordinate了解更多信息。

类vtkCoordinate可以用于坐标系统之间的变换,也可以用于连接各个坐标系统以形成“相对”或者“偏移”等坐标值。请参考下一节内容了解vtkCoordinate的用法。

4.10 控制vtkActor2D

vtkActor2D与vtkActor很多功能都类似,除了vtkActor2D是在层叠(overlay)平面上绘制的以及没有与之相关联的4×4的变换矩阵。与vtkActor类似,vtkActor2D涉及到一个mapper(即vtkMapper2D)和属性对象(即vtkProperty2D)。使用vtkActor2D时,比较困难的部分是如何定位它的对象。定位vtkActor2D对象时会用到类vtkCoordinate(请参考上一部分“vtkCoordinate和坐标系统”一节)。下面的例子演示了如何使用vtkCoordinate对象。

vtkActor2D bannerActor
  bannerActor SetMapper banner
  [bannerActor GetProperty] SetColor 0 1 0
  [bannerActor GetPositionCoordinate]SetCoordinateSystemToNormalizedDisplay
  [bannerActor GetPositionCoordinate] SetValue0.5 0.5

在这个例子中,访问了坐标对象以及定义了它的坐标系统,然后设置了该坐标系统下合适的坐标值。这个例子中,使用了归一化显示坐标系统(Normalized DisplayCoordinate System),因此坐标范围定义为0到1,坐标值(0.5,0.5)就设置vtkActor2D对象在渲染窗口的中间位置。vtkActor2D也提供了一个方便的接口,SetDisplayPosition()可以设置坐标系统为DISPLAY,并且利用传入的参数(以像素为单位)设置vtkActor2D对象在渲染窗口中的位置。下一节的内容将会演示如何使用这个方法。