本文内容基本上来自于VTKUsersGuidVisualization Techniques一章的内容的总结, 所有的示例也都改写自VTK的示例. 具体的原代码在哪里记不清楚了, 但是都可以从相关的类的引用中找到.

1. 准备

本文会介绍对结构化网格中的某个曲面做可视化的几种技术,这里的例子来自于VTK中的几个例子的简单改写。它使用的是VTK的一个样例数据,combxyz.bincombq.bin。测试程序界面如下所示:

1.1 数据文件格式和Reader

我们使用的文件格式是PLOT3D文件格式,PLOT3D是一个计算机图形程序,用于可视化计算流体动力学的网格,NASA的Overflow CFD软件对PLOT3D格式做了扩展,支持Q变量。它由一个网格文件(也称为XYZ文件,在我们这里就是combxyz.bin文件)和一个可选的解决方案文件(也成为Q文件,在这里就是combq.bin文件)。

VTK提供了vtkMultiBlockPLOT3DReader来提供对它的解析。要做可视化,需要为要做可视化的标量和矢量指定特定的函数号。标量函数有:1 -不读取或计算任何标量100 -密度110 -压力111 -压力系数(需要伽玛溢出文件)112 -马赫数(需要伽玛溢出文件)113 -声速(需要伽玛溢出文件)120 -温度130 -焓140 -内部能量144 -动能153 -速度大小163 -停滞能量170 -熵184 -漩涡211 -涡度大小。矢量函数号有:-1 -不读取或计算任何矢量200 -速度201 -涡度202 -动量210 -压力梯度。212 -应变率。

此外,还可以读取数据并设置为点属性数据中,此时使用AddFunction()列出要读取的函数。

学过力学的人应该很熟悉这些内容,如果没学过也没有什么关系,对我们来说,仅仅是一些数据而已。

1.2 颜色映射ColorMap

颜色映射是可视化中很普遍的一种做法,基本思想是利用lut表查找到scalar的属性值对应的颜色值。

有一点很重要的是,在缺省情况下,actor会使用mapper带的颜色值来渲染,而不是actor自己的。要使用actor自己带的颜色值,要使用mapper的ScalarVisibilityOff()关掉自己的颜色。

lut表是一个HSVA格式的。

lut表可以使用SetTableValue()来修改特定位置的颜色

可以使用SetScalarRange()来控制映射颜色的值的范围。

也可以什么都不用, 直接调用Build()函数来生成一个默认的lut表.

生成lut表之后, 还可以手工修改里面指定位置的映射值.

lut表的生成是一门艺术, 需要根据实际值域的情况做不断的调整. 当然, 也可以做一些自适应的方式, 比如参考数据的直方图等.

1.3 对vtkPolyData着色

vtkPolyData是我们使用的最基本的建模模型. 它由点(Point)和单元(Cell)组成. 一个vdkPolyData包含一个点几何和一个单元集合. 点的集合定义了vtkPolyData的几何结构, 单元集合则定义了它的拓扑结构.

每个点和每个属性都可以有属性信息, 它们都可以同时有标量(Scalar)属性, 也可以有向量(Vector)属性. 而颜色, 是一种标量信息. 这个也是很容易理解的.

但是, 有一点是习惯对象思维的人需要注意的是, 在VTK的数据中, 比如vtkPolyData, 点集合, 单元集合, 点的标量属性集合, 点的向量属性集合, 等等, 这种叫法, 体现的也是它们的存储方式. 也就是说, 它们是按照类别集体存储的, 每个点的标量属性保存为一个数组, 向量属性保存为一个属性, …. 换句话说, 所谓”对象”, 只是逻辑意义上的对象. 这个东西对C来说是正常的, C++的程序员要理解也很容易, Java, Python之类的就要转变一下思维了. 所谓披着对象外衣的C而已.

因为它们实际上是数组, 所以要求是全有全无的的. 比如说, 以点的标量属性颜色为例, 要么所有的点都没有颜色属性, 要么所有的点都有颜色属性. 在内部, 其实就是用数组下标索引, 而下标就是点的ID. 对单元是同理.

具体的颜色值从哪里来也是很复杂的一个逻辑:

  • 使用点或单元的属性值
    • 默认使用标量属性值.
      • 如何使用标量数据:
        • SetColorModeToDefault(): 使用一个unsigned char类型的三元组数组. 如果没有, 就是用其他类型的标量数据进行颜色映射来生成颜色.
        • SetColorModeToMapScalars(): 直接利用标量数据做颜色映射, 而不考虑数据类型.
        • 当数据元组的组数大于1时, 使用元组的第一分组数据做颜色映射.
      • 使用谁的标量数据:
        • SetScalarModeToDefault(): 默认设置. 有限使用点标量数据. 当点标量数据不可用时, 看有没有单元的标量数据可用.
        • SetScalarModeToUsePointData(): 只使用点的标量数据.
        • SetScalarModeToUseCellData(): 值使用单元的标量数据.
  • 使用场属性(Field)
    • 使用谁的场属性:
      • SetScalarModeToUsePointFieldData()
      • SetScalarModeToUseCellFieldData()
    • 使用哪个场属性:
      • ColorByArrayComponent(), 或者
      • SelectColorArray()

2. 可视化的分类

从最终的诉求看, 在VTK中, 大概提供了下面几种观察的角度:

  • 看网格中一个或几个切片的属性值
  • 看网格被某个平面/曲面切开的剖面上的属性值
  • 查看属性值被映射的值
  • 查看网格中的某个/些等值面上的属性值
  • 查看某个面上的场的值

这些都是VTK的例子中涉及到的方面.

从基本上看, 都是一些Filter的组合. 了解和熟悉了这些Filter, 就可以自己来做灵活组合, 以满足自己的要求了.

2.1 网格面上面的可视化

获取网格面, 使用vtkGeometryFilter, vtkStructuredGridGeometryFilter, vtkImageDataGeometryFilter等. 我们主要使用的是vtkStructuredGridGeometryFilter. 它的方法SetExtent()可以指定i-j-k方向的取值范围, 以得到一个网格曲面. 这里要注意的是, 要获取一个曲面, 一定要有一个维度上面的范围是1. 例如, 我们要获取k方向上第9层的网格面, 就应该这样: plane->SetExtent(1, 100, 1, 100, 9, 9);

当我们想看多个面的时候, 比如前面例子中, 第9层和第10层, 我们需要分别构造两个曲面, 然后使用vtkAppendPolyData将它们集合起来. 而不能这样用: plane->SetExtent(1, 100, 1, 100, 9, 10);

为啥不能这么用, 我还不知道. 反正效果不对, 先记着, 以后有时间再慢慢了解.

2.2 用一个曲面来切开网格

最简单的情况就是用一个平面来切开网格, 获取这个切面上的数据. 在VTK里面, 叫Cut, 对应的Filter就叫vtkCutter. 对vtkCutter, 我们需要使用SetCutFunction()来定义切分的函数. 比如我们可以定义一个平面vtkPlane, 这个最容易实现.

和它比较接近的Filter有vtkClipPolyData, vtkExtractGeometry等几个, 它们的作用是分割网格成两个部分, 而不是切面. 对我而言, 暂时不需要这个行为, 也就不讨论了.

2.3 Prob

这里指的是用一个数据集对另一个数据集进行采样的过程. 使用vtkProbeFilter, 用SetInputConnection()来指定要使用的探针数据集, 用SetSourceData()来指定要被探测(切开)的数据集.

我还没有弄明白Prob和Cut之间的差别. 从效果上看, 似乎没有看到区别. 或许, 是对投影函数的定义不同? 这里的Prob, 应该是具有更灵活的方式?

2.4 取等值面

VTK中可以使用vtkContourFilter来获取等值面(等值线). 如果我们在一个平面上使用vtkContourFilter, 那么得到的就是等值线了. 这个应该不难理解.

这里同样, 我只能猜测, 所谓的值, 是点的标量属性值?

2.5 数据的投影

数据的投影, 被称为warp. 利用vtkWarpScalar. 需要用SetNormal()指定投影法线的方向, 使用SetScaleFactor()指定缩放因子.

2.6 法向量计算

vtkContourFilter用于计算点/单元的法向量, 法向量在这里用于优化显示.

一些有用的类.

vtkGeometryFilter

是一个通用的过滤器,从任何类型的数据集提取数据集边界几何,拓扑和相关的属性数据。还可用于将任何类型的数据转换为多边形类型。这对于表面渲染特别有用。对于某些3D数据集,转换过程可能不太令人满意。例如,这个过滤器将提取一个体积或结构化网格数据集的外表面(如果点、单元格和范围裁剪被禁用)。(对于结构化数据,您可能想要使用vtkImageDataGeometryFilter, vtkStructuredGridGeometryFilter, vtkExtractUnstructuredGrid, vtkreclineargridgeometryfilter或vtkExtractVOI。)

vtkStructuredGridGeometryFilter

它从一个结构化网格中提取几何。通过指定适当的i-j-k索引,可以提取一个点,一条曲线,一个曲面或一个体。根据数据的类型,曲线和曲面可以是弯曲的,也可以是平面的。它实际上是提取一个$$m \times n \times o$$的点的区域。其中,i,j,k都是点的索引的位置,都是从0开始计数的。例如, 如果数据集的维度是$$50\times50\times50$$,那么(0,49,0,49,7,7)表示提取k方向上第7个平面。

它使用SetExtent()来选择。

vtkAppendPolyData

用于将一个多边形数据集合并到另一个里面去。 所有的几何图形都被提取和附加,但是点和单元格属性(即标量、向量、法线)只有在所有数据集都有点和/或单元格属性可用的情况下才被提取和附加。(例如,如果一个数据集有点标量,而另一个数据集没有,则不会添加点标量。)

vtkWarpScalar

它通过沿点法线移动点通过标量量乘以比例因子来修改点坐标。用于创建地毯或x-y-z图。

一般情况下,通过SetNormal()来指定变形的发现方向,通过SetScaleFactor()来指定缩放大小。

vtkPolyDataNormals

这个过滤器用于计算多边形网格的点/单元的法线,并对多边形重新排序,以确保多边形邻居之间的方向一致,可能会对锐利的边缘进行分割生成新的点,以获得清晰的表面定义。

但是,这个的效果有点迷惑。在我们的例子里面,warpCombustorcolorIsosurface的效果完全不同。

vtkStructuredGridOutlineFilter

$$x \times y$$