第 21 章:切片、合成体渲染、MIP 与等值面
学习目标
四种图像都能从同一份三维数据产生,但表达的问题不同。本章用第 6 阶段示例的 40×40×40 合成数据比较它们。读完后,你应该能针对“检查某一层”“观察透明的内部结构”“寻找沿视线最高值”“获取可测量表面”选择合适方式,并避免把漂亮的投影误认成完整的三维事实。
21.1 同一份数据,四个不同问题
示例的低值椭球壳约在标量 75 附近,两个内部亮团达到更高标量,整个数组的实测范围约为 [0,195.2]。四种显示方式都读取同一份 vtkImageData,没有为每张图重新造数据:

切片使用正面相机,其余三种使用同一三维取景,因此不要用上图相同屏幕位置做逐像素数值比较。它展示的是方法的结果类型。
| 方式 | 一条视线怎样使用数据 | 画面主要回答什么 | 典型限制 |
|---|---|---|---|
| 切片 | 只取选定平面的值 | 这一层是什么 | 不显示别的层 |
| 合成体渲染 | 多次取值,按颜色与 α 从前向后累积 | 哪些结构透出、遮挡关系怎样 | 强烈依赖传递函数和采样设置 |
| MIP | 找视线上的最大标量 | 这条视线是否遇到高值 | 丢失最大值的前后深度 |
| 等值面 | 在阈值穿过网格单元处提取三角形 | 某一数值边界的几何形状怎样 | 只保留所选阈值附近的表面 |
21.2 切片:在一个确定位置看数值
const imageMapper = vtkImageMapper.newInstance();
imageMapper.setInputData(imageData);
imageMapper.setSlicingMode(vtkImageMapper.SlicingMode.K);
imageMapper.setSlice(20);
const imageSlice = vtkImageSlice.newInstance();
imageSlice.setMapper(imageMapper);
renderer.addViewProp(imageSlice);
本例 originZ=-20、spacingZ=1,所以 K=20 的采样平面在世界 z=0。它能精确地回答“在这一层两个亮团在哪里”,但另一层的亮团可能根本不在画面里。调窗宽窗位改变显示灰度,不改变原始值。第 15 章已详细解释 ImageMapper 与 ImageSlice。
若想追踪三维结构,可连续浏览多层、做多个正交切面,或与体渲染并排查看。单张切片看不到全部体,也不是体渲染失败;它本来只承诺一个确定的截面。
21.3 合成体渲染:透明度决定内外层关系
const volumeMapper = vtkVolumeMapper.newInstance();
volumeMapper.setInputData(imageData);
volumeMapper.setBlendModeToComposite();
const volume = vtkVolume.newInstance();
volume.setMapper(volumeMapper);
volume.getProperty().setRGBTransferFunction(0, colorFunction);
volume.getProperty().setScalarOpacity(0, opacityFunction);
renderer.addVolume(volume);
这是第 16 章单条视线公式在整张画面上的应用。低值壳的 α 较小,因而能透出部分内部高值团;高值区域 α 较大,颜色与形状更明显。若把壳的 α 调得很高,内部结构可能被完全挡住。示例“不透明度倍率”控制合成模式的函数,观察前方遮挡怎样变化。
合成结果可以给人立体感,但颜色并非原始数据自带的材质。若改变颜色函数、α 曲线、光照或相机,外观会明显变化。做科学判断时,应保留传递函数设置和必要的图例,而不是仅保存截图。
21.4 MIP:最大值很突出,深度却消失
volumeMapper.setBlendModeToMaximumIntensity();
对某条视线取到的标量 s1,s2,...,MIP 的核心是 max(s1,s2,...)。然后再将这个最大值显示成灰度或颜色。高值的内部亮团在 MIP 中很显眼,即使它被低值壳包围。示例为 MIP 配了一条更不透明的显示函数,使投影中的最大值易于辨认;它不是合成模式的前后透明度累积。
若两个高值团位于同一条视线上,MIP 最终只保留更大的那个值。投影无法单凭一个像素说明它在前方还是后方,也不能提供真实遮挡关系。这在寻找高强度结构时有用,在判断深度、数量和重叠时则需要切片或其他视角辅助。
MIP 与“把合成体渲染的不透明度调低”不是同一个操作。前者改变了沿视线的数据选择规则;后者仍按多个样本的 α 与颜色累积。
21.5 等值面:先提取 PolyData,再渲染表面
等值面选一个标量阈值 q,在网格单元中寻找数据从小于 q 变为大于 q 的位置,并用三角形近似该边界。示例使用 vtkImageMarchingCubes:
const marchingCubes = vtkImageMarchingCubes.newInstance({
contourValue: 140,
computeNormals: true,
mergePoints: true,
});
marchingCubes.setInputData(imageData);
const mapper = vtkMapper.newInstance();
mapper.setInputConnection(marchingCubes.getOutputPort());
const actor = vtkActor.newInstance();
actor.setMapper(mapper);
renderer.addActor(actor);
这条管线是 vtkImageData → Filter → vtkPolyData → vtkMapper → vtkActor。在本教程版本、当前数据和阈值 140 下,示例得到约 740 个三角形。阈值降低时,壳层的一部分也可能进入等值面;阈值升高时,内部团会缩小甚至消失。它不像调整体渲染 α:改变 contourValue 会使 Filter 重新生成几何。
等值面可以旋转、测量、裁剪和导出为表面网格,是与 Three.js 表面经验最接近的结果。但它只近似一个阈值的边界;阈值两边的全部体内数值不会自动保存在这张表面上。vtkImageMarchingCubes 属于 Filter,第 24 章还会从一般数据管线角度说明它。
21.6 等值面阈值与透明度阈值不可混同
把体渲染的 α 设成“低于 140 全透明,高于 140 不透明”,画面可能像一个表面,但计算仍然是沿视线采样和合成。等值面过滤器会输出真正的三角网格,具有点、单元和法线。两者在边界插值、厚度、光照与可导出几何上都可能不同。
做实验时可以同时改变示例的“不透明度倍率”和“等值面阈值”:前者改变 VolumeProperty 的显示映射;后者改变 Filter 输出。若只是看截图相似,很容易忽略这层数据类型差异。
21.7 选用方法与验证结果
| 任务 | 优先尝试 | 仍需配合的检查 |
|---|---|---|
| 定位病灶或结构在第几层 | 切片 | 相邻层和世界坐标 |
| 观察组织之间的遮挡与透明关系 | 合成体渲染 | 记录颜色与 α 曲线;用切片验证 |
| 快速寻找视线方向的高强度区域 | MIP | 多角度或切片定位深度 |
| 取得可测量或可导出的边界网格 | 等值面 | 检查阈值、网格分辨率和伪影 |
四种方法都不是“更真实”的统一排名。它们在回答不同问题,且都受采样分辨率与原始数据质量限制。示例中的人造壳层和亮团容易辨认;真实影像里噪声、部分容积效应和数值单位还会影响结果。
本章小结
切片选平面,合成体渲染累积整段视线,MIP 选沿线最大值,等值面提取三角形。这四条路径从同一 vtkImageData 出发,却产生不同的数据或画面语义。第 7 阶段将读取真实文件,并进一步认识 Reader 与 Filter 如何接在这些显示管线前面。