VTK经验分享 3. VTK的基础概念

来源:互联网 发布:淘宝怎么买gta 编辑:程序博客网 时间:2024/05/17 11:33

VTK和三维可视化建模的理论基础真是很多很多,啃教程的时候会感觉比较头疼。但是感觉只要抓住主要脉络就好。以下是自己的一些总结:

ps:下面的“[书]”指《The_Visualization_Toolkit-An_Object_Oriented_Approach_to_3D_Graphics-3rd_Edition.pdf》,必备教程,请从第一篇里边提到的svn里下。

 

3.1 VTK 可视化基本流程

    [书P.50]可视化的目的是把数据转换成图元(graphics primitives),继而再把图元渲染(render)为图像(image)。这句话涵盖了可视化的两大过程:Data Transformation(数据转换) 和 Rendering(渲染)

 

3.1.1 把数据转换成图元 —— Data Transformation

    对于Data Transformation,我们需要有两个方面的考虑:

    ①.数据的处理,它表示了数据转换过程中我们对数据所做的操作。如勾轮廓(outline)、提取等值面(surface contour)、取切平面(Extract Plane)、取等值线(Line Contour)等等。

    ②.数据的模型,它表示了数据转换过程中,以什么样的内部结构描述、储存数据和图元。

 

    [书P.100-101]为了从面向对象的角度描述Data Transformation,在多数时候,vtk将数据的描述、储存方式(②)和处理方式(①)进行了结合和抽象,封装成各个Algorithm类。这些Algorithm的对象(又叫Process Objects)相互连接使用,就描述了一个数据转换的流程,称为“pipeline”。根据在pipeline中所起不同作用,这些Algorithm类的对象又分为三类:

    [书P.102]

    1> Source,标志pipeline的起始。无输入,1-n个输出。

         • 根据参数产生数据的,叫procedural objects

         • 连接到外部数据的,叫reader objects

    2> Filter,描述pipeline的处理过程,有1-n个输入,1-n个输出。

    3> Mapper,标志pipeline的结束,1-n个输入,是数据转换的终止。

         • 一般的Mapper对象会将数据转换为图元(graphics primitives),它们一方面是Data Transformation的终结,另一方面连接着Graphics Model,标志着图形绘制的开始。

         • 另一种是将数据输出为文件的Mapper,称为writer objects。

 

3.1.2 把图元(graphics primitives)渲染为图像 —— Rendering

    这个过程的概念相对简单。我们记住渲染一个3D场景的几大要素就可以了,这些要素组成了“Graphics Model”。在vtk中,它们体现为:

    ①.vtkCamera - 相机、代表我们的视角

    ②.vtkActor - 演员,代表场景中的一个物体。我们用Mapper来表示其几何构成,用Property来描述其透明度、光照属性等

    ③.vtkLight - 灯光

    ④.vtkRenderer - 综合灯光、相机、演员来产生图像

    ⑤.vtkRenderWindow - 将产生的图像放在屏幕上展示

 

    另外,vtkActor是vtkProp的子类,vtkProp还有一些其他的比较重要的子类如vtkVolume(体渲染相关),vtkFollower(一直“面对观众”的actor)等等。

 

    此外,坐标系、颜色等知识也是重要的,但相对好理解,这部分在这里不作介绍。前篇介绍的水灵的视频教程里有对坐标系的详细讲解。[书 Chapter3]里也有详细的介绍。

 

3.2 VTK 数据集

 

    在一个pipeline中,除了我们使用的各种Process Object(Algorithm)之外,还有一个必不可少的Data Object,它代表了前面提到的数据的模型,是pipeline中的Algorithm所处理的对象。虽然在某些时候,它被vtk较好的封装所掩盖,甚至不用我们关心,但对于vtk的学习、理解以致灵活运用来说,它们是至关重要的。

 

    [书P.129]Data Object是表示数据的最宽泛的形式,表现为不带任何形式的数据的集合。这样它实际上带有的有用信息非常少。只有把这些数据以一定的结构组织起来,它们才具备各种可视化算法(Algorithm)可以操作的形式,此时,它们被称为数据集(Dataset)。

 

    我们从两个方面描述一个数据集:结构(structure),以及跟结构紧密关联的数据属性(data attributes)。在这些讲解之后,是一个例子,来帮助大家更好地理解相关的重要概念。

 

3.2.1 数据集的结构

 

  1> 两类结构,以及对应的表示方式:

    ① 数据集的结构包括两类:拓扑结构(topology),几何结构(geometry)。

        • 拓扑结构是在一些几何变换中(旋转、平移、缩放等)保持不变的属性集合,比如我们说一个多边形是“三角形”,就是说它的拓扑结构。

        • 几何结构是3D空间中关于位置的准确说明,当我们用三维坐标描述一系列点,就是在描述几何结构。

 

 

    ② 拓扑结构由基元(cells)构成和描述,几何结构由点(points)构成和描述。

 

    ③ 【定义】数据集的结构就是基元(cells)和点(points)的集合。

        计算机数据天生就是离散的,这也是我们如此定义数据集结构的直接原因。这里的“点”代表了有“数据”(后面我们将知道,这个'数据'就是data attributes)的位置,而基元将各个离散的点联系了起来,将数据空间划分成了网格状结构,为我们在点与点之间插入"未知但是可以计算出来的数据"(插值,interpolation)提供了基础。只有当我们把数据表示为基元的集合时,才能够进行取等值面、取切面数据的操作。

 

    “几何结构”还是比较好理解的,但是在“拓扑结构”和“基元”上,我们必须多花点心思,特别是“基元”,这是非常重要的概念:

 

    基元(cell):基元由一系列点按一定的顺序(这种顺序决定了基元的类型)构成。基元的类型(cell types)有多种。请参考[书P.131-134],非常重要的,请务必把这几页做个mark,常回头看看。

 

  2> 规则、不规则:

 

    不论是几何结构,还是拓扑结构,都可以分为“规则”或者“不规则”的。

 

    所谓“规则”,就是能从一些参数中推断出这个数据结构,比方说ImageData,这种常见的数据集就是由空间中一系列排列整齐、大小相等、边和面都与x-y-z世界坐标系平行或正交的六面体晶格组成(其实这些六面体就是这个数据集的cells,它们的顶点就是points),那么显然,我们只要知道了这个数据集的每个坐标系上的点数(dimension),原点(第一个点)的坐标(origin),每个坐标轴方向的间距(spacing),就可以准确无误地得到这个数据集的几何和拓扑结构。这样,我们说ImageData的几何、拓扑结构都是“规则”的。而像前面这样用dimension、origin、spacing等一系列参数间接表示数据集的结构的方式,叫“隐式表示(implicit representation)”。

 

    “不规则”当然就是规则的反面了,比如UnstructuredGrid,这种数据结构的几何、拓扑结构都无法用参数表示。在vtk中,我们必须借助点集、基元组等,指明其每一个点的坐标,和每一个cell的构成。这样的表示方式叫“显式表示(explicit representation)”。


  3> VTK的数据集表示:

    根据自身的两类结构规则或不规则,以及基元的结构特点,数据集有以下六种:多边形数据(PolyData),影像数据(Image Data),结构化网格(Structured Grid),线性网格(Rectilinear Grid),非结构化网格(Unstrctured Grid),非结构化点(Unstructured Points)。VTK提供了对应的类型来表示它们:


    ① 多边形数据(PolyData): vtkPolyData

      maintains four separate lists to:
      - vertices: vtkVertex, vtkPolyVertex (0d)
      - lines: vtkLine, vtkPolyLine (1d)
      - polygons: vtkTriangle, vtkQuad, vtkPolygon (2d)
      - trangle strips: vtkTriangleStrip (2d)

    ② 影像数据(Image Data): vtkImageData (vtkStructuredPoints in previous vtk versions)

      - both geometry & topology:
        ~.setDimension(int[i,j,k])
        ~.setSpacing(double[si,sj,sk])
        ~.setOrigin(double[ox,oy,oz])

      - the dataset will contains:
        i*j*k points
        (i-i)*(j-1)*(k-1) cells

   ③ 结构化网格(Structured Grid): vtkStructuredGrid

      - topology: specified by ~.setDimension(int[i,j,k]);
      - geometry: ~.setPoints(~vtkPoints); // explicitly specified, must have i*j*k points!

      - the dataset will contains:
        i*j*k points
        (i-i)*(j-1)*(k-1) cells

   ④ 线性网格(Rectilinear Grid): vtkRectilinearGrid

      - both geometry & topology:
        ~.SetDimensions(int[i,j,k])
        ~.SetXCoordinates(~vtkDataArray)
        ~.SetYCoordinates(~vtkDataArray)
        ~.SetZCoordinates(~vtkDataArray)

      - the dataset will contains:
        i*j*k points
        (i-i)*(j-1)*(k-1) cells

   ⑤ 非结构化网格(Unstrctured Grid): vtkUnstrcturedGrid

      - Both points and cells are explicitly represented using derived classes of vtkPoints and vtkCellArray

   ⑥ 非结构化点(Unstructured Points): either vtkPolyData or vtkUnstrcturedGrid



3.2.2 数据集的数据属性

 

    数据集的数据属性相对好理解一点。对于一个数据集来说,它的拓扑结构的每一个基元,以及几何结构的每个点上,都可以有数据,这些数据可以是温度、压强等标量,可以是流速这样的矢量,还可以是张量。

 

    这些数据往往就是我们需要展示的东西。当我们知道数据集的拓扑结构,以及数据属性(几何点上的,基元上的,都行)时,对于这个数据集内的任意一点,我们都可以根据其周围有确切值的地方,用插值等算法推算出此点上的数据。这是非常有用的,也是为什么拓扑结构和基元如此重要的原因。

 

3.2.3 工作中的一个实际例子

 

    现在我有某房间的温度分布数据。包括:房间中所有测温度的点的坐标,以及该点上的温度。


    下面我想用vtk展现这个房间任意一个切面上的温度分布,要如何做呢?

 

    1> 首先得把这个数据转换成vtk能读懂的数据集,所以我们必须从现有的数据开始,分析它有什么规律和特点,符合哪种vtk数据集的特点,或者可以转换成哪种vtk数据集

      当时这个数据有如下特点:

      ①房间为右手正交的坐标系,取左下角为起始点(0,0,0)。

      ②此数据的空间点排列符合ImageData中“dimension”的概念,x轴100、y轴80、z轴20,总共16万个数据点;但spacing在某些地方不固定,在x、y轴上,有些点之间的间距不一样。大体上都是0.5,但有的地方是1.0。

      ③对于这种类型的数据,不能用ImageData描述,但完全符合vtkRectilinearGrid的特点。所以就用这个了。

 

      想要将这个房间的数据转为数据集,有两种办法:

      ①自己从无到有创建一个数据集。在代码中新建vtkRectilinearGrid,写代码从数据文件中解析出vtkRectilinearGrid需要的信息,如:dimension(~.SetDimension(...)),x、y、z(~.set[X/Y/Z]Coordinates(...))轴各自的坐标分布,点上的数据~.getPointData().SetScalars(...)。

      ②将数据文件先转换成vtk能读懂的数据文件格式xxx.vtk,之后直接用vtkRectilinearGridReader读取这个文件。

      明显第二种好得多,但是不妨试试第一种,因为这个过程中会看到上面提到的一些基础概念在vtk中是如何体现的。

 

      关于如何从无到有创建一个基本的数据集,下一篇准备用一个例子来介绍。我感觉这个过程挺重要的,能让自己对vtk的基础认识有一个很大的提高。

 

    2> 成功转换出数据集以后,就可以通过vtkCutter(vtk对切平面抽取的实现)取出任意平面。从之前对”基元“的介绍中,我们可以知道,因为此时的数据集是一个正确、完整的数据集了,含有正确的cell信息,所以我们截出来的切平面上是有温度数据的。温度数据是标量,我们可以直接指定它的vtkLookupTable,来把标量映射为颜色。之后就大功告成了:)

 

      这个例子的实现过程会贯穿以后的文章并作为主线。由于这次这篇文章还是讲概念,就不继续讨论相关内容了。

 

 

 

   That's all today, Thanks for reading :)