24.2.4 渲染子系统

乍看VTK拥有一个简洁的面向对象的渲染模型,这个模型由对应于构建三维场景的组件的类组成。例如:vtkActor是由与vtkCamera结合在一起的vtkRenderer来渲染的对象,一个vtkRenderWindow中可能具有多个vtkRenderer。该场景由一个或多个vtkLight提供光照。每个vtkActor的位置由vtkTransform控制,而该演员的外观则通过vtkProperty来制订。最后,该演员的几何表示由vtkMapper来定义。映射在VTK中扮演着重要的角色,它们用于数据处理管线的结尾,同时向渲染系统提供接口。考虑下面的例子,我们在这个例子中大幅削减数据,然后将结果写入一个文件,最后通过映射将其可视化,并实现与该结果的交互。

vtkOBJReader *reader = vtkOBJReader::New(); reader->SetFileName("exampleFile.obj");  vtkTriangleFilter *tri = vtkTriangleFilter::New(); tri->SetInputConnection(reader->GetOutputPort());  vtkQuadricDecimation *deci = vtkQuadricDecimation::New(); deci->SetInputConnection(tri->GetOutputPort()); deci->SetTargetReduction( 0.75 );  vtkPolyDataMapper *mapper = vtkPolyDataMapper::New(); mapper->SetInputConnection(deci->GetOutputPort());  vtkActor *actor = vtkActor::New(); actor->SetMapper(mapper);  vtkRenderer *renderer = vtkRenderer::New(); renderer->AddActor(actor);  vtkRenderWindow *renWin = vtkRenderWindow::New(); renWin->AddRenderer(renderer);  vtkRenderWindowInteractor *interactor = vtkRenderWindowInteractor::New(); interactor->SetRenderWindow(renWin);  renWin->Render();

这里只有一个演员,渲染器 和 渲染窗与映射一同创建,该映射将管线与渲染系统连接起来。也要注意vtkRenderWindowInteractor的引入,其实例捕捉鼠标与键盘事件,并将之转换为摄像机操作以及其它动作。这一转换过程由vtkInteractorStyle进行定义(下文还会就此详述)。缺省情况下,许多实例和数据值被置于场景之后。例如:创建了恒等变换,以及一个单独的光源和性质。

随着时间的推进,该对象模型变得更加复杂。多数复杂性来源于开发派生类,这些派生类用于具体指定渲染过程中的某一方面。vtkActor目前是vtkProp的特例(prop正如戏台上的道具),而且还有一群这样的道具用于渲染二维的图形叠加和文本,指定三维物体,甚至用于支持诸如体绘制或GPU实现等的高级渲染技术(见图24.4)。

类似地,随着VTK支持的数据模型的增长,各式用于实现数据于渲染系统接口的映射也随之出现。另外一个发生显著扩展的领域是变换的层次结构。这里最初只有一个简易的4×4变换矩阵,现在变成了一个强悍的类层次结构,它们支持包括薄板样条变换(thin-plate spline transformation)在内的非线性变换。例如:原始的vtkPolyDataMapper拥有支持具体设备的子类(如:vtkOpenGLPolyDataMapper)。近几年,它已经被图24.4所展示的一种称为“painter”的复杂的图形学管线所取代。

enter p_w_picpath description here

图24.4:显示功能类

painter的设计支持一大类渲染数据的技术,这些技术能够组合起来以提供特殊的渲染效果。这种能力远远超出了于1994年最初实现的简易的vtkPolyDataMapper

可视化系统的另外一个重要方面是子系统的选择。在VTK中,有一个类层次picker,被粗略地分成两类对象:一类对象根据与硬件相关方法和软件方法作比对来选择vtkProp(例如:ray-casting);另一类对象在一次picker运算之后,提供不同水平的信息。例如:一些picker仅提供XYZ世界空间的位置,而不指明它们选择了哪个vtkProp;其它picker不但给出所选的vtkProp,还给出组成用于定义道具几何特征网格的具体的点或单元。

24.2.4 事件与交互

与数据交互是可视化的关键一环。在VTK中,交互的方式有多种。最简单的方式是,用户通过命令观察事件并做出合适的反应(命令模式/观察者模式)。vtkObject的所有子类都保有一列观察者,这些观察者将其自身寄存于对象中。寄存过程中,观察者指出其感兴趣的特殊事件,并加入关联的命令,此命令将在事件发生时被调用。为了说明这一工作原理,来考虑下面的例子,该例中有一个带有观察者的滤波器(本例中是一个多边形削减滤波器),此观察者观察三种事件:StartEventProgressEvent,和EndEvent。这些事件在这三种情况下被该滤波器所调用:滤波器开始执行时,滤波器执行过程中(周期性调用),以及滤波器执行结束时。下面的代码中,vtkCommand类拥有一个Execute方法,该方法用于打印与该类执行算法所花费时间有关的恰当信息。

class vtkProgressCommand : vtk Command {   public:     static vtkProgressCommand *New() { return new vtkProgressCommand; }     virtual void Execute(vtkObject *caller, unsigned long, void *callData)     {       double progress = *(static_cast<double*>(callData));       std::cout << "Progress at " << progress << std::endl;     } };  vtkCommand* pobserver = vtkProgressCommand::New();  vtkDecimatePro *deci = vtkDecimatePro::New(); deci->SetInputConnection( byu->GetOutputPort() ); deci->SetTargetReduction( 0.75 ); deci->AddObserver( vtkCommand::ProgressEvent, pobserver );

尽管这是交互的一种原始形式,它也是许多使用VTK的应用程序的基本要素。例如:上述的简短代码可以很容易地转换、用于显示并管理图形界面中的进度条。这一命令/观察者子系统也是VTK中三维挂件的核心,这些挂件是用于数据的请求、操纵以及编辑的复杂的交互性对象,下文将予以描述。

提到上面的例子,很重要的一点是,VTK中的事件都是预定义的,但是这里也为自定义事件开了后门。vtkCommand类定义了一组枚举型事件(例如:上面例子中的vtkCommand::ProgressEvent)以及一个用户事件。UserEvent只是一个×××数值,一般用作一组应用程序中自定义事件的起始抵消值。于是,vtkCommand::UserEvent+100可能是指一个VTK预定义的事件之外的某个事件。

从用户的角度来看,一个VTK挂件可以看作是场景中的一个演员,只是用户可以通过操纵句柄或者其它几何特性(句柄操纵与几何特性操纵均是基于前文所述之抓取功能——原文:picking functionality,即24.2.4一节中最后一段所述——的)来与之交互。与挂件的交互是很直观的:用户抓住球面句柄并将其移动,或者抓住一条直线并将其移动。然而,在场景的背后,事件被发送出去(例如:InteractionEvent),而一个编写合理的应用程序就能够观察到这些事件,并采取恰当的行动。例如,它们通常由下面所给出的vtkCommand::InteractorEvent所触发:

vtkLW2Callback *myCallback = vtkLW2Callback::New();   myCallback->PolyData = seeds;    // streamlines seed points, updated on interaction   myCallback->Actor = streamline;  // streamline actor, made visible on interaction  vtkLineWidget2 *lineWidget = vtkLineWidget::New();   lineWidget->SetInteractor(iren);   lineWidget->SetRepresentation(rep);   lineWidget->AddObserver(vtkCommand::InteractionEvent, myCallback);

实际上,VTK挂件由两个对象构建而成:一个是vtkInteractorObserver的子类,另一个是vtkProp的子类。vtkInteractorObserver只是观察渲染窗中的用户交互(例如:鼠标事件和键盘事件)并处理之。这些操纵通常由突出显示句柄,改变鼠标指针的外观,以及变换数据等所组成,它们都会修改vtkProp的几何特征。当然,这些挂件的特殊细节要求编写子类来控制其行为的细微差别,目前系统中拥有50多个不同的挂件。

24.2.4 库的总结

VTK是一个大型软件工具箱。目前,系统由大约1500万行代码(包括注释,但是不包括自动生成的包裹层软件),约1000个C++类组成。为了管理系统的复杂度并减少构建和链接的时间,系统被分割放置在十几个子路径中。表24.1列出了这些子路径,并简要总结了这些库所提供的功能。














Common VTK核心类
Filtering 用于管理管线数据流的类
Rendering 渲染,抓取,查看图像,以及交互
VolumeRendering 体绘制技术
Graphics 三维几何处理
GenericFiltering 非线性三维几何处理
Imaging 图像处理管线
Hybrid 同时要求使用图形学和图像处理功能的类
Widgets 复杂的交互
IO VTK的输入和输出
Infovis 信息可视化
Parallel 并行处理(控制器和通信器)
Wrapping 对Tcl,Python以及Java的包裹的支持
Examples 内容广泛、文档良好的示例

表24.1 VTK的子路径

24.3 回顾与展望

VTK一直是一个非常成功的系统。虽然第一行代码于1993年写出,但是目前,VTK仍然在不断成长壮大、其开发速度也在不断加快2。本节,我们将谈谈一些经验和将来的挑战。

24.3.1 成长管理

VTK发展历程中,最令人惊叹的方面之一就是项目的寿命。开发的速度归因于若干主要原因:

  • 新算法和功能被持续不断地加入。例如,信息学子系统(Titan,最初由Sandia国立实验室和Kitware软件共同开发)是最近加入的一个重要的部分。额外的绘图和渲染类也同时加入进来,还有新的科学数据类型功能。另外一个加入的重要部分是三维交互挂件。最后,基于GPU的渲染以及数据处理的持续演进正在催生新的VTK功能。
  • VTK不断增多的曝光和使用是一个自我保持的过程,该过程向社区加入了更多的使用者和开发者。例如,ParaView是最受欢迎的基于VTK的科学可视化应用程序,并且受到了高性能计算社区的高度重视。3D Slicer是主要的生物医学计算平台,它大部分也建立于VTK之上,并且每年受到数百万美元的资助。
  • VTK的开发过程持续演进。近年来,CMake、CDash、CTest、以及CPack等软件过程工具已经集成到了VTK的构建环境中。最近,VTK的代码库已经迁移至Git和一个更为复杂的工作流。这些改进确保VTK保持科学计算社区内软件开发的领先地位。

虽然成长是令人兴奋的,确证软件系统的建立,预测VTK的未来,但妥善的管理却是极其困难的。因此,近期VTK将更多地专注于管理社区以及软件的成长。为此,已经采取了若干措施。

首先,创立了正式的管理架构。创建了架构审查委员会(Architectural Review Board),来指导社区和技术的发展,专注于高层次的、战略性的议题。VTK社区也正在组建一个由意见领袖组成的公认的团队,来指导某些VTK子系统的技术开发。

其次,制定了关于更进一步使工具箱模块化的计划,尤其是应对由git引入的工作流功能,还认识到使用者和开发者一般都想在工作中使用工具箱中小的子系统,并且不想构建并链接整个包。此外,为了支持不断成长的社区,对新的功能和子系统的支持是很重要的,即使它们并不一定是工具箱的核心部分。通过创建松散的、模块化的一群模块,在维持核心的稳定性的同时,适应外围的大量代码贡献是可能的。

24.3.2 技术整合

除了软件过程之外,在开发管线当中还有许多技术创新。

  • 共同处理是这样一种功能,可视化引擎被集成于仿真代码之中,而且周期性地提取生成用于可视化的数据。这一技术极大地降低了完整解决方案数据的大的输出数据量。
  • VTK中的数据处理管线还是太复杂。正在寻求简化和重构这些子系统的方法。
  • 直接与数据交互的能力正在使用者中间流行。尽管VTK拥有一大票挂件,但是更多的交互技术正在不断涌现,包括基于触摸屏的方法和三维方法。交互技术将会继续快速开发。
  • 计算化学对于材料设计人员和工程师的重要性正在不断提升。对化学数据的可视化与交互的功能正在加入VTK。
  • VTK的渲染系统素来因其过于复杂而饱受诟病,这使它难以派生出新的类或者支持新的渲染技术。此外,VTK不直接支持场景图概念,这同样也是许多使用者要求过的功能。
  • 最后是数据的新形式不断出现。例如,在医疗领域,变分辨率的层次化体数据(如:具有局部放大的共焦显微镜影像)。

24.3.3 开放科学

最后,Kitware和更加广泛的VTK社区决定加入Open Science。从务实的角度讲,它一个这样的方式,我们将传播公开的数据、公开的发表、以及公开的源代码——这是确保我们正在创建可重现的科学系统所必需的特征。虽然VTK一直以来都以开源和公开数据的系统的形式传播,但是文档过程却一直缺乏。在拥有正式书籍[Kit10,SML06]的同时,还一直有各种非正式的方法来收集包括新的源码在内的技术发表物。我们正在通过开发像是VTK Journal3的新的发表机制来改善这种状况,该期刊可以发表由文档、源代码、数据、以及有效的测试图像组成的文章。它还实现了自动化的代码审查(利用VTK的高质量的软件测试过程)以及人对递交文章的审查。

24.3.4 经验教训

虽然VTK很成功,但是还有许多事情我们没有处理好:

  • 设计的模块性。我们在选择我们的类的模块性上做得不错。例如,我们不会做类似为每个像素都创建一个对象的这种傻事,而是创建了高层次的vtkImageClass,它内部处理像素数据组成的数组。然而,在某些情况下,我们不得不将之重构为小的片段,并继续这一过程。一个基本的例子就是数据处理管线。最初,数据管线是通过数据和算法对象的交互而隐式实现的。我们最终认识到我们得创建一种显式的管线执行对象来协调数据与算法之间的交互,并且用于实现不同的数据处理策略。
  • 遗漏的关键概念。我们曾经的最大遗憾就是没有广泛的利用C++的迭代器。在许多情况下,VTK中的数据的遍历与科学编程语言Fortran十分类似。迭代器所提供的额外的灵活性本来可能对系统有很大帮助。例如,在处理局部区域的数据,或者仅仅是那些满足某种迭代准则的数据时,这是极具优势的。
  • 设计上的问题。当然,有一长列非最优的设计决策。我们同数据处理管线斗争,已经经历了许多代,每次都设计得更好些。渲染系统也是很复杂的,并且难以从其中派生出新类。另外一个由VTK的最初概念所引起的挑战是:我们将其看作是用于观察数据的只读可视化系统。然而,目前的客户经常希望它能够编辑数据,这就需要完全不同的数据结构。

像VTK这样的开源系统的好处之一是许多这些错误能够并且将会随着时间而得以纠正。我们拥有一个积极的、有能力的开发社区,他们每天都在改进着这个系统,并且我们希望在可预见的将来,这一状态能够维持下去。


脚注
1. http://en.wikipedia.org/wiki/Opaque_pointer.
2. See the latest VTK code analysis at http://www.ohloh.net/p/vtk/analyses/latest.
3. http://www.midasjournal.org/?journal=35.

本文链接:http://www.ituring.com.cn/article/6695