本章的目的是通过一组选定的示例向您介绍VTK的一些功能。我们的重点将放在常用的方法和对象以及对象的组合上。我们还将介绍重要的概念和有用的应用。绝不是所有的VTK的功能涵盖;这一章的目的是给你一个可能的概览。您将需要参考在线文档或类.h文件来了解每个类可能具有的其他选项。
这里包含的大多数示例都是用Tcl编程语言实现的。它们可以很容易地在c++、Java和python中实现——语言之间的转换过程很简单。(参见第37页的“语言间转换”。)c++确实提供了一些优势,主要是对数据结构和指针的访问和操作,一些用c++语言实现的例子反映了这一点。
这里提供的每个示例都包含示例代码,通常还附带一个补充图像。我们指出源代码文件的名称(当一个存在于VTK源代码树中时),因此您将不必手动输入它。我们建议您先运行并理解示例,然后再尝试对象方法和参数。您可能还希望尝试建议的替代方法和/或类。通常,可视化工具包提供了几种方法来实现类似的结果。还要注意的是,脚本通常是根据源代码发行版中的内容进行修改的。这样做是为了简化概念或删除多余的代码。
学习像VTK这样的面向对象系统首先需要理解编程抽象,然后熟悉对象库及其方法。我们建议您查看第19页的“系统架构”,以获取有关编程抽象的信息。本章中的示例将为您提供许多VTK对象的良好概述。
4.1创建简单模型
可视化工具箱的使用通常如下:读取/生成一些数据,过滤它,呈现它,并与它交互。在本节中,我们将从查看读取和生成数据的方法开始。
获取数据有两种基本方法。数据可能存在于被读入系统的文件(或多个文件、流等)中,也可能是程序生成的(通过算法或数学表达式)。回想一下,在可视化管道中启动数据处理的对象称为源对象(参见图3-5)。生成数据的对象称为过程(源)对象,读取数据的对象称为读取器(源)对象。
程序源对象
我们将从渲染一个简单的圆柱体开始。下面显示的示例代码(VTK/Examples/
Rendering/Tcl/Cylinder.tcl)演示了可视化管道和呈现引擎中的许多基本概念。运行结果如图4-1所示。
我们通过调用Tcl命令来加载VTK包(包需要VTK)并创建一个GUI解释器(包需要VTK交互)来开始脚本,该解释器允许您在运行时键入命令。此外,我们还加载了vtktesting,它定义了一组颜色,其中之一(番茄)将在稍后的脚本中使用。
package require vtk
package require vtkinteraction
package require vtktesting
然后我们创建一个过程源对象:vtk圆柱体源。这个源代码创建了一个圆柱体的多边形表示。圆柱体的输出通过SetInputConnection()方法设置为vtkPolyDataMapper的输入。我们创建一个参与者(被渲染的对象),它引用映射器作为它的定义几何。注意在Tcl中构造对象的方式:我们使用类名后面跟着所需的实例名。
vtkCylinderSource cylinder
cylinder SetResolution 8
vtkPolyDataMapper cylinderMapper
cylinderMapper SetInputConnection [cylinder GetOutputPort]
vtkActor cylinderActor
cylinderActor SetMapper cylinderMapper
eval [cylinderActor GetProperty] SetColor $tomato
cylinderActor RotateX 30.0
cylinderActor RotateY -45.0
作为c++实现与Tcl(或其他解释语言)实现的相似程度的提醒,下面显示了用c++实现的相同代码,可以在VTK/Examples/中找到
呈现/ Cxx / Cylinder.cxx。
vtkCylinderSource *cylinder = vtkCylinderSource::New();
cylinder->SetResolution(8);
vtkPolyDataMapper *cylinderMapper = vtkPolyDataMapper::New();
cylinderMapper->SetInputConnection(cylinder->GetOutputPort());
vtkActor *cylinderActor = vtkActor::New();
cylinderActor->SetMapper(cylinderMapper);
cylinderActor->GetProperty()->SetColor(1.0000, 0.3882, 0.2784);
cylinderActor->RotateX(30.0);
cylinderActor->RotateY(-45.0);
回想一下,源对象启动可视化管道,而映射器对象(或包含映射功能的道具对象)终止管道,因此在本例中,我们有一个由两种算法组成的管道(即源和映射器)。VTK管道使用延迟评估方案,因此即使管道连接,也没有生成或处理数据(因为我们还没有请求数据)。
接下来,我们创建图形对象,这将允许我们渲染演员。vtkRenderer实例ren1为渲染窗口renWin的一个视口协调渲染过程。渲染窗口交互器是一个3D小部件,允许我们操纵相机。
#Create the graphics structure
vtkRenderer ren1
vtkRenderWindow renWin
renWin AddRenderer ren1
vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
注意,我们已经通过AddRenderer()方法将渲染器与渲染窗口关联起来。我们还必须使用AddActor()方法将actor与渲染器关联起来。
# Add the actors to the renderer, set the background and size
ren1 AddActor cylinderActor
ren1 SetBackground 0.1 0.2 0.4
renWin SetSize 200 200
SetBackground()方法使用(0,1)之间的RGB(红、绿、蓝)值指定渲染窗口的背景颜色,SetSize()方法以像素为单位指定窗口大小。最后,我们通过将GUI交互器与呈现窗口交互器的用户定义方法相关联来结束这个示例。(当鼠标焦点位于呈现窗口中时,通过按u键调用用户定义的方法。参见第45页的“使用VTK交互器”。(参见第29页的“用户方法、观察者和命令”)Initialize()方法开始事件循环,而Tcl/Tk 命令wm退出。确保在应用程序启动时解释器小部件. vtkinteraction不可见。
# Associate the “u” keypress with a UserEvent and start the event loop
iren AddObserver UserEvent {wm deiconify .vtkInteract}
iren Initialize
# suppress the tk window
wm withdraw .
当脚本运行时,可视化管道将执行,因为呈现引擎将请求数据。(窗口暴露事件将强制渲染窗口渲染自己。)只有在管道执行之后,过滤器才会对输入数据进行更新。如果你愿意,你可以通过调用renWin Render手动执行管道。
在运行这个示例之后,您可以尝试一些事情。首先,通过在呈现窗口中滑动鼠标来使用交互器。接下来,通过调用圆柱体SetResolution 12来更改圆柱体对象的分辨率。您可以编辑示例文件并重新执行它,或者在呈现窗口中按u键以调出解释器GUI并在其中键入命令。请记住,如果您使用的是Tcl交互器弹出窗口,那么您所做的更改只有在请求数据之后才可见,因此可以使用renWin Render命令或在呈现窗口中单击鼠标来跟踪更改。
阅读器源对象
这个示例与前面的示例类似,不同之处在于我们读取数据文件而不是程序地生成数据。读取一个使用二进制STL数据格式表示多边形数据的立体光刻文件(后缀. STL)。(参考
图4-2和Tcl脚本“VTK/Examples/Rendering/”
Tcl / CADPart.tcl)。
vtkSTLReader part
part SetFileName \
$VTK_DATA_ROOT/Data/42400-IDGH.stl
vtkPolyDataMapper partMapper
partMapper SetInputConnection \
[part GetOutputPort]
vtkLODActor partActor
partActor SetMapper partMapper
注意对vtkLODActor的使用。此actor更改其表示以保持交互性能。它的默认行为是创建一个点云和线框,边界框轮廓来表示中间和低级表示。(更多信息请参见第55页的“Level-Of-Detail Actors”。)本例中使用的模型非常小,在今天的大多数计算机上,您只能看到高级表示(模型的完整几何形状)。
许多读取器无法感知输入文件的更改和重新执行。例如:“42400-IDGH. xml”文件。STL更改后,管道将不会重新执行。您可以通过调用它们的Modified()方法来手动修改对象。这将导致过滤器及其下游的所有过滤器重新执行。
可视化工具箱具有有限的内置建模功能。如果您想使用VTK来编辑和操作复杂的模型(例如,由实体建模器或建模工具创建的模型),您通常会使用阅读器(参见第239页的“阅读器”)来连接数据。(另一种选择是导入器,它用于摄取整个场景。更多信息请参见第245页的“进口商”。)
4.2使用VTK交互器
一旦您可视化了数据,您通常希望与它进行交互。可视化工具包提供了几种方法来实现这一点。第一种方法是使用内置类vtkRenderWindowInteractor。第二种方法是通过指定事件绑定来创建您自己的交互器。不要忘记,如果您使用的是解释性语言,您可以在运行时键入命令。您可能还希望参阅第59页的“选择”,了解如何从屏幕中选择数据。(注意:开发人员也可以选择一个窗口系统。参见第421页的“与窗口系统集成”。)
vtkRenderWindowInteractor
与数据交互的最简单方法是实例化vtkRenderWindowInteractor。该类响应一组预定义的事件和操作,并提供一种覆盖默认操作的方法。vtkRenderWindowInteractor允许你控制相机和演员,并提供两种交互风格:位置敏感(即操纵杆模式)和运动敏感(即轨迹球模式)。(更多关于交互器样式在下一节)
vtkRenderWindowInteractor响应渲染窗口中的以下事件。(请记住,多个渲染器可以在一个渲染窗口中绘制,并且渲染器可以在渲染窗口内绘制视图。
•按键j /按键t -在操纵杆(位置敏感)和轨迹球(运动敏感)风格之间切换。在操纵杆风格中,只要按下鼠标按钮,运动就会持续发生。在轨迹球样式中,当按下鼠标按钮并且鼠标指针移动时发生运动。
•按键c /按键a -切换相机和演员(对象)模式。在相机模式下,鼠标事件会影响相机的位置和焦点。在对象模式下,鼠标事件影响鼠标指针下的参与者。
•按钮1 -围绕焦点旋转相机(如果相机模式)或围绕原点旋转演员(如果演员模式)。旋转的方向是从渲染器的视口中心到鼠标位置的方向。在操纵杆模式下,旋转的大小由鼠标和渲染窗口中心之间的距离决定。
•按钮2 -平移相机(如果相机模式)或翻译演员(如果对象模式)。在操纵杆模式下,平移或平移的方向是从视口中心到鼠标位置。在轨迹球模式下,运动方向是鼠标移动的方向。(注意:对于2键鼠标,平移被定义为<Shift>-按钮1)
•按钮3 -缩放相机(如果相机模式),或缩放演员(如果对象模式)。如果鼠标位置在视口的上半部分,则放大/增加比例;如果鼠标位置在下半部分,则缩放/缩小刻度。在操纵杆模式下,缩放的大小由鼠标指针到窗口水平中心线的距离来控制。
•按键3 -切换渲染窗口进入和退出立体声模式。默认情况下,会创建红蓝立体声对。部分系统支持水晶眼液晶立体眼镜;你必须在渲染窗口上调用SetStereoTypeToCrystalEyes()。
•按e/q -退出或退出应用程序。
•按f键-飞到光标下的点。这将设置焦点并允许围绕该点进行旋转。
•按p键-执行拾取操作。渲染窗口交互器有一个内部实例的vtkPropPicker,它用来挑选。有关挑选的更多信息,请参阅第59页的“挑选”。
•按r键-沿当前视图方向重置相机视图。将演员居中并移动摄像机,以便所有演员都可见。
•按键s -修改所有角色的表示,使它们是表面。
•按u键-调用用户定义的方法。通常,这个按键会弹出一个交互器,您可以在其中键入命令。
•按w键-修改所有演员的表示,使他们是线框
默认的交互样式是位置敏感的(例如,操纵杆样式)——也就是说,只要按下鼠标按钮,它就操纵摄像机或演员并持续呈现。如果您不喜欢默认行为,您可以更改它或编写自己的行为。(见“vtkRenderWindow交互风格”在421页的信息关于编写自己的风格。)
vtkRenderWindowInteractor还有其他有用的功能。调用LightFollowCameraOn()(默认行为)会导致灯光位置和焦点与摄像机位置和焦点同步(即创建“头灯”)。当然,这可以通过LightFollowCameraOff()关闭。可以使用“AddObserver(UserEvent)”方法添加响应“u”键的回调。也可以设置几个与拾取相关的方法。AddObserver(StartPickEvent)定义了一个在拾取之前调用的方法,AddObserver(EndPickEvent)定义了一个在拾取完成之后调用的方法。(有关定义用户方法的更多信息,请参阅第29页的“用户方法、观察者和命令”。)您还可以通过SetPicker()方法指定要使用的vtkAbstractPicker子类的实例。(参见第59页的“挑选”。)
如果你使用的是基于所需交互性调整渲染质量的道具,你可能希望通过交互器中的SetDesiredUpdateRate()来设置所需的帧率。通常,这是自动处理的。(当鼠标按钮被激活时,期望的更新率增加;当鼠标按钮被释放时,所需的更新速率将被设置下来。)请参阅第55页的“细节级演员”,第57页的“vtkLODProp3D”,以及第139页的“体渲染”章节,了解有关道具及其相关映射器如何调整渲染样式以实现所需帧率的进一步信息。
我们之前已经看到了如何使用vtkRenderWindowInteractor,这里是一个重述。
vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
iren AddObserver UserEvent {wm deiconify .vtkInteract}
Interactor Styles
在VTK中有两种截然不同的方式来控制交互风格。第一种是使用vtkInteractorStyle的子类,可以是系统提供的,也可以是您自己编写的。第二个方法是添加观察者来观察vtkRenderWindowInteractor上的事件,并定义你自己的一组回调(或命令)来实现这种风格。(注意:3D小部件是另一种更复杂的与场景中数据交互的方式。更多信息请参见第72页的“3D Widgets”。
类vtkRenderWindowInteractor可以支持不同的交互样式。当你在交互器中输入“t”或“j”时(参见前一节),你正在轨迹球和操纵杆之间切换交互风格。它的工作方式是vtkRenderWindowInteractor将它接收到的特定于窗口系统的事件(例如,鼠标按钮按下,鼠标移动,键盘事件)转换为VTK事件,如MouseMoveEvent, StartEvent等。(相关信息请参见第29页的“用户方法、观察者和命令”。)然后,不同的样式观察特定的事件并执行适合该事件的操作。要设置样式,使用vtkRenderWindowInteractor::SetInteractorStyle()方法。例如:
vtkInteractorStyleFlight flightStyle
vtkRenderWindowInteractor iren
iren SetInteractorStyle flightStyle
(注意:当vtkRenderWindowInteractor被实例化时,一个特定于窗口系统的渲染窗口交互器实际上被实例化了。例如,在Unix系统上,类vtkxrenderwindowwinteractor实际上是作为vtkrenderwindowwinteractor的实例创建和返回的。在Windows上,类vtkwin32renderwindowwinteractor被实例化。)
添加vtkRenderWindowInteractor观察者。虽然在VTK中有各种各样的交互器样式,但您可能更喜欢创建自己的自定义样式来满足特定应用程序的需要。在c++中,自然的方法是创建vtkInteractorStyle的子类。(参见第421页的“vtkRenderWindow交互样式”。)然而,在解释性语言(如Tcl、Python或Java)中,这很难做到。对于解释型语言,最简单的方法是使用观察者来定义特定的交互绑定。(参见第29页的“用户方法、观察者和命令”。)这些绑定可以用VTK支持的任何语言进行管理,包括c++、Tcl、Python和Java。这样的一个例子可以在Tcl代码VTK/Examples/GUI/Tcl/CustomInteraction中找到。它为一个简单的tcl应用程序定义绑定。这里有一段节选,让你了解发生了什么。
vtkRenderWindowInteractor iren
iren SetInteractorStyle ""
iren SetRenderWindow renWin
# Add the observers to watch for particular events. These invoke
# Tcl procedures.
set Rotating 0
set Panning 0
set Zooming 0
iren AddObserver LeftButtonPressEvent {global Rotating; set Rotating 1}
iren AddObserver LeftButtonReleaseEvent \
{global Rotating; set Rotating 0}
iren AddObserver MiddleButtonPressEvent {global Panning; set Panning 1}
iren AddObserver MiddleButtonReleaseEvent \
{global Panning; set Panning 0}
iren AddObserver RightButtonPressEvent {global Zooming; set Zooming 1}
iren AddObserver RightButtonReleaseEvent {global Zooming; set Zooming 0}
iren AddObserver MouseMoveEvent MouseMove
iren AddObserver KeyPressEvent Keypress
proc MouseMove {} {
...
set xypos [iren GetEventPosition]
set x [lindex $xypos 0]
set y [lindex $xypos 1]
...
}
proc Keypress {} {
set key [iren GetKeySym]
if { $key == "e" } {
vtkCommand DeleteAllObjects
exit
}
...
}
注意,本例中的关键步骤是通过调用禁用默认交互样式
SetInteractionStyle(" ")。然后添加观察器来监视与适当Tcl过程绑定的特定事件。
这个示例是一种从Tcl脚本添加绑定的简单方法。如果您想创建一个完整的
使用Tcl/Tk的GUI,然后使用vtkTkRenderWidget,并参考第433页的“Tcl/Tk”了解更多细节。
4.3过滤数据
前面的示例管道由源对象和映射器对象组成;管道没有过滤器。在本节中,我们将展示如何将过滤器添加到管道中。
使用SetInputConnection()和GetOutputPort()方法连接过滤器。例如,我们可以修改第44页“Reader Source Object”中的脚本,以缩小组成模型的多边形。脚本如下所示。(只显示管道和其他相关对象。)完整的脚本可以在
VTK/Examples/Rendering/Tcl/FilterCADPart.tcl.
vtkSTLReader part
part SetFileName \
“$VTK_DATA_ROOT/Data/42400-IDGH.stl”
vtkShrinkPolyData shrink
shrink SetInputConnection [part GetOutputPort]
shrink SetShrinkFactor 0.85
vtkPolyDataMapper partMapper
partMapper SetInputConnection \
[shrink GetOutputPort]
vtkLODActor partActor
partActor SetMapper partMapper
正如您所看到的,创建可视化管道非常简单。您需要为手头的任务选择正确的类,确保所连接过滤器的输入和输出类型是兼容的,并设置必要的实例变量。(当源或过滤器的输出数据集类型可以作为管道中下一个过滤器或映射器的输入时,输入和输出类型是兼容的。输出数据集类型必须与输入数据集类型完全匹配,或者是它的子类。)可视化管道可以包含循环,但不能直接连接过滤器的输出
4.4摄像机控制
您可能已经注意到,在前面的脚本中没有实例化摄像机或灯光。如果你熟悉3D图形,你知道灯光和相机是必要的渲染对象。在VTK中,如果灯光和相机没有直接创建,渲染器会自动实例化它们。
实例化相机
下面的Tcl脚本展示了如何实例化并将相机与渲染器关联起来
vtkCamera cam1
cam1 SetClippingRange 0.0475572 2.37786
cam1 SetFocalPoint 0.052665 -0.129454 -0.0573973
cam1 SetPosition 0.327637 -0.116299 -0.256418
cam1 ComputeViewPlaneNormal
cam1 SetViewUp -0.0225386 0.999137 0.034901
ren1 SetActiveCamera cam1
另外,如果您希望访问一个已经存在的摄像机(例如,一个由渲染器自动实例化的摄像机),您可以在Tcl中使用
set cam1 [ren1 GetActiveCamera]
$cam1 Zoom 1.4
让我们回顾一下我们刚刚介绍的一些相机方法。SetClippingPlane()有两个参数,到近处和远处裁剪平面沿视图平面法线的距离。回想一下,所有不在这些平面之间的图形原语在渲染期间都会被消除,因此您需要确保希望看到的对象位于剪切平面之间。FocalPoint和Position(世界坐标)实例变量控制摄像机的方向和位置。computeviewplannormal()基于当前位置和焦点重置视图平面的法线。(如果视图平面法线与视图平面不垂直,可以得到一些交互图4-3过滤数据。在这里,我们使用一个过滤器来收缩多边形形成模型向他们的质心。测试剪切效应的基础。)设置ViewUp控制相机的“向上”方向。最后,Zoom()方法通过改变视角(即SetViewAngle())来放大对象。你也可以使用Dolly()方法来移动摄像机沿着视图平面的法线来放大或缩小可见的演员。
简单操作方法
上面描述的方法并不总是控制相机最方便的方法。如果相机“看着”你想要的点(也就是说,焦点设置好了),你可以使用Azimuth()和Elevation()方法来围绕焦点移动相机。
cam1 Azimuth 150
cam1 Elevation 60
这些方法通过给定的角度(以度为单位)在经度方向(方位角)和纬度方向(仰角)上移动摄像机,使摄像机在以焦点为中心的球坐标系中移动。这些方法不修改视向上矢量,并依赖于视向上矢量保持不变。注意,在北极和南极有奇点——视向上向量与视平面法线平行。为了避免这种情况,您可以使用OrthogonalizeViewUp()强制视图向量与视图向量正交。然而,这改变了相机坐标系统,所以如果你在一个具有自然地平线或视向上向量(如地形)的物体周围飞行,相机操作就不再是关于数据的自然操作。
控制视图方向
相机的一个常见功能是从一个特定的方向生成一个视图。您可以通过调用SetFocalPoint()、SetPosition()和computeviewplannormal(),然后调用
ResetCamera()在与相机相关的渲染器上。
vtkCamera cam1
cam1 SetFocalPoint 0 0 0
cam1 SetPosition 1 1 1
cam1 ComputeViewPlaneNormal
cam1 SetViewUp 1 0 0
cam1 OrthogonalizeViewUp
ren1 SetActiveCamera cam1
ren1 ResetCamera
初始方向(视图向量或视图平面法线)是从相机的焦点和位置计算出来的,它与computeviewplannormal()一起定义了初始视图向量。
可选地,您可以指定一个初始的视向上向量,并将其与视图向量正交。然后ResetCamera()方法沿着视图向量移动摄像机,这样渲染器的演员对摄像机都是可见的。
透视与正交视图
在前面的例子中,我们假设相机是透视相机,在渲染过程中,视角控制演员在视图平面上的投影。透视投影虽然能产生更自然的图像,但在某些应用中会引入失真。正交(或平行)投影是另一种投影方法。在正交投影中,视图光线是平行的,物体渲染时没有距离效果。要设置相机使用正交投影,使用vtkCamera::ParallelProjectionOn()方法。在平行投影模式下,相机视角不再控制缩放。相反,使用SetParallelScale()方法来控制角色的放大。
保存/恢复摄像机状态
应用程序的另一个常见需求是保存和恢复相机状态(即恢复视图)的能力。为了保存相机状态,你需要保存(至少)裁剪范围、焦点和位置以及视向上矢量。您还需要计算视图平面法线(如第49页“实例化相机”中的示例所示)。然后,要恢复相机状态,只需用保存的信息实例化一个相机,并将其分配给适当的渲染器(即SetActiveCamera())。
在某些情况下,您可能需要存储其他信息。例如,如果设置了相机视角(或平行比例),则需要保存此内容。或者,如果您使用相机进行立体观看,则需要EyeAngle和stereo标志。
4.5控制灯光
灯光比照相机更容易控制。最常用的方法是SetPosition(), SetFocalPoint()和SetColor()。光的位置和焦点控制着光的指向。光的颜色表示为RGB矢量。此外,可以通过SwitchOn()和SwitchOff()方法打开和关闭灯,并且可以使用SetIntensity()方法设置灯的亮度。
默认情况下,vtkLight的实例是方向灯。也就是说,位置和焦点定义了一个平行于光线传播的矢量,并且假定光源位于无穷远点。这意味着,如果焦点和位置转换相同,物体上的照明不会改变。灯光与渲染器的关联如下。
vtkLight light
light SetColor 1 0 0
light SetFocalPoint [cam1 GetFocalPoint]
light SetPosition [cam1 GetPosition]
ren1 AddLight light
在这里,我们创建了一个红色的前灯:一个位于相机(cam1)位置并指向相机焦点的灯。这是一个有用的技巧,并且被交互式渲染器用于在摄像机移动时定位光线。(参见第45页“使用VTK互动器”。)
定点光源
通过使用PositionalOn()方法可以创建位置(即聚光灯)。此方法与SetConeAngle()方法一起使用,以控制点的扩散。180度的基础表示没有聚光灯效果(即没有截断的光锥),只有位置效果。
4.6控制3D道具
在VTK中,要在渲染窗口中绘制的对象通常被称为“道具”。(prop这个词来自戏剧的词汇——道具是出现在舞台上的东西。)有几种不同类型的道具,包括vtkProp3D和vtkActor。vtkProp3D是一个抽象的超类,为那些类型的道具存在于3D空间。类vtkActor是vtkProp3D的一种类型,其几何形状由分析原语(如多边形和直线)定义。
指定vtkProp3D的位置
我们已经看到了如何使用相机在物体周围移动;或者,我们也可以保持摄像机稳定,变换道具。以下方法可用于定义vtkProp3D(及其子类)的位置。
•SetPosition(x,y,z) -指定vtkProp3D在世界坐标中的位置。
•AddPosition(deltaX,deltaY,deltaZ) -沿着x, y和z轴将道具按指定的量进行平移。
•RotateX(theta), RotateY(theta), RotateZ(theta) -分别围绕x, y, z坐标轴旋转道具theta度。
•SetOrientation(x,y,z)—通过围绕z轴旋转,然后围绕x轴旋转,然后围绕y轴旋转来设置道具的方向。
•AddOrientation(a1,a2,a3) -添加到道具的当前方向。
•RotateWXYZ(theta,x,y,z) -围绕定义的x-y-z向量旋转道具theta度。
•SetScale(sx,sy,sz) -在x, y, z轴坐标方向上缩放道具。
•SetOrigin(x,y,z) -指定道具的原点。原点是发生旋转和缩放的点。
这些方法以复杂的方式一起工作以控制生成的变换矩阵。要记住的最重要的事情是,上面列出的操作是按特定顺序应用的,应用顺序会极大地影响最终的参与者位置。在VTK中应用这些转换的顺序如下:
1.移到原点
2.规模
3.旋转Y
4.旋转X
5.旋转z
6.从原点
7.移动。
向原点的移动和从原点的移动分别是原点值的负平移和正平移。净平移由vtkProp3D的Position值给出。这些变换中最令人困惑的部分是旋转。例如,执行x旋转后再执行y旋转与反向操作的结果非常不同(见图4-4)。有关角色转换的更多信息,请参阅Visualization Toolkit文本。在下一节中,我们将描述各种vtkProp3D -其中VTK中使用最广泛的类称为vtkActor。稍后(参见第62页的“控制vtkActor2D”),我们将检查2D道具(即vtkActor2D),它们倾向于用于注释和其他2D操作。
Actors
actor是vtkProp3D最常见的类型。像vtkProp3D的其他具体子类一样,vtkActor用于组渲染属性,如表面属性(例如,环境,漫反射和高光颜色),表示(例如,表面或线框),纹理映射和/或几何定义(映射器)。
定义几何。正如我们在前面的例子中看到的,角色的几何形状是用SetMapper()方法指定的:
vtkPolyDataMapper mapper
mapper SetInputConnection [aFilter GetOutputPort]
vtkActor anActor
anActor SetMapper mapper
在这种情况下,mapper的类型是vtkPolyDataMapper,它使用分析原语(如点、线、多边形和三角形带)呈现几何形状。映射器终止可视化管道,并充当可视化子系统和图形子系统之间的桥梁。
演员的属性。actor引用vtkProperty的实例,该实例反过来控制actor的外观。可能最常用的属性是角色颜色,我们将在下一节中描述。属性的其他重要特征是它的表示(点,线框或表面),它的着色方法(平面或Gouraud阴影),演员的不透明度(相对透明度),以及环境,漫反射和高光颜色及其相关系数。下面的脚本显示了如何设置其中一些实例变量。
vtkActor anActor
anActor SetMapper mapper
[anActor GetProperty] SetOpacity 0.25
[anActor GetProperty] SetAmbient 0.5
[anActor GetProperty] SetDiffuse 0.6
[anActor GetProperty] SetSpecular 1.0
[anActor GetProperty] SetSpecularPower 10.0
注意我们是如何通过GetProperty()方法解引用参与者的属性的。或者,我们可以创建一个属性并将其分配给参与者。
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
后一种方法的优点是,我们可以通过为每个参与者分配相同的属性来控制多个参与者的属性。
演员的颜色。颜色可能是演员身上最重要的属性。控制此属性的最简单过程是SetColor()方法,该方法用于设置角色的红、绿、蓝(RGB)值。取值范围为0 ~ 1。
[anActor GetProperty] SetColor 0.1 0.2 0.4
或者,您可以分别设置环境色、漫反射色和高光色
vtkActor anActor
anActor SetMapper mapper
[anActor GetProperty] SetAmbientColor .1 .1 .1
[anActor GetProperty] SetDiffuseColor .1 .2 .4
[anActor GetProperty] SetSpecularColor 1 1 1
在这个例子中,我们将环境色设置为深灰色,漫反射色设置为蓝色,高光色设置为白色。(注意:SetColor()方法将环境色、漫反射色和高光色设置为指定的颜色。)
重要提示:只有当actor的映射器没有可用的标量数据时,actor属性中的颜色设置才会生效。默认情况下,映射器的输入标量数据为角色上色,而角色的颜色被忽略。要忽略标量数据,可以使用ScalarVisibilityOff()方法,如下面的Tcl脚本所示。
vtkPolyDataMapper planeMapper
planeMapper SetInputConnection [CompPlane GetOutputPort]
planeMapper ScalarVisibilityOff
vtkActor planeActor
planeActor SetMapper planeMapper
[planeActor GetProperty] SetRepresentationToWireframe
[planeActor GetProperty] SetColor 0 0 0
演员的透明度。很多时候,调整角色的透明度(或不透明度)是有用的。例如,如果您希望显示被患者皮肤包围的内部器官,则调整皮肤的透明度可以使用户看到与皮肤相关的器官。使用vtkProperty::SetOpacity()方法如下所示。
vtkActor popActor
popActor SetMapper popMapper
[popActor GetProperty] SetOpacity 0.3
[popActor GetProperty] SetColor .9 .9 .9
请注意,透明度是在渲染库中使用混合过程实现的。这个过程要求多边形以正确的顺序呈现。在实践中,这是很难实现的,特别是如果您有多个透明的参与者。为了排列多边形,你应该在渲染器的角色列表的末尾添加透明的角色(也就是说,最后添加它们)。另外,你可以使用过滤器vtkDepthSortPolyData沿着视图向量对多边形进行排序。请参见VTK/Examples/ VisualizationAlgorithms/Tcl/DepthSort。TCL为使用此过滤器的示例。有关此主题的更多信息,请参阅第79页的“半透明多边形几何”。
演员还有其他几个重要特征。您可以使用visbilityon()和visbilityoff()方法控制角色是否可见。如果您不想在挑选操作期间挑选角色,请使用PickableOff()方法。(请参阅第59页的“采摘”,了解更多关于采摘的信息。)参与者也有一个拾取事件,当他们被拾取时可以调用该事件。另外,您可以使用GetBounds()方法获得actor的轴对齐的边界框。
详细级别的Actors
图形系统的一个主要问题是,对于交互使用来说,它们通常太慢了。为了解决这个问题,VTK使用细节级角色以低分辨率表示为代价来实现可接受的渲染性能。
在第44页的“阅读器源对象”中,我们看到了如何使用vtkLODActor。基本上,使用vtkLODActor的最简单方法是用vtkLODActor的实例替换vtkActor的实例。此外,您还可以控制细节级别的表示。vtkLODActor的默认行为是从原始映射器中创建两个额外的低分辨率模型。第一个是点云,从定义映射器输入的点中采样。您可以像下面这样控制云中的点的数量。(默认值是150分。)
vtkLODActor dotActor
dotActor SetMapper dotMapper
dotActor SetNumberOfCloudPoints 1000
最低分辨率的模型是参与者的边界框。可以使用AddLODMapper()方法添加其他级别的详细信息。它们不必按照复杂程度的顺序添加。
为了控制演员在渲染过程中选择的细节级别,你可以在渲染窗口中设置所需的帧速率:
vtkRenderWindow renWin
renWin SetDesiredUpdateRate 5.0
换算成每秒5帧。vtkLODActor将自动选择适当的细节级别以产生所请求的速率。(注意:像vtkRenderWindowInteractor这样的交互器小部件会自动控制所需的更新速率。它们通常在释放鼠标按钮时将帧率设置得非常低,而在按下鼠标按钮时则增加帧率。这给相机运动的低分辨率/高帧率模型带来了令人愉悦的效果,当相机停止时,高分辨率/低帧率模型。如果您想要更多地控制细节级别,请参阅第57页的“vtkLODProp3D”。vtkLODProp3D允许您具体设置每个级别。)
程序集
参与者通常在层次程序集中分组,以便一个参与者的运动影响其他参与者的位置。例如,机器人手臂可能由上臂、前臂、手腕和末端执行器组成,所有这些都通过关节连接起来。当上臂绕肩关节旋转时,我们期望手臂的其余部分也随之移动。此行为是使用程序集实现的,其类型为
(子类)vtkActor。下面的脚本显示了它是如何完成的(来自VTK/Examples/)
呈现/ Tcl / assembly.tcl)。
# create four parts: a top level assembly and three primitives
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
vtkCylinderSource cylinder
vtkPolyDataMapper cylinderMapper
CylinderMapper SetInputConnection [cylinder GetOutputPort]
vtkActor cylinderActor
cylinderActor SetMapper cylinderMapper
[cylinderActor GetProperty] SetColor 1 0 0
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
# Add the actors to the renderer, set the background and size
ren1 AddActor assembly
ren1 AddActor coneActor
注意我们如何使用vtkAssembly的AddPart()方法来构建层次结构。只要不存在任何自引用循环,程序集可以任意深度嵌套。请注意,vtkAssembly是vtkProp3D的子类,因此它没有属性或相关映射器的概念。因此,vtkAssembly层次结构的叶节点必须携带有关材料属性(颜色等)和任何相关几何形状的信息。actor也可以被多个程序集使用(注意如何在程序集中使用coneActor并将其用作actor)。此外,渲染器的AddActor()方法用于将程序集的顶层与渲染器关联起来;不需要将程序集层次结构中较低级别的那些角色添加到呈现器中,因为它们是递归呈现的。
您可能想知道,如果参与者在多个程序集中使用,或者如上例中与程序集中混合使用,则如何区分参与者相对于其上下文的使用。(这在诸如拾取之类的活动中尤其重要,用户可能需要知道哪个vtkProp被拾取以及它被拾取的上下文。)我们通过引入类vtkAssemblyPath来解决这个问题,该类是vtkProps的有序列表,带有相关的转换矩阵(如果有的话),详细内容见第59页的“拾取”。
Volumes
类vtkVolume用于体渲染。它类似于类vtkActor。像vtkActor一样,vtkVolume继承了vtkProp3D的方法来定位和定向卷。vtkVolume有一个关联的属性对象,在本例中是vtkVolumeProperty。请参阅第116页的“体绘制”,了解vtkVolume的使用和体绘制的详细说明。
vtkLODProp3D
vtkLODProp3D类类似于vtkLODActor(参见第55页的“Level-Of-Detail Actors”),因为它使用不同的自身表示来实现交互式帧率。与vtkLODActor不同,vtkLODProp3D支持体渲染和表面渲染。这意味着您可以在体渲染应用程序中使用vtkLODProp3D来实现交互式帧率。下面的示例展示了如何使用该类。
vtkLODProp3D lod
set level1 [lod AddLOD volumeMapper volumeProperty2 0.0]
set level2 [lod AddLOD volumeMapper volumeProperty 0.0]
set level3 [lod AddLOD probeMapper_hres probeProperty 0.0]
set level4 [lod AddLOD probeMapper_lres probeProperty 0.0]
set level5 [lod AddLOD outlineMapper outlineProperty 0.0]
基本上,您可以创建不同的映射器,每个映射器对应不同的呈现复杂性,并将映射器添加到vtkLODProp3D中。AddLOD()方法可以接受体积或几何映射器,也可以接受纹理映射和/或属性对象。(根据您希望提供的信息,此方法有不同的签名。)字段中的最后一个值是渲染的估计时间。通常,您将其设置为零,以表明没有初始估计。该方法返回一个整数id,可用于访问适当的LOD(即选择一个级别或删除它)。
vtkLODProp3D测量渲染每个LOD并对它们进行适当排序所需的时间。然后,根据渲染窗口所需的更新速率,vtkLODProp3D选择适当的级别来渲染。请参阅“使用vtkLODProp3D来提高性能”在135页了解更多信息。
4.7使用纹理
纹理映射是一个强大的图形工具,用于创建逼真和引人注目的可视化。2D纹理映射背后的基本思想是,在渲染过程中,图像可以“粘贴”到表面上,从而创建更丰富、更详细的图像。纹理映射需要三条信息:纹理应用到的表面;纹理贴图,在VTK中是vtkImageData数据集(即2D图像);以及纹理坐标,控制纹理在表面上的定位。
下面的示例(图4-5)演示了纹理映射的使用(参见VTK/Examples/Rendering/Tcl/)
TPlane.tcl)。注意,纹理贴图(vtkTexture类)与角色相关联,纹理坐标来自平面(纹理坐标是在创建平面时由vtkPlaneSource生成)。
# 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
通常情况下,纹理坐标是不可用的,因为它们不是在管道中生成的。如果需要生成纹理坐标,请参考第111页的“生成纹理坐标”。虽然一些旧的显卡对纹理的尺寸有限制(例如,它们必须是2的幂并且在一边小于1024),VTK允许任意大小的纹理。在运行时,VTK将查询图形系统,以确定其能力,并将自动重新采样你的纹理,以满足卡的要求。
4.8选择
挑选是一项常见的可视化任务。选择用于选择数据和参与者或查询底层数据值。当选择显示位置(即像素坐标)并用于调用vtkAbstractPicker的pick()方法时,就会进行拾取。根据拾取类的类型,从拾取返回的信息可能像x-y-z全局坐标一样简单,也可能包括单元格id、点id、单元格参数坐标、被拾取的vtkProp实例和/或组装路径。pick方法的语法如下所示。
Pick(selectionX, selectionY, selectionZ, Renderer)
注意,pick方法需要一个渲染器。与渲染器相关联的角色是选择的候选对象。此外,selectionZ通常设置为0——它与z缓冲区的深度有关。(在典型用法中,不直接调用此方法。相反,用户与管理拾取的类vtkRenderWindowInteractor交互。在这种情况下,用户将通过分配一个拾取类的实例给vtkRenderWindowInteractor来控制拾取过程,我们将在后面的例子中看到。)
可视化工具箱支持多种不同功能和性能的选择器。(请参见图19-16,这是一个选择类层次结构的说明。)类vtkAbstractPicker作为所有选择器的基类。它定义了一个最小的API,允许用户使用GetPickPosition()方法检索拾取位置(在全局坐标中)。
存在vtkAbstractPicker的两个直接子类。第一个,vtkWorldPointPicker,是一个快速(通常在硬件中)拾取类,它使用z缓冲区返回x-y-z全局拾取位置。但是,没有返回其他信息(关于被选中的vtkProp等)。类vtkAbstractPropPicker是vtkAbstractPicker的另一个直接子类。它为挑选器定义了一个API,可以挑选vtkProp的实例。在这个类中有几个方便的方法允许查询一个选择的返回类型。
•GetProp() -返回被选中的vtkProp实例。如果选择了任何东西,那么这个方法将返回一个指向vtkProp实例的指针,否则返回NULL。
•GetProp3D() -如果一个vtkProp3D的实例被选中,返回一个指向vtkProp3D实例的指针。
•GetActor2D() -如果vtkActor2D的实例被选中,返回一个指向vtkActor2D实例的指针。
•GetActor() -如果vtkActor的实例被选中,返回一个指向vtkActor实例的指针。
•GetVolume() -如果vtkVolume的实例被选中,返回一个指向vtkVolume实例的指针。
•GetAssembly() -如果vtkAssembly的实例被选中,返回一个指向vtkAssembly实例的指针。
•getpropassemassembly() -如果选择了vtkpropassemassembly的实例,返回指向vtkpropassemassembly实例的指针。
关于这些方法,我要提醒一句。该类(及其子类)返回有关所选择的程序集路径的顶层的信息。因此,如果您有一个顶层类型为vtkAssembly的程序集,其叶节点类型为vtkActor,则GetAssembly()方法将返回指向vtkAssembly实例的指针,而GetActor()方法将返回NULL指针(即没有vtkActor)。如果你有一个复杂的场景,包括组件、演员和其他类型的道具,最安全的方法是使用GetProp()方法来确定是否有任何东西被选中,然后使用GetPath()。
vtkAbstractPropPicker有三个直接子类。这些是vtkPropPicker, vtkAreaPicker和vtkPicker。vtkPropPicker使用硬件拾取来确定被拾取的vtkProp实例,以及拾取位置(在全局坐标中)。vtkPropPicker通常比vtkAbstractPropPicker的所有其他派生类更快,但它不能返回关于所选内容的详细信息。
vtkAreaPicker和它的基于硬件选择的后代vtkRenderedAreaPicker同样无法确定详细信息,因为这三个存在的目的是识别屏幕上显示的整个对象。AreaPicker类不同于其他的选择器,因为它们可以确定屏幕上整个矩形像素区域的开始位置,而不是单个像素后面的位置。这些类有一个AreaPick(x_min, y_min, x_max, y_max, Renderer)方法,可以在标准的Pick(x,y,z, Renderer)方法之外调用。如果您需要详细信息,例如特定的单元格和点或关于区域后面的信息,请查看下面的选择器解释。
vtkPicker是一个基于软件的选择器,选择vtkProp的基于他们的边界框。它的pick方法从相机位置通过选择点发射一条射线,并与每个道具3D的边界框相交;当然,可以选择多个道具3D。“最接近”的道具3D(根据它沿光线的边界框交叉点)被返回。(GetProp3Ds()方法可用于获取所有边界框相交的道具3D。)vtkPicker相当快,但不能生成单个唯一的选择。
vtkPicker有两个子类,可以用来检索关于什么被选中的更详细的信息(例如,点id,单元id等)vtkPointPicker选择一个点,并返回点id和坐标。它的工作原理是,从相机位置通过选择点发射一束射线,并将那些位于公差范围内的点投射到射线上。选择最接近摄像机位置的投影点及其相关的actor。(注意:实例变量公差表示为渲染窗口对角线长度的一小部分。)vtkPointPicker比vtkPicker慢,但比vtkCellPicker快。由于涉及到容差,它不能总是返回唯一的选择。
vtkCellPicker选择一个单元格并返回关于交点的信息(单元格id,全局坐标和参数单元格坐标)。它的工作原理是发射一条射线并与每个角色的底层几何中的所有细胞相交,确定每个细胞是否在特定的公差范围内与该射线相交。沿着指定光线最靠近摄像机位置的单元格及其关联的actor被选中。(注意:实例变量Tolerance在交集计算中使用,你可能需要实验它的值来获得满意的行为。)vtkCellPicker是所有picker中最慢的,但提供了最多的信息。它将在指定的公差范围内生成唯一的拾取。
定义了几个事件来与拾取操作交互。拾取器在执行拾取操作之前调用StartPickEvent。在拾取操作完成后调用EndPickEvent。每次选择参与者时,都会调用选择器的PickEvent和参与者的PickEvent。(注意,使用vtkWorldPointPicker时不会调用PickEvent。)
vtkAssemblyPath
了解类vtkAssemblyPath是必不可少的,如果你要在不同类型的vtkProp的场景中执行拾取,特别是如果场景包含vtkAssembly的实例。vtkAssemblyPath只是vtkAssemblyNode的有序列表,其中每个节点包含一个指向vtkProp的指针,以及一个可选的vtkMatrix4x4。列表的顺序很重要:列表的开头表示程序集层次结构中的根或顶级节点,而列表的末尾表示程序集层次结构中的叶节点。节点的顺序也会影响相关的矩阵。每个矩阵都是节点的vtkProp矩阵与列表中前一个矩阵的连接。因此,对于给定的vtkAssemblyNode,关联的vtkMatrix4x4表示vtkProp的位置和方向(假设vtkProp最初未转换)。
例子
通常,选择是由vtkRenderWindowInteractor自动管理的(参见第45页“使用VTK交互器”了解更多关于交互器的信息)。例如,当按下p键时,vtkRenderWindowInteractor调用带有其内部实例vtkPropPicker的pick。然后你可以向vtkrenderwindowwinteractor请求它的选择器,并收集你需要的信息。您还可以指定一个特定的vtkAbstractPicker实例供vtkRenderWindowInteractor使用,如下面的脚本所示。以数据集为例,结果如图4-6所示。此示例的脚本可以在VTK/Examples/Annotation/Tcl/annotatePick.tcl中找到。
vtkCellPicker picker
picker AddObserver EndPickEvent annotatePick
vtkTextMapper textMapper
set tprop [textMapper GetTextProperty]
$tprop SetFontFamilyToArial
$tprop SetFontSize 10
$tprop BoldOn
$tprop ShadowOn
$tprop SetColor 1 0 0
vtkActor2D textActor
textActor VisibilityOff
textActor SetMapper textMapper
vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
iren SetPicker picker
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
}
picker Pick 85 126 0 ren1
这个例子使用vtkTextMapper在屏幕上绘制拾取的世界坐标。(见
更多信息见第63页的“文本注释”。)注意,我们注册了EndPickEvent,以便在拾取发生后执行设置。该方法被配置为在选择完成时调用annotatePick()过程。
4.9 vtkCoordinate和坐标系
可视化工具箱支持几种不同的坐标系统,类vtkCoordinate管理它们之间的转换。支持的坐标系如下:
•DISPLAY -(渲染)窗口中的x-y像素值。(注意,vtkRenderWindow是vtkWindow的子类)。原点位于左下角(对于下面描述的所有2D坐标系统都是如此)。
•NORMALIZED DISPLAY - x-y(0,1)在窗口中的规范化值。
•VIEWPORT -视口中的x-y像素值(或渲染器- 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,除了它在覆盖平面上绘制并且没有
与之相关的4x4变换矩阵。像vtkActor一样,vtkActor2D引用映射器
(vtkMapper2D)和一个属性对象(vtkProperty2D)。使用vtkActor2D时最困难的部分是定位它。为此,使用了类vtkCoordinate。(见前一节,坐标与坐标系统”)。下面的脚本显示了如何使用vtkCoordinate对象。
vtkActor2D bannerActor
bannerActor SetMapper banner
[bannerActor GetProperty] SetColor 0 1 0
[bannerActor GetPositionCoordinate]
SetCoordinateSystemToNormalizedDisplay
[bannerActor GetPositionCoordinate] SetValue 0.5 0.5
在这个脚本中所做的是访问坐标对象并定义它的坐标系统。然后为该坐标系设置适当的值。在这个脚本中使用了一个规范化的显示坐标系统,因此显示坐标的范围从0到1,并且值(0.5,0.5)被设置为将vtkActor2D定位在渲染窗口的中间。vtkActor2D还提供了一个方便的方法,SetDisplayPosition(),它将坐标系统设置为DISPLAY,并使用输入参数在渲染窗口中使用像素偏移设置vtkActor2D的位置。下一节中的示例展示了如何使用该方法。
4.11文本标注
可视化工具包提供了两种注释图像的方法。首先,文本(和图形)可以在底层3D图形窗口的顶部呈现(通常称为在覆盖平面中呈现)。其次,可以将文本创建为3D多边形数据,并将其转换为任何其他3D图形对象。我们将其分别称为2D和3D注释。如图4-7所示。
2D文本注释
为了使用2D文本注释,我们使用2D演员(vtkActor2D及其子类,如vtkScaledTextActor)和映射器(vtkMapper2D和子类,如vtkTextMapper)。2D角色和映射器类似于它们的3D对应对象,除了它们在底层图形或图像的顶部的覆盖平面上渲染。下面是一个示例Tcl脚本在VTK/Examples/ Annotation/Tcl/TestText.tcl;如图4-7左侧所示。
vtkSphereSource sphere
vtkPolyDataMapper sphereMapper
sphereMapper SetInputConnection [sphere GetOutputPort]
sphereMapper GlobalImmediateModeRenderingOn
vtkLODActor sphereActor
sphereActor SetMapper sphereMapper
vtkTextActor textActor
textActor SetTextScaleModeToProp
textActor SetDisplayPosition 90 50
textActor SetInput "This is a sphere"
# Specify an initial size
[textActor GetPosition2Coordinate] \
SetCoordinateSystemToNormalizedViewport
[textActor GetPosition2Coordinate] SetValue 0.6 0.1
set tprop [textActor GetTextProperty]
$tprop SetFontSize 18
$tprop SetFontFamilyToArial
$tprop SetJustificationToCentered
$tprop BoldOn
$tprop ItalicOn
$tprop ShadowOn
$tprop SetColor 0 0 1
# Create the RenderWindow, Renderer and both Actors
vtkRenderer ren1
vtkRenderWindow renWin
renWin AddRenderer ren1
vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
# Add the actors to the renderer
ren1 AddViewProp textActor
ren1 sphereActor
类vtkTextProperty的实例允许您控制字体家族(Arial, Courier,或Times),设置文本颜色,打开和关闭粗体和斜体,并应用字体阴影。(阴影是用来使字体在复杂的背景图像上更易于阅读。)文本的位置和颜色由相关的vtkActor2D控制。(在这个例子中,位置是使用显示或像素坐标设置的。)
vtkTextProperty还支持对齐(垂直和水平)和多行文本。使用setjustify ationtolleft (), setjustify to居中()和setjustify ationtoright()方法来控制水平对齐。使用setverticaljustify tobottom (), setverticaljustify to居中()和setverticaljustify totop()方法来控制垂直对齐。默认情况下,文本为左底对齐。要插入多行文本,请使用嵌入文本中的\n字符。图4-8中的示例演示了对齐和多行文本(取自VTK/Examples/Annotation/Tcl/multiLineText.tcl)。该示例的实质如下所示。
vtkTextMapper textMapperL
textMapperL SetInput "This is\nmulti-line\ntext output\n(left-top)"
set tprop [textMapperL GetTextProperty]
$tprop ShallowCopy multiLineTextProp
$tprop SetJustificationToLeft
$tprop SetVerticalJustificationToTop
$tprop SetColor 1 0 0
vtkActor2D textActorL
textActorL SetMapper textMapperL
[textActorL GetPositionCoordinate] \
SetCoordinateSystemToNormalizedDisplay
[textActorL GetPositionCoordinate] SetValue 0.05 0.5
注意使用vtkCoordinate对象(通过调用GetPositionCoordinate()方法获得)来控制角色在规范化显示坐标系统中的位置。有关放置注释的更多信息,请参阅第62页的“vtkCoordinate和坐标系统”一节。
3D文本注释和vtkFollower
3D文本注释是使用vtkVectorText来创建文本字符串的多边形表示,然后在场景中适当地定位。一个用于定位3D文本的有用类是vtkFollower。该类是一种actor类型,它总是面对渲染器的活动摄像机,从而确保文本是可读的。这个Tcl脚本在VTK/Examples/Annotation/Tcl/ textOrigin中找到。tcl显示了如何做到这一点(图4-7)。该示例使用vtkVectorText实例与vtkFollower结合创建一个轴并标记原点。
vtkAxes axes
axes SetOrigin 0 0 0
vtkPolyDataMapper axesMapper
axesMapper SetInputConnection [axes GetOutputPort]
vtkActor axesActor
axesActor SetMapper axesMapper
vtkVectorText atext
atext SetText "Origin"
vtkPolyDataMapper textMapper
textMapper SetInputConnection [atext GetOutputPort]
vtkFollower textActor
textActor SetMapper textMapper
textActor SetScale 0.2 0.2 0.2
textActor AddPosition 0 -0.1 0
...etc...after rendering...
textActor SetCamera [ren1 GetActiveCamera]
当相机绕轴移动时,跟随器将朝向相机。(通过在渲染窗口中移动鼠标来移动摄像机。)
4.12特殊绘图类
可视化工具包提供了几个执行补充绘图操作的组合类。这些功能包括绘制标量条、执行简单的x-y绘图以及为3D空间上下文放置飞行轴的能力。
标量Bar
类vtkScalarBar用于创建一个颜色编码键,该键将颜色值与数字数据值联系起来,如图4-9所示。标量条有三个部分:带有彩色分段的矩形条、标签和标题。要使用vtkScalarBar,必须引用vtkLookupTable的实例
(定义颜色和数据值的范围),在覆盖平面上的标量条的位置和方向,以及可选地指定诸如(标签和标题的)颜色、标签数量和标题的文本字符串等属性。下面的示例显示了典型用法。
vtkScalarBarActor scalarBar
scalarBar SetLookupTable [mapper GetLookupTable]
scalarBar SetTitle "Temperature"
[scalarBar GetPositionCoordinate] \
SetCoordinateSystemToNormalizedViewport
[scalarBar GetPositionCoordinate] SetValue 0.1 0.01
scalarBar SetOrientationToHorizontal
scalarBar SetWidth 0.8
scalarBar SetHeight 0.17
标量条的方向由SetOrientationToVertical()和vtkSetOrientationToHorizontal()方法控制。要控制标量条的位置(即它的左下角),设置位置坐标(在任何你想要的坐标系中-参见第62页的“vtkCoordinate and coordinate Systems”),然后使用规范化视口值指定宽度和高度
(或者,指定Position2实例变量来设置右上角)。
x - y图
类vtkXYPlotActor从一个或多个输入数据集生成x-y图,如图4 -所示
10. 这个类对于显示跨一系列点(如线探针或边界边缘)的数据变化特别有用。
要使用vtkXYPlotActor2D,您必须指定一个或多个输入数据集、轴和绘图标题,并在覆盖平面上定位复合角色。PositionCoordinate实例变量定义了x-y图的左下角的位置(在标准化视口坐标中指定),Position2Coordinate实例变量定义了右上角。(注意:
Position2Coordinate是相对于PositionCoordinate的,所以你可以通过设置PositionCoordinate来移动vtkXYPlotActor。)两个位置坐标的组合指定了一个矩形,该图将位于其中。下面的例子(来自VTK/Examples/
注释/Tcl/xyPlot.tcl)显示了如何使用该类。
vtkXYPlotActor xyplot
xyplot AddInput [probe GetOutput]
xyplot AddInput [probe2 GetOutput]
xyplot AddInput [probe3 GetOutput]
[xyplot GetPositionCoordinate] SetValue 0.0 0.67 0
[xyplot GetPosition2Coordinate] SetValue 1.0 0.33 0
xyplot SetXValuesToArcLength
xyplot SetNumberOfXLabels 6
xyplot SetTitle "Pressure vs. Arc Length (Zoomed View)"
xyplot SetXTitle ""
xyplot SetYTitle "P"
xyplot SetXRange .1 .35
xyplot SetYRange .2 .4
[xyplot GetProperty] SetColor 0 0 0
注意x轴的定义。默认情况下,x坐标被设置为输入数据集中的点索引。
或者,您可以使用线的弧长和归一化弧长作为vtkXYPlotActor的输入来生成x值。
边界框轴(vtkCubeAxesActor2D)
另一个复合演员类是vtkCubeAxesActor2D。这个类可以用来表示摄像机正在观看的空间位置,如图4-11所示。该类在输入数据集的边界框周围绘制坐标轴,并标记为x-y-z坐标值。当相机放大时,轴被缩放以适应相机的视口,并且标签值被更新。用户可以控制各种字体属性以及相对字体大小(字体大小是自动选择的——SetFontFactor()方法可以用来影响所选字体的大小)。下面的脚本演示了如何使用该类(取自VTK/Examples/Annotation/Tcl/ cubeAxes.tcl)。
vtkTextProperty tprop
tprop SetColor 1 1 1
tprop ShadowOn
vtkCubeAxesActor2D axes
axes SetInput [normals GetOutput]
axes SetCamera [ren1 GetActiveCamera]
axes SetLabelFormat "%6.4g"
axes SetFlyModeToOuterEdges
axes SetFontFactor 0.8
axes SetAxisTitleTextProperty tprop
axis SetAxisLabelTextProperty tprop
注意,有两种绘制坐标轴的方法。默认情况下,使用边界框的外边缘(SetFlyModeToOuterEdges())。你也可以将坐标轴放置在最靠近摄像机位置的顶点(SetFlyModeToClosestTriad())。
序列数据标记
在某些应用程序中,您可能希望显示来自底层数据集的数值。类vtkLabeledDataMapper允许您标记与数据集的点相关的数据。这包括标量、向量、张量、法线、纹理坐标和字段数据,以及数据集的点id。文本标签放置在渲染图的叠加平面上,如图4-12所示。图由Tcl脚本VTK/Examples/Annotation/Tcl/ labeledMesh生成。TCL,它将在下面的部分中包含。该脚本使用了三个新类,vtkCellCenters(在单元格的参数中心生成点),vtkIdFilter(从数据集id中生成标量或字段数据的id)和vtkSelectVisiblePoints(选择当前可见的点),用于标记球体的单元格和点id。此外,vtkSelectVisiblePoints有能力在显示(像素)坐标中定义一个“窗口”,它在其中操作-窗口外的所有点都被丢弃。
# Create a sphere
vtkSphereSource sphere
vtkPolyDataMapper sphereMapper
sphereMapper SetInputConnection [sphere GetOutputPort]
sphereMapper GlobalImmediateModeRenderingOn
vtkActor sphereActor
sphereActor SetMapper sphereMapper
# Generate ids for labeling
vtkIdFilter ids
ids SetInputConnection [sphere GetOutputPort]
ids PointIdsOn
ids CellIdsOn
ids FieldDataOn
vtkRenderer ren1
# Create labels for points
vtkSelectVisiblePoints visPts
visPts SetInputConnection [ids GetOutputPort]
visPts SetRenderer ren1
visPts SelectionWindowOn
visPts SetSelection $xmin [expr $xmin + $xLength] \
$ymin [expr $ymin + $yLength]
vtkLabeledDataMapper ldm
ldm SetInput [visPts GetOutput]
ldm SetLabelFormat "%g"
ldm SetLabelModeToLabelFieldData
vtkActor2D pointLabels
pointLabels SetMapper ldm
# Create labels for cells
vtkCellCenters cc
cc SetInputConnection [ids GetOutputPort]
vtkSelectVisiblePoints visCells
visCells SetInputConnection [cc GetOutputPort]
visCells SetRenderer ren1
visCells SelectionWindowOn
visCells SetSelection $xmin [expr $xmin + $xLength] \
$ymin [expr $ymin + $yLength]
vtkLabeledDataMapper cellMapper
cellMapper SetInputConnection [visCells GetOutputPort]
cellMapper SetLabelFormat "%g"
cellMapper SetLabelModeToLabelFieldData
[cellMapper GetLabelTextProperty] SetColor 0 1 0
vtkActor2D cellLabels
cellLabels SetMapper cellMapper
# Add the actors to the renderer, set the background and size
ren1 AddActor sphereActor
ren1 AddActor2D pointLabels
ren1 AddActor2D cellLabels
4.13数据转换
正如我们在“注意我们如何使用vtkAssembly的AddPart()方法来构建层次结构”一节中看到的那样。只要不存在任何自引用循环,程序集可以任意深度嵌套。请注意,vtkAssembly是vtkProp3D的子类,因此它没有属性或相关映射器的概念。因此,vtkAssembly层次结构的叶节点必须携带有关材料属性(颜色等)和任何相关几何形状的信息。actor也可以被多个程序集使用(注意如何在程序集中使用coneActor并将其用作actor)。此外,渲染器的AddActor()方法用于将程序集的顶层与渲染器关联起来;不需要将程序集层次结构中较低级别的那些角色添加到呈现器中,因为它们是递归呈现的。在第57页,可以在世界空间中定位和定向vtkProp3D。然而,在许多应用程序中,我们希望在可视化管道中使用数据之前对其进行转换。例如,要使用平面来剪切(第98页的“剪切”)或剪辑(第110页的“剪辑数据”)一个对象,平面必须定位在管道中,而不是通过角色变换矩阵。有些对象(尤其是程序源对象)可以在空间中的特定位置和方向上创建。例如,vtkSphereSource有Center和Radius实例变量,vtkPlaneSource有Origin, Point1和Point2实例变量,允许你使用三个点来定位平面。但是,如果不将数据移动到新位置,许多类无法提供此功能。在这种情况下,您必须使用vtkTransformFilter或vtkTransformPolyDataFilter来转换数据。
vtkTransformFilter是一个将vtkPointSet数据集对象作为输入的过滤器。作为抽象类vtkPointSet子类的数据集显式地表示点,也就是说,vtkPoints的实例用于存储坐标信息。vtkTransformFilter对点应用变换矩阵并创建变换后的点数组;其余的数据集结构(如单元拓扑)和属性数据(如标量、向量等)保持不变。vtkTransformPolyDataFilter与vtkTransformFilter做同样的事情,除了它更方便在包含多边形数据的可视化管道中使用。
下面的例子(取自VTK/Examples/ datamanmanipulation /Tcl/marching。(如图4-13所示)使用vtkTransformPolyDataFilter来重新定位3D文本字符串。(关于3D文本的更多信息,请参阅第65页的“3D文本注释和vtkFollower”。)
#define the text for the labels
vtkVectorText caseLabel
caseLabel SetText "Case 12c - 11000101"
vtkTransform aLabelTransform
aLabelTransform Identity
aLabelTransform Translate -.2 0 1.25
aLabelTransform Scale .05 .05 .05
vtkTransformPolyDataFilter labelTransform
labelTransform SetTransform aLabelTransform
labelTransform SetInputConnection [caseLabel GetOutputPort]
vtkPolyDataMapper labelMapper
labelMapper SetInputConnection [labelTransform GetOutputPort];
vtkActor labelActor
labelActor SetMapper labelMapper
注意,vtkTransformPolyDataFilter要求您为它提供vtkTransform的实例。
回想一下,演员使用vtkTransform来控制他们在空间中的位置和方向。vtkTransform的实例支持许多方法,这里展示了一些最常用的方法。
•RotateX(角)应用在x轴旋转度(角度)
•RotateY(角)——应用绕y轴旋转
•RotateZ(角)应用在z轴旋转
•RotateWXYZ(角,x, y, z) -应用周围旋转向量定义为x y z分量
•规模(x, y, z) -应用规模在x, y, z方向
•翻译(x, y, z) -应用翻译
•逆()——反变换矩阵
•SetMatrix (m)——指定4 x4变换矩阵直接
•GetMatrix (m) -•PostMultiply() -控制变换矩阵的乘法顺序。如果调用PostMultiply(),则在当前矩阵的左侧应用矩阵操作。
•PreMultiply() -矩阵乘法应用于当前变换矩阵的右侧
上面描述的最后两种方法提醒我们,应用转换的顺序会极大地影响生成的转换矩阵。(请参阅“注意我们如何使用vtkAssembly的AddPart()方法来构建层次结构。只要不存在任何自引用循环,程序集可以任意深度嵌套。请注意,vtkAssembly是vtkProp3D的子类,因此它没有属性或相关映射器的概念。因此,vtkAssembly层次结构的叶节点必须携带有关材料属性(颜色等)和任何相关几何形状的信息。actor也可以被多个程序集使用(注意如何在程序集中使用coneActor并将其用作actor)。此外,渲染器的AddActor()方法用于将程序集的顶层与渲染器关联起来;不需要将程序集层次结构中较低级别的那些角色添加到呈现器中,因为它们是递归呈现的。,见第57页。)我们建议您花一些时间试验这些方法和应用程序的顺序,以完全理解vtkTransform。
现代转换技术
高级用户可能希望使用VTK广泛的转换层次结构。(其中大部分工作是由大卫·戈比完成的。)层次结构,其中类层次结构如图19-17所示,支持多种线性和非线性转换。
VTK转换层次结构的一个奇妙特性是,不同类型的转换可以在过滤器中使用,从而得到非常不同的结果。例如,vtkTransformPolyDataFilter接受vtkAbstractTransform类型的任何转换(或子类)。这包括转换类型,从线性,仿射vtkTransform(由4x4矩阵表示)到非线性,扭曲vtkThinPlateSplineTransform,这是一个复杂的函数,表示一组源和目标地标之间的相关性。
3D Widgets
交互器样式(参见第45页的“使用VTK交互器”)通常用于控制摄像机,并提供简单的按键和鼠标导向的交互技术。交互器样式在场景中没有表示;也就是说,它们不能被“看到”或交互,用户必须知道鼠标和键绑定是什么才能使用它们。然而,由于能够直接对场景中的对象进行操作,某些操作将大大简化。例如,如果可以交互式地定位线的端点,那么沿着一条线开始一个流线耙就很容易执行。
3D小部件的设计就是为了提供这个功能。和类vtkInteractorStyle一样,3D widgets也是vtkInteractorObserver的子类。也就是说,它们监视由vtkRenderWindowInteractor调用的事件。(回想一下,vtkRenderWindowInteractor将窗口系统特定的事件转换为VTK事件调用。)然而,与vtkInteractorStyle不同的是,3D小部件以各种方式在场景中表示自己。图4-14展示了VTK中的一些3D小部件。
以下是目前在VTK中发现的最重要的小部件列表,并简要介绍了它们的功能。请注意,这里提到的一些概念在本文中尚未涉及。请参阅第255页的“交互,小部件和选择”,以了解更多关于特定概念和VTK中可用的各种小部件的信息。
•vtkScalarBarWidget -管理vtkScalarBar,包括定位,缩放和定向。(请参阅第66页的“标量条”以获取有关标量条的更多信息)
•vtkPointWidget -在3D空间中定位一个点的x-y-z位置。小部件生成一个多边形输出。点小部件通常用于探测。(见“探测”在第100页)
•vtkLineWidget -放置直线与指定的细分分辨率。小部件生成一个多边形输出。line小部件的常见用途是探测(第100页的“探测”)和绘制数据(第66页的“X-Y图”)或生成流线(第95页的“流线”)或流表面(第97页的“流表面”)。
•vtkPlaneWidget -定向和定位有限平面。平面分辨率是可变的,小部件产生隐式函数和多边形输出。平面小部件用于探测(第100页的“探测”)和播种流线(第95页的“流线”)。
•vtkImplicitPlaneWidget -定向和定位一个无界平面。这个小部件产生一个隐式函数和一个多边形输出。多边形输出是通过使用边界框剪切平面来创建的。隐式平面小部件通常用于探测(第100页上的“探测”)、切割(第98页上的“切割”)和裁剪数据(第110页上的“裁剪数据”)。
•vtkBoxWidget -定位和定位一个边界框。小部件生成隐式函数和转换矩阵。盒子小部件用于转换vtkProp3D和子类(“转换数据”在第70页)或剪切(“切割”在第98页)或剪辑数据(“剪辑数据”在第110页)。图4-14在VTK中发现的一些3D小部件vtkScalarBarWidget vtkPointWidget vtkLineWidget vtkPlaneWidget vtkImplicitPlaneWidget vtkBoxWidget vtkImagePlaneWidget vtkSphereWidget vtkSplineWidget 74基础
•vtkImagePlaneWidget -操作三维体积数据集内的三个正交平面。探测平面以获得数据位置、像素值和窗口水平是可能的。图像平面小部件用于可视化体数据(第103页“图像处理和可视化”)。
•vtkSphereWidget -操纵可变分辨率的球体。这个小部件产生一个隐式函数和一个变换矩阵,并使焦点和位置的控制能够支持诸如vtkCamera和vtkLight之类的类。球体小部件可用于控制灯光和摄像机(第49页的“控制摄像机”和第51页的“控制灯光”),剪辑(第110页的“剪辑数据”)和切割(第98页的“切割”)。
•vtkSplineWidget -操纵插值3D样条(“创建电影文件”在第248页)。该小部件生成由一系列指定分辨率的线段表示的多边形数据。小部件还直接管理每个x-y-z坐标值的底层样条。
虽然每个小部件提供不同的功能和不同的API,但3D小部件的设置和使用方式是相似的。一般程序如下。
1. 实例化小部件。
2. 指定要观察的vtkRenderWindowInteractor。vtkRenderWindowInteractor调用小部件可能处理的事件。
3. 根据需要使用命令/观察者机制创建回调(即命令)——参见第29页的“用户方法、观察者和命令”。小部件调用事件StartInteractionEvent、InteractionEvent和EndInteractionEvent。
4. 大多数小部件需要“放置”——在场景中定位。这通常需要指定vtkProp3D的实例、数据集或显式指定一个边界框,然后调用PlaceWidget()方法。
5. 最后,必须启用小部件。默认情况下,按i键将启用小部件,它将出现在场景中。
请注意,在任何给定的时间可以启用多个小部件,并且这些小部件在与vtkInteractorStyle实例结合使用时功能良好。因此,在场景中鼠标不移动到任何特定的小部件上都将使用vtkInteractorStyle,但是在特定的小部件上鼠标移动只会使用该小部件—通常没有其他小部件或交互器样式会看到事件。(一个值得注意的例外是类vtkInteractorEventRecorder,它记录事件,然后传递它们。它还可以播放事件。这是一个非常有用的类,用于记录会话和测试。)
下面的例子(在VTK/Examples/ GUI/Tcl/ImplicitPlaneWidget.tcl中找到)演示了如何使用3D小部件。vtkImplicitPlaneWidget将用于剪辑对象。(有关剪辑的更多信息,请参阅第110页的“剪辑数据”。)在这个例子中,要裁剪的vtkProp3D是由位于球体点的球体和锥体字形形成的,并在球体法线方向上定向。(有关字形的更多信息,请参阅第94页的“字形”。)狼牙棒被一个平面夹住,把它分成两部分,其中一部分是绿色的。vtkImplicitPlaneWidget用于控制剪辑平面的位置和方向,方法是将鼠标放在小部件法向量上,移动定义平面原点的点,或者通过抓取小部件边界框来平移平面。
vtkSphereSource sphere
vtkConeSource cone
vtkGlyph3D glyph
glyph SetInputConnection [sphere GetOutputPort]
glyph SetSourceConnection [cone GetOutputPort]
glyph SetVectorModeToUseNormal
glyph SetScaleModeToScaleByVector
glyph SetScaleFactor 0.25
# The sphere and spikes are appended
# into a single polydata.
# This makes things simpler to manage.
vtkAppendPolyData apd
apd AddInputConnection [glyph GetOutputPort]
apd AddInputConnection [sphere GetOutputPort]
vtkPolyDataMapper maceMapper
maceMapper SetInputConnection [apd GetOutputPort]
vtkLODActor maceActor
maceActor SetMapper maceMapper
maceActor VisibilityOn
# This portion of the code clips the mace with the vtkPlanes
# implicit function. The clipped region is colored green.
vtkPlane plane
vtkClipPolyData clipper
clipper SetInputConnection [apd GetOutputPort]
clipper SetClipFunction plane
clipper InsideOutOn
vtkPolyDataMapper selectMapper
selectMapper SetInputConnection [clipper GetOutputPort]
vtkLODActor selectActor
selectActor SetMapper selectMapper
[selectActor GetProperty] SetColor 0 1 0
selectActor VisibilityOff
selectActor SetScale 1.01 1.01 1.01
vtkRenderer ren1
vtkRenderWindow renWin
renWin AddRenderer ren1
vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
# Associate the line widget with the interactor
vtkImplicitPlaneWidget planeWidget
planeWidget SetInteractor iren
planeWidget SetPlaceFactor 1.25
planeWidget SetInput [glyph GetOutput]
planeWidget PlaceWidget
planeWidget AddObserver InteractionEvent myCallback
ren1 AddActor maceActor
ren1 AddActor selectActor
iren AddObserver UserEvent {wm deiconify .vtkInteract}
renWin Render
# Prevent the tk window from showing up then start the event loop.
wm withdraw .
proc myCallback {} {
planeWidget GetPlane plane
selectActor VisibilityOn
}
如上所示,隐式平面小部件被实例化和放置。小部件的放置是相对于数据集的。(Tcl语句“[glyph GetOutput]”返回vtkPolyData, vtkDataSet的子类。)PlaceFactor调整小部件的相对大小。在本例中,小部件比输入数据集的边界框大25%。小部件行为的关键是添加了一个响应InteractionEvent的观察者。StartInteraction和EndInteraction通常分别由小部件在鼠标向下和鼠标向上时调用;在鼠标移动时调用InteractionEvent。InteractionEvent绑定到Tcl过程myCallback,该过程将小部件维护的平面复制到vtkplane的实例——一个用于执行剪切的隐式函数。(参见第213页的“隐式建模”。)
3D小部件是VTK的一个强大功能,可以快速添加复杂的交互到任何应用程序。我们鼓励您探索VTK发行版(在examples /GUI和Hybrid/Testing/Cxx)中包含的示例,以了解其功能的广度和力量。
4.14抗锯齿
有两种方法可以使用VTK来实现抗混叠:每个原语类型或通过多采样。
多采样通常会得到更令人满意的结果。
这两种抗锯齿方法都是由vtkRenderWindow API控制的。当图形卡启用并支持多采样时,每个基元类型的抗锯齿标志将被忽略。在这两种情况下,设置必须在创建vtkRenderWindow对象之后,但在屏幕上初始化之前完成。
请注意,通常情况下,实际的OpenGL实现的抗锯齿结果是不同的。(OpenGL实现要么是软件实现,比如Mesa,要么是显卡及其驱动程序的组合)
三个标志,每种类型的基元,控制抗锯齿:
•PointSmoothing,
•LineSmoothing
•PolygonSmoothing。
最初,它们都是禁用的。以下是在点基元上启用抗锯齿所需的4个步骤:
1.vtkRenderWindow * w = vtkRenderWindow::新();
2. w→SetMultiSamples (0);
3. w→SetPointSmoothing (1);
4. w→渲染();
下面是一个完整的例子,显示一个网格的顶点,代表一个球体与点抗锯齿:
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkSphereSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
int main()
{
vtkRenderWindowInteractor *i=vtkRenderWindowInteractor::New();
vtkRenderWindow *w=vtkRenderWindow::New();
i->SetRenderWindow(w);
w->SetMultiSamples(0); // no multisampling
w->SetPointSmoothing(1); // point antialiasing
vtkRenderer *r=vtkRenderer::New();
w->AddRenderer(r);
vtkSphereSource *s=vtkSphereSource::New();
vtkPolyDataMapper *m=vtkPolyDataMapper::New();
m->SetInputConnection(s->GetOutputPort());
vtkActor *a=vtkActor::New();
a->SetMapper(m);
vtkProperty *p=a->GetProperty();
p->SetRepresentationToPoints(); // we want to see points
p->SetPointSize(2.0); // big enough to notice antialiasing
p->SetLighting(0); // don't be disturb by shading
r->AddActor(a);
i->Start();
s->Delete();
m->Delete();
a->Delete();
r->Delete();
w->Delete();
i->Delete();
}
以下几行是特定于点抗锯齿的:
w->SetPointSmoothing(1);
p->SetRepresentationToPoints();
p->SetPointSize(2.0);
您可以通过将它们更改为:
w->SetLineSmoothing(1);
p->SetRepresentationToWireframe();
p->SetLineWidth(2.0);
你可以简单地使用:
w->PolygonSmoothing(1);
p->SetRepresentationToSurface();
多重采样
多重采样比以前的方法得到了更好的结果。最初,启用多采样。但它只有在显卡支持的情况下才有效。目前,VTK只支持X窗口上的多采样。要禁用multisampling,将MultiSamples的值(最初设置为8)设置为0:1。
1 vtkRenderWindow *w=vtkRenderWindow::New();
2. 2 w→SetMultiSamples (0);//禁用multisampling。
3. 3 w→渲染();
回到前面的例子,如果你使用的是X11,只要去掉线禁用多重采样,我们就会看到多重采样对点、线或多边形的影响。
4.15半透明多边形几何
将几何体渲染为半透明是一种强大的可视化工具。它允许“看穿”数据。它也可以用来关注感兴趣的地区;感兴趣的区域呈现为不透明,而上下文呈现为半透明。
渲染半透明的几何体并不简单:屏幕上像素的最终颜色是通过像素可见的所有几何原语的贡献。像素的颜色是所有可见原语的颜色混合操作的结果。混合操作本身通常是顺序相关的(即不可交换)。因此,为了正确呈现,需要进行深度排序。然而,深度排序有计算成本。
VTK提供了三种渲染半透明多边形几何的方法。它们中的每一个都是在正确性(质量)和成本(深度排序)之间的权衡。
快速和不正确。开始忽略前面关于深度排序的注释。这样就没有额外的计算成本,但屏幕上的结果是不正确的。但是,根据应用程序上下文,结果可能已经足够好了。
慢一点,几乎正确。这种方法包括使用两个过滤器。首先,使用vtkAppendPolyData附加所有多边形几何。然后将vtkAppendPolyData的输出端口连接到vtkDepthSortPolyData的输入端口。深度排序是按几何基元的质心执行的,而不是按像素。由于这个原因,它是不正确的,但它解决了大多数排序问题,并给出了一个通常足够好的结果。看看VTK/Hybrid/Testing/Tcl/depthSort。TCL为例。
非常缓慢和正确。如果显卡支持(仅nVidia),请使用“深度剥离”。它执行逐像素排序(更好的结果),但它真的很慢。在第一次渲染之前,在vtkRenderWindow上请求alpha位:
vtkRenderWindow *w=vtkRenderWindow::New();
w->SetAlphaBitPlanes(1);
Make sure multisampling is disabled:
w->SetMultiSamples(0);
在渲染器上,启用深度剥离:
vtkRenderer *r=vtkRenderer::New();
r->SetUseDepthPeeling(1);
设置深度剥离参数(渲染通道的最大数量和遮挡比)。
下一节将解释这些参数
r->SetMaximumNumberOfPeels(100);
r->SetOcclusionRatio(0.1);
Render the scene:
w->Render();
Finally, you can check that the graphics card supported depth peeling:
r->GetLastRenderingUsedDepthPeeling();
深度剥离参数。为了玩深度剥离参数,有必要了解算法本身。该算法从前到后剥离半透明几何体,直到没有更多的几何体要渲染。如果迭代循环达到用户设置的最大迭代次数,或者如果最后一次剥离修改的像素数小于窗口面积的某个比率(该比率由用户设置,如果该比率设置为0.0,则意味着用户想要确切的结果),则迭代循环停止。比值为0.2的渲染速度比比值为0.1的渲染速度快)。
OpenGL的要求。如果支持以下OpenGL扩展,显卡支持深度剥离:
* GL_ARB_depth_texture or OpenGL>=1.4
* GL_ARB_shadow or OpenGL>=1.4
* GL_EXT_shadow_funcs or OpenGL>=1.5
* GL_ARB_vertex_shader or OpenGL>=2.0
* GL_ARB_fragment_shader or OpenGL>=2.0
* GL_ARB_shader_objects or OpenGL>=2.0
* GL_ARB_occlusion_query or OpenGL>=1.5
* GL_ARB_multitexture or OpenGL>=1.3
* GL_ARB_texture_rectangle
* GL_SGIS_texture_edge_clamp, GL_EXT_texture_edge_clamp or OpenGL>=1.2
实际上,它可以与nVidia GeForce 6系列及以上或Mesa(例如7.4)一起使用。它不工作与ATI卡。
的例子。
这里有一个使用深度剥离的完整示例(您也可以在VTK/Rendering/Testing/Cxx中查找名称中有深度剥离的文件)。
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkActor.h"
#include "vtkImageSinusoidSource.h"
#include "vtkImageData.h"
#include "vtkImageDataGeometryFilter.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkPolyDataMapper.h"
#include "vtkLookupTable.h"
#include "vtkCamera.h"
int main()
{
vtkRenderWindowInteractor *iren=vtkRenderWindowInteractor::New();
vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->SetMultiSamples(0);
renWin->SetAlphaBitPlanes(1);
iren->SetRenderWindow(renWin);
renWin->Delete();
vtkRenderer *renderer = vtkRenderer::New();
renWin->AddRenderer(renderer);
renderer->Delete();
renderer->SetUseDepthPeeling(1);
renderer->SetMaximumNumberOfPeels(200);
renderer->SetOcclusionRatio(0.1);
vtkImageSinusoidSource *imageSource=vtkImageSinusoidSource::New();
imageSource->SetWholeExtent(0,9,0,9,0,9);
imageSource->SetPeriod(5);
imageSource->Update();
vtkImageData *image=imageSource->GetOutput();
double range[2];
image->GetScalarRange(range);
vtkDataSetSurfaceFilter *surface=vtkDataSetSurfaceFilter::New();
surface->SetInputConnection(imageSource->GetOutputPort());
imageSource->Delete();
vtkPolyDataMapper *mapper=vtkPolyDataMapper::New();
mapper->SetInputConnection(surface->GetOutputPort());
surface->Delete();
vtkLookupTable *lut=vtkLookupTable::New();
lut->SetTableRange(range);
lut->SetAlphaRange(0.5,0.5);
lut->SetHueRange(0.2,0.7);
lut->SetNumberOfTableValues(256);
lut->Build();
mapper->SetScalarVisibility(1);
mapper->SetLookupTable(lut);
lut->Delete();
vtkActor *actor=vtkActor::New();
renderer->AddActor(actor);
actor->Delete();
actor->SetMapper(mapper);
mapper->Delete();
renderer->SetBackground(0.1,0.3,0.0);
renWin->SetSize(400,400);
renWin->Render();
if(renderer->GetLastRenderingUsedDepthPeeling())
{
cout<<"depth peeling was used"<<endl;
}
else
{
cout<<"depth peeling was not used (alpha blending instead)"<<endl;
}
vtkCamera *camera=renderer->GetActiveCamera();
camera->Azimuth(-40.0);
camera->Elevation(20.0);
renWin->Render();
iren->Start();
}
绘图机制:自定义多数据映射器。有时你想要完全控制渲染polydata的步骤。VTK使使用画家机制成为可能。由于工厂设计模式,下面一行实际上创建了一个vtkPainterPolyDataMapper:
vtkPolyDataMapper *m=vtkPolyDataMapper::New();
你可以通过向下投射来访问vtkPainterPolyDataMapper API:
vtkPainterPolyDataMapper
*m2=vtkPainterPolyDataMapper::SafeDownCast(m);
这个多数据映射器将渲染委托给一个vtkPainter对象。SetPainter()和GetPainter()可以访问这个委托。
vtkPainter本身只是一个抽象的API,由具体的画家共享。他们每个人负责渲染的一个阶段。这种机制允许选择和组合阶段。例如,vtkPolygonsPainter负责绘制多边形,而vtkLightingPainter负责设置照明参数。画家的组合形成了画家的链条。它是一个链,因为每个画家都可以将渲染的部分执行委托给另一个画家。
大多数时候,你不需要显式地设置油漆工链:vtkDefaultPainter已经为你设置了一个标准的油漆工链。
写你自己的画家。编写你自己的painter主要包括编写2个类:vtkPainter的抽象子类,一个OpenGL实现的具体类。
让我们看一下现有的Painter: vtkLightingPainter。vtkLightingPainter派生自vtkPainter,几乎是空的。真正的实现是在具体类vtkOpenGLLightingPainter中,它覆盖了受保护的方法RenderInternal()。RenderInternal()的参数本质上是渲染器和actor。
实现RenderInternal()包括编写实际的渲染阶段代码,并通过调用->Superclass::RenderInternal()调用画家链中的下一个画家(“委托”)。
4.16动画
•动画是可视化系统的重要组成部分,等等。
•可以通过编写循环来创建简单的动画,这些循环可以不断地改变过滤器和渲染上的一些参数。但是,当涉及多个参数更改时,这种实现可能会变得复杂。
•VTK提供了一个框架,包括vtkAnimationCue和vtkAnimationScene来管理动画设置和播放。
•vtkAnimationCue对应于一个随时间变化的实体,例如演员的位置;而vtkAnimationScene表示由vtkAnimationCue实例组成的动画的场景或设置。
动画场景(vtkAnimationScene)
vtkAnimationScene表示动画的场景或设置。动画是通过在渲染每一帧之前改变一些可视化参数的同时按顺序渲染帧来生成的。每一帧都有一个与之相关的动画时间,这可以用来确定帧在动画中的位置。动画时间只是一个计数器,它根据播放模式在动画持续时间内不断增加。
以下是vtkAnimationScene的重要方法:
SetStartTime()/SetEndTime()
这些表示动画场景的开始和结束时间。这是在播放期间动画时间所覆盖的范围。
SetPlayMode()
这是用来控制他们的播放模式,即如何改变动画时间。有两种模式可用:
Sequence Mode (PLAYMODE_SEQUENCE)
在这种模式下,动画时间每帧增加(1/帧率),直到到达结束时间。因此,在单次运行中渲染的帧数是固定的,与渲染每帧所需的时间无关。
RealTime模式(PLAYMODE_REALTIME)
在此模式下,动画运行大约(EndTime-StartTime)秒,其中第n帧的动画时间由(动画时间和(n-1)帧+渲染时间(n-1)帧)给出。因此,渲染的帧数取决于如何变化渲染每帧所需的时间。
SetFrameRate()
帧率是单位时间内的帧数。这只在顺序游戏模式中使用。
AddCue(), RemoveCue(), RemoveAllCue()
从场景中添加/移除动画线索的方法。
SetAnimationTime()
可以用来显式地推进到一个特定的帧。可以在播放时调用
GetAnimationTime()
来查询动画时钟时间。
Play()
开始播放动画。
SetLoop()
当设置为True时,Play()导致在循环中播放动画。
动画提示(vtkAnimationCue)
vtkAnimationCue对应于动画中变化的实体。vtkAnimationCue不知道如何改变参数。所以用户要么子类化vtkAnimationCue,要么使用事件观察者在动画进行时执行更改。
在动画场景中,提示有开始时间和结束时间。在播放过程中,当场景的动画时间在为提示指定的开始和结束时间范围内时,提示处于活动状态。当提示被激活时,它会触发vtkCommand::StartAnimationCueEvent。对于随后的每一帧,它触发vtkCommand::AnimationCueTickEvent,直到到达结束时间,此时触发vtkCommand::EndAnimationCueEvent。下面是vtkAnimationCue的重要方法
SetTimeMode
TimeMode定义了如何指定开始时间和时间。有两种模式可用。
相对(TIMEMODE_RELATIVE)在此模式下,动画提示时间是相对于动画场景开始指定的。在这种模式下,提示的开始和结束时间总是在[0,1]的范围内,其中0对应动画场景的开始,1对应动画场景的结束。
SetStartTime/SetEndTime这些是用来指示动画时间的范围,当这个提示是活跃的。当TimeMode为TIMEMODE_RELATIVE时,它们与动画场景的开始和结束时间以相同的单位指定,并且相对于动画场景的开始。
如果TimeMode是TIMEMODE_NORMALIZED,则它们在[0,1]范围内,其中0对应于动画场景的开始,而1对应于动画场景的结束。
GetAnimationTime ()
这是为vtkCommand::AnimationCueTickEvent提供的事件处理程序。处理程序可以使用它来确定当前帧在动画中运行了多远。它的值取决于TimeMode。如果TimeMode是相对的,那么该值将是自提示被激活以来的时间单位数。如果TimeMode是Normalized,那么它将是[0,1]范围内的值,其中0是提示的开始,而1是提示的结束。
GetClockTime()
这与vtkAnimationScene::GetAnimationTime()返回的动画时钟时间相同。它仅在vtkCommand::AnimationCueTickEvent的事件处理程序中有效。
GetDeltaTime()
这可以用来获取动画点击时间从上一帧被渲染时的变化,如果有的话。同样,这仅在vtkCommand::AnimationCueTickEvent的事件处理程序中有效。如前所述,可以子类化vtkAnimationCue,而不是编写事件处理程序来做动画,在这种情况下你可以覆盖这个方法。这些参数分别对应于GetAnimationTime()、GetDeltaTime()和GetClockTime()返回的值。StartCueInternal(), EndCueInternal()这些方法可以在子类中被重写,以在播放期间进行设置和清理以及开始和结束提示。或者,可以为vtkCommand::StartAnimationCueEvent和vtkCommand::EndAnimationCueEvent添加事件观察者来做同样的事情。在下面的例子中,我们创建了一个简单的动画,其中vtkSphereSource的StartTheta随着动画的长度而变化。
在本例中,我们对动画提示使用规范化时间模式,因此我们可以更改场景时间或提示时间,而更改StartTheta值的代码仍然可以保持不变。
class vtkCustomAnimationCue: public vtkAnimationCue
{
public:
static vtkCustomAnimationCue* New();
vtkTypeRevisionMacro(vtkCustomAnimationCue, vtkAnimationCue);
vtkRenderWindow *RenWin;
vtkSphereSource* Sphere;
protected:
vtkCustomAnimationCue()
{
this->RenWin = 0;
this->Sphere = 0;
}
// Overridden to adjust the sphere's radius depending on the frame we
// are rendering. In this animation we want to change the StartTheta
// of the sphere from 0 to 180 over the length of the cue.
virtual void TickInternal(double currenttime, double deltatime,
double clocktime)
{
double new_st = currenttime * 180;
// since the cue is in normalized mode, the currentime will be in the
// range [0,1], where 0 is start of the cue and 1 is end of the cue.
this->Sphere->SetStartTheta(new_st);
this->RenWin->Render();
}
};
vtkStandardNewMacro(vtkCustomAnimationCue);
vtkCxxRevisionMacro(vtkCustomAnimationCue, "$Revision$");
int main(int argc, char *argv[])
{
// Create the graphics structure. The renderer renders into the
// render window.
vtkRenderer *ren1=vtkRenderer::New();
vtkRenderWindow *renWin=vtkRenderWindow::New();
renWin->SetMultiSamples(0);
renWin->AddRenderer(ren1);
vtkSphereSource* sphere = vtkSphereSource::New();
vtkPolyDataMapper* mapper = vtkPolyDataMapper::New();
mapper->SetInputConnection(sphere->GetOutputPort());
vtkActor* actor = vtkActor::New();
actor->SetMapper(mapper);
ren1->AddActor(actor);
ren1->ResetCamera();
renWin->Render();
// Create an Animation Scene
vtkAnimationScene *scene = vtkAnimationScene::New();
scene->SetModeToSequence();
scene->SetFrameRate(30);
scene->SetStartTime(0);
scene->SetEndTime(60);
// Create an Animation Cue to animate the camera.
vtkCustomAnimationCue *cue1 = vtkCustomAnimationCue::New();
cue1->Sphere = sphere;
cue1->RenWin = renWin;
cue1->SetTimeModeToNormalized();
cue1->SetStartTime(0);
cue1->SetEndTime(1.0);
scene->AddCue(cue1);
scene->Play();
scene->Stop();
ren1->Delete();
renWin->Delete();
scene->Delete();
cue1->Delete();
return 0;
}
本书为英文翻译而来,供学习vtk.js的人参考。