第 14 章:标量着色与颜色映射

学习目标

本章把“数据集有数值”与“屏幕有颜色”之间的步骤逐一展开。读完后,你应该能在多个数组中选中一个、设定有意义的映射范围、理解颜色表与 Actor 单色的关系,并区分连续物理量和离散类别的着色需求。

14.1 从一个数到一个像素

以第 4 阶段示例的 Temperature 为例,表面着色的大致路径是:

某点的 Temperature
   ↓ 选择数组及其分量
标量值
   ↓ 按 [最小值, 最大值] 归一化
颜色表中的位置
   ↓ 渲染三角形并处理插值、光照
屏幕颜色

vtkDataArray 只保存值;Mapper 决定用哪个数组、哪个范围和哪张颜色表。Actor 的 Property 仍可能影响光照、不透明度等外观。看到蓝色不等于“数据就是蓝色”,也不等于数值一定小;必须同时知道颜色表及其范围。

14.2 活动标量与明确按名称选择

第 3 阶段使用 pointData.setScalars(temperature) 设置活动点标量。只有一个数组时,Mapper 的默认标量模式通常足够。但示例还有另一个点数组 Distance。要让用户明确切换,采用:

surface.getPointData().setScalars(temperature);
surface.getPointData().addArray(distance);

mapper.setScalarModeToUsePointFieldData();
mapper.setColorByArrayName("Temperature");
mapper.setScalarVisibility(true);

这里的 Point Field Data 模式仍从 Point Data 属性集合里按名称取数组;名称容易让人误以为它取的是 surface.getFieldData(),需要特别留意。setScalarModeToUsePointData() 也可以在该集合中寻找指定数组,但当名称不存在时会回退到活动标量。按名称选择时,示例使用 Point Field Data 模式,以便错误名称不会悄悄显示另一个数组。

切换数组:

mapper.setColorByArrayName("Distance");
mapper.setScalarRange(distance.getRange());
renderWindow.render();

选择单元数组时,使用相应的 setScalarModeToUseCellFieldData() 并核对数组元组数与单元数。Point Data 在三角形内通常表现为连续变化;Cell Data 是每个单元一个值,通常表现为分块。数组关联是数据意义的一部分,不能靠颜色表修正错误关联。

14.3 颜色范围不是数据范围

dataArray.getRange() 告诉你当前数据的最小与最大数值;mapper.setScalarRange(min, max) 告诉颜色映射将哪个数值区间铺满颜色表。二者可以相同,也可以有意不同。

假设两次实验的温度分别在 [10, 40] 和 [20, 80]:

映射策略 结果 适合场景
各自用 getRange() 两幅图都充分使用整张颜色表 看每幅图的内部结构
共同固定为 [0, 100] 同色代表相同温度 做跨时间或跨样本比较

如果每张图都自动伸缩范围,两个不同值可能呈现同一种红色。因此做比较时必须在图例中明确数组名、单位、颜色表和范围。超过范围的值会落到颜色表的端部或其越界颜色设置;这不表示原始数值被改写。

本例用 vtkLookupTable 生成一条连续颜色表:

const lookupTable = vtkLookupTable.newInstance();
lookupTable.setHueRange(0.67, 0.0);
mapper.setLookupTable(lookupTable);
mapper.setScalarRange(temperature.getRange());

色相端点约对应蓝到红,但具体屏幕颜色还受饱和度、亮度、插值和光照影响。setScalarRange 在默认配置下会把范围交给 LookupTable;若 mapper.setUseLookupTableScalarRange(true),Mapper 改用 LookupTable 自身的范围,随后修改 Mapper 范围可能看不到变化。遇到“范围设置失效”,先查这个开关。

14.4 先插值数值,还是先插值颜色

点数组的每个顶点带一个值,三角形内部必须有连续显示结果。常见的两种顺序是:

  1. 先将各顶点数值映射为颜色,再在三角形内部插值颜色。
  2. 先在三角形内部插值标量,再将插值结果查颜色表。

vtk.js Mapper 的 setInterpolateScalarsBeforeMapping(true) 用于选择第二种路径;默认关闭时通常使用第一种路径。若颜色表不是线性的,两种路径不一定产生一样的中间色。它们都不改变原始点值。单元标量本来就按单元关联,不能靠打开这个选项凭空得到点值插值。

14.5 连续量、类别与直接颜色

温度、密度、速度幅值适合连续颜色映射。材料编号、器官标签、分类结果通常需要离散且稳定的颜色,例如类别 1 永远是同一颜色;不应让编号 1 和 2 之间出现“1.5 类”。vtk.js 的 vtkLookupTable 可按类别配置颜色;具体项目还应建立清楚的标签与颜色图例。

还有一种情况:数组本身已经存储 RGB 或 RGBA 分量,而不是待映射的物理标量。Mapper 的颜色模式可用 setColorModeToDirectScalars() 走直接颜色路径;此时分量数、数值类型和范围必须与预期颜色编码一致。不要对温度这种单分量物理量使用直接颜色模式。

14.6 与 Actor Property 的边界

当 mapper.setScalarVisibility(true) 且正确选中了数组,Actor 的 property.setColor(...) 不再是该表面的主要颜色来源。需要单色查看网格时,关闭标量可见性:

mapper.setScalarVisibility(false);
actor.getProperty().setColor(0.92, 0.72, 0.28);
renderWindow.render();

这一步只改变显示设置;Temperature 与 Distance 仍保存在 vtkPolyData 中。第 4 阶段示例的“颜色依据”菜单同时演示两种数组和 Actor 单色。练习会要求你在固定范围与自动范围之间做判断。

本章小结

一个可解释的标量图至少要说明:值来自哪个数组、数组关联到点还是单元、选用了什么范围和颜色表。Mapper 控制这些选择,Actor Property 决定其他表面外观。下一章将数值数组放入三维规则网格,从中取一张二维切片。