第 3 章:数据管线与场景管线

学习目标

读完本章,你应该能沿着一个示例追踪数据从哪里来、被谁处理、最后由谁显示;还应能区分数据处理连接与场景对象连接,而不把所有箭头都看成同一种“父子关系”。

3.1 为什么要分成两条关系

先看一个带 Filter 的一般流程:

Reader 或 Source → Filter → Mapper
       数据处理与传递

Mapper → Actor → Renderer → RenderWindow
         可见表示与渲染组织

第一行决定数据内容。Reader 从外部读取,Source 生成数据,Filter 将输入数据变成另一份输出数据。第二行决定数据怎样出现在画面里。Mapper 将数据接入渲染,Actor 表示可见对象,Renderer 管理画面中的对象和相机,RenderWindow 组织绘制。

这两条关系会在 Mapper 处相遇,但不能混为一谈。例如,改变 Source 的半径会改变它产生的点坐标;改变 Actor 的位置只会移动显示出来的对象。两者在屏幕上都可能让球移动或变大,却有不同的数据意义。

3.2 Source、Reader 与 Filter

本阶段使用 <code>vtkSphereSource</code>。它不读取文件,而是根据半径、分段数等参数计算球体的 <code>vtkPolyData</code>。它产生的数据随后可供 Mapper 或 Filter 使用。

Reader 的角色相似,但输入是外部数据。例如后续会分别介绍适合 VTK XML 文件和 vtk.js 数据集格式的 Reader。它们并不能因为都叫 Reader 就互换文件格式。

Filter 接收已有数据并输出处理后的数据。常见操作包括裁剪、切片、提取等值面。一个 Filter 的输出又可以成为下一个 Filter 的输入,因此管线可以逐步延长:

数据读取 → 裁剪 → 提取等值面 → Mapper → Actor

这里每个步骤都应先问清输入和输出的数据类型。如果 Filter 输出的是 <code>vtkPolyData</code>,后面的表面 Mapper 才知道怎样显示它;如果仍是 <code>vtkImageData</code>,就要选择适合切片或体渲染的 Mapper。

3.3 Mapper、Actor、Renderer 与 RenderWindow

<code>vtkMapper</code> 接收数据输入,并负责将数据映射到绘制过程。表面示例中的 Mapper 可接收 <code>vtkPolyData</code>。后续进行标量着色时,Mapper 还要知道选哪个数据数组以及怎样用颜色表示它。

<code>vtkActor</code> 通过 <code>setMapper</code> 指向一个 Mapper。Actor 有位置、方向、缩放和可见性等场景属性,也能通过 <code>getProperty()</code> 调整颜色等外观。它不是数据容器;同一份数据可以被不同 Actor 以不同位置或外观展示。

<code>vtkRenderer</code> 管理一组可见对象和相机。将 Actor 加入 Renderer 时调用 <code>renderer.addActor(actor)</code>。体渲染对象则会使用相应的 Volume API,不能把所有可见对象都假设成 Actor。

<code>vtkRenderWindow</code> 组织一个或多个 Renderer 及具体绘制视图。在浏览器中,还需要将视图放入 DOM、设置尺寸、建立交互器。第 4 章使用 <code>vtkFullScreenRenderWindow</code> 帮助完成这些环境工作;第 2 阶段会逐个拆开。

3.4 三个看似相似、实际不同的连接

在第 4 章代码中,你会看到:

mapper.setInputConnection(sphereSource.getOutputPort());
actor.setMapper(mapper);
renderer.addActor(actor);

第一行连接数据处理管线。<code>getOutputPort()</code> 表示 Source 的一个输出端口,Mapper 从这里取得结果。第二行指定 Actor 如何映射与显示数据。第三行把 Actor 放进一个 Renderer 的可见对象集合。

若你已经手工构造好一份数据,也可以直接写:

mapper.setInputData(polyData);

这时 Mapper 接收的是一个数据对象,而不是 Source 的输出连接。初学时可这样判断:

手上已有的是什么 常用连接
一个 Reader、Source 或 Filter,期望沿管线获得它的输出 <code>setInputConnection(algorithm.getOutputPort())</code>
一个直接构造或已经取得的数据对象 <code>setInputData(dataObject)</code>

连接方式决定数据如何到达 Mapper。管线对象修改后,vtk.js 会依据修改状态安排更新;具体的执行与缓存细节留到第 25 章。现在只需记住:修改 Source 参数后要重新请求画面渲染,不能只修改 JavaScript 变量就期待屏幕自行变化。

3.5 从球体示例反向追踪

看到屏幕上的橙色球体,可以反向提问:

  1. 谁把它加入画面? Renderer 持有 Actor。
  2. 它用什么方式显示? Actor 指向 Mapper,并通过 Property 设置表面颜色。
  3. Mapper 的数据来自哪里? 它连接 <code>vtkSphereSource</code> 的输出端口。
  4. Source 输出什么? 一份包含点和多边形的 <code>vtkPolyData</code>。
  5. 谁决定观察位置? Renderer 的相机;示例调用 <code>resetCamera()</code> 让相机对准对象。

这种反向追踪适合排错。画面为空时,依次检查数据有没有生成、Mapper 是否接入数据、Actor 是否有 Mapper、Actor 是否进入 Renderer、相机是否看向它。颜色不对时,再检查 Property 和标量映射,而不是先调整相机。

3.6 画面相似,数据意义可能不同

如果把球体移动到右侧,可以用 <code>actor.setPosition(1, 0, 0)</code>。这表示数据所生成的球仍在局部原点,只是场景中的表示被移动了。

另一种做法是让 Source 本身生成一个中心在右侧的球体。这样改变的是输出数据中的点坐标。若后续要把数据传给裁剪器、测量器或导出器,两种做法可能产生不同结果。不要仅凭屏幕画面判断数据是否被修改。

本章小结

阅读 vtk.js 代码时,同时追踪两件事:数据沿 Reader/Source、Filter、Mapper 怎样流动,以及Mapper 怎样通过 Actor、Renderer、RenderWindow 出现在画面中。下一章将把这两条关系变成一个可运行程序。