VTK编程指南<六>:VTK可视化管线与渲染详解
1、VTK渲染引擎
回顾前几章节的RenderCylinder示例 可以找到以下的类:
vtkProp;
ytkAbstractMapper;
vtkProperty;
vtkCamera;
vtkLight;
vtkRenderer;
vtkRenderWindow;
vtkRenderWindowInteractor
vtkTransform;
vtkLookupTable;
可以发现这些类都是与数据显示或渲染相关的。用计算机图形学的专业词汇来说,就是它们构成了VTK的渲染引擎(Rendering Engine)。渲染引擎主要负责数据的可视化表达,是VTK里的两个重要概念之一,而另一个重要概念就是可视化管线(Visualization Pipeline)。
可视化管线是指用于获取或创建数据、处理数据以及把数据写入文件或者把数据传递给渲染引擎进行显示,这样的一种结构在VTK里就称为可视化管线。数据对象(Data Object)、处理对象(Process Object)和数据流方向(Direction of Data Flow)是可视化管线的三个基本要素。每个VTK程序都会有可视化管线存在,比如示例2.1RenderCylinder,其可视化管线可以简单地表示成如图所示的连接关系。
示例RenderCylinder 的可视化管线非常简单,先是创建一个柱体数据,接着经 Mapper后生成的图元直接送入渲染引擎渲染,创建的数据没有经过任何处理。再看一个稍微复杂的可视化管线。在该示例中,先读入一个后缀为vtk的文件(head.vtk),然后用移动立方体法(vtkMarchingCubes)提取等值面,最后把等值面数据经 Mapper 送往渲染引擎进行显示,示例代码如下:
#include <iostream>#include <vtkSmartPointer.h>
#include <vtkStructuredPointsReader.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkImageDataGeometryFilter.h>
#include <vtkMarchingCubes.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyDataReader.h>
#include <vtkActor.h>#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);// VTK was built with vtkRenderingOpenGL2
VTK_MODULE_INIT(vtkInteractionStyle);//测试文件:data/head.vtk
int main(int argc, char* argv[])
{std::string sFilePath = "D:\\VTK_Learn\\Examples\\Chap02\\data\\head.vtk";//读入Structured_Points类型的vtk文件。vtkSmartPointer<vtkStructuredPointsReader> reader =vtkSmartPointer<vtkStructuredPointsReader>::New();reader->SetFileName(sFilePath.c_str());//用移动立方体法提取等值面。vtkSmartPointer<vtkMarchingCubes> marchingCubes =vtkSmartPointer<vtkMarchingCubes>::New();marchingCubes->SetInputConnection(reader->GetOutputPort());marchingCubes->SetValue(0, 550);//将生成的等值面数据进行MappervtkSmartPointer<vtkPolyDataMapper> mapper =vtkSmartPointer<vtkPolyDataMapper>::New();mapper->SetInputConnection(marchingCubes->GetOutputPort());//把Mapper的输出送入渲染引擎进行显示vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();actor->SetMapper(mapper);vtkSmartPointer<vtkRenderer> renderer =vtkSmartPointer<vtkRenderer>::New();renderer->AddActor(actor);renderer->SetBackground(1.0, 1.0, 0.0);vtkSmartPointer<vtkRenderWindow> renWin =vtkSmartPointer<vtkRenderWindow>::New();renWin->AddRenderer(renderer);renWin->SetSize(640, 480);renWin->Render();renWin->SetWindowName("vtkPipelineDemo");vtkSmartPointer<vtkRenderWindowInteractor> interactor =vtkSmartPointer<vtkRenderWindowInteractor>::New();interactor->SetRenderWindow(renWin);interactor->Initialize();interactor->Start();return EXIT_SUCCESS;
}
2、VTK可视化管线
比较代码可以看出多了一个vtkMarchingCubes用于处理读入的数据。VTK 中把与类似 vtkMarchingCubes 对数据做处理的类称为 Filter。
Source 是指用于创建数据(如vtkCylinderSource)或者读取数据(如 vtkBMPReader、vtkStructuredPointsReader 等)类的统称,即 VTK 的数据源。Source 输出的数据作为 Filter 的输入,经Filter 处理以后(可以经多个 Filter 处理),生成新的数据。Filter 的输出可以直接写入文件,或者经 Mapper 变换后传入渲染引擎进行渲染、显示,结束可视化管线。
图 2-12 所示的箭头方向即为VTK里数据流流动的方向。可视化管线的三要素分别是数据对象、处理对象和数据流方向,Source、Filter 和 Mapper一起构成了处理对象,它们的区别是基于数据流的初始化、维持和终止。根据数据的生成方式,Source 可以分为程序源对象(Procedural,如vtkCylinderSource,通过程序代码生成相关的数据)和读取源对象(Reader,如 vtkBMPReader,从外部文件中导入数据)。
关于 Source、Filter 和 Mapper 的区别,可以简单地通过图 2-13 所示的示意图进行表示。Source 没有输入,但至少有一个输出;Filter 可以有一个或多个输入,产生一个或多个输出;Mapper 接受一个或多个输入,但没有输出,写文件的Writer(如 vtkBMPWriter)可以看作Mapper,负责把数据写入文件或者流(Stream)中,因此,Mapper 是可视化管线的终点,同时也是可视化管线和渲染引擎(有时也称之为图形管线)的桥梁。
2.1、可视化管线的连接
由示例可知,可视化管线里各个模块的连接是通过接口SetInputConnection()和 GetOutputPort()来完成的。VTK 5.0 版本之前,可视化管线之间的连接使用 SetInput()和 GetOutput(),VTK 5.0 版本的可视化管线使用 SetInputConnection()和GetOutputPort()连接,同时也保留了对旧版本的支持。
marchingCubes->SetInputConnection(reader->GetOutputPort));
vtkMarchingCubes作为 Filter 只接受一个输入,Filter 概括起来有以下三种类型(见图2-14):单个输入,产生单个输出;多个输入,产生单个输出,但输出的数据可有多种用途,比如,读入数据后,可以对其作等值面提取,另外还可以针对读入的数据生成轮廓线(Outline);单个输入,产生多个输出。
使用 SetInputConnection()和 GetOutputPort()连接可视化管线时,还要求连接的两部分之间的数据类型必须一致。由于管线是运行时才执行的,如果连接的两部分类型不匹配,程序运行时就会报错。比如,vtkMarchingCubes 要求输入的是 vtkImageData 类型的数据,如果给它输入的是vtkPolyData类型的,程序运行时就会报如下的错误:
ERROR:In ..L.lVTK-5.10\FilteringlvtkDemandDrivenPipeline.cxx,line 827
vtkStreamingDemandDrivenPipeline (0000000000BAAB90):Input for connection index 0 on input port index 0 for algorithm vtkMarchingCubes(0000000000BA9590) is of type vtkPolyData,but a vtkImageData is required.
2.2、可视化管线的执行
可视化管线连接完成后,必须有一种机制来控制管线的执行。有时,对某一部分数据做了改变,只希望改变的这部分数据在可视化管线里做更新,而不要影响其他没做改变的数据。如图2-15 所示,假如 Filter D的输入发生了变化,E和 F是依赖于 D的输入的,所以虚线框内的部分是需要重新执行的管线,而 C和 G是另外一个分支,D输入的改变不影响 C和 G,所以,为了节省运行时间,C和 G是不需要重新执行的。毕竟对于三维的应用程序来说,处理的数据量一般都是比较大的,如果真能这样做,有利于提高程序的运行效率。
VTK采用一种叫作“惰性赋值”(LazyEvaluation)的方案来控制管线的执行,惰性赋值是指根据每个对象的内部修改时间来决定什么时候执行管线,只有当用户或者程序发出“请求数据”时,管线才会被执行。
vtkObject 类里有一个重要的成员变量 MTime,管线里的每个从vtkObject 派生的类的对象都会跟踪自己的内部修改时间,当遇到“请求数据”时,该对象会比较这个修改时间,如果发现修改时间发生了改变,对象就会执行。换言之,VTK是采用命令驱动(Demand Driven)的方法来控制管线的执行,这种方法的好处是,当对数据对象作了更改时,不必立即做计算,只有当发出请求时才开始处理,这样能最小化计算所需的时间,以便更流畅地与数据进行交互。
vtkSmartPointer<vtkBMPReader> reader = vtkSmartPointer<vtkBMPReader>::New();std::cout<<"Modification Time of reader (After New()): "<<reader->GetMTime()<<std::endl;reader->SetFileName(argv[1]);std::cout<<"Modification Time of reader (After SetFileName()): "<<reader->GetMTime()<<std::endl;vtkImageData* imageData = reader->GetOutput();std::cout<<"Modification Time of reader (After GetOutput()): "<<reader->GetMTime()<<std::endl;reader->Update();std::cout<<"Modification Time of reader (After Update()): "<<reader->GetMTime()<<std::endl;int extent[6];imageData->GetWholeExtent(extent);std::cout<<"Extent of image: "<<extent[0]<<" "<<extent[1]<<" "<<extent[2]<<" "<<extent[3]<<" "<<extent[4]<<" "<<extent[5]<<" "<<std::endl;
该例先读入一幅 BMP图像,然后把 reader 的输出值赋给 imageData,接着需要获取读入图像的大小,调用 vtkImageData::GetWholeExtent()方法后,输出为“Extent of image: 0-1 0 -1 0 -1”。该结果显然是错误的,所读图像实际大小为300X246 像素,这就是因为没有“请求数据(RequestData())。
只有在 reader->GetOutput()之前,调用 reader->Update(),才会迫使管线的执行,即 reader 从磁盘中读取数据,然后才可获得 imageData 的正确信息。通常,程序不用显式地调用 Update()函数,因为在渲染引擎的最后,当调用 Render()函数时,Actor 就会收到渲染请求,接着 Actor 会请求 Mapper 给它发送数据,而 Mapper 又会请求上一层的 Filter 的数据,Filter 最后请求 Source 给它数据,于是,整条管线就被执行。除非像以上代码所列,读入数据以后,中间需要输出某些信息,在得到这些信息之前,就应该显式地调用 Update()函数。管线的执行过程大致如图 2-16 所示。