Visualization Toolkit官方开发文档翻译(8)–第七章高级计算机图形学

第 3 章介绍了计算机图形学的基本概念。该章的一个主要主题是如何使用点、线和多边形等表面基元来表示和渲染几何图形。在本章中,我们主要关注体积图形。与表面图形相比,体积图形在渲染非均匀材料的能力上具有更大的表现范围,是可视化 3D 图像(体积)数据集的主要技术。

我们通过描述对表面和体积图形都很重要的两种技术开始本章。这些是使用简单的混合函数来模拟对象透明度,并使用纹理贴图来增加真实感,而不会产生过多的计算成本。我们还描述了这些技术固有的各种问题和挑战。然后,我们将重点讨论体积图形,包括对象顺序和图像顺序技术、照明模型、混合表面和体积图形的方法,以及提高性能的方法。最后,本章以各种用于创建更逼真的可视化的重要技术作为结尾。这些技术包括立体观看、抗锯齿和高级相机技术,例如运动模糊、焦点模糊和相机运动。

7.1 透明度和 Alpha 值

到目前为止,我们一直专注于渲染不透明的对象——也就是说,我们假设对象在其表面反射、散射或吸收光,并且没有光透射到其内部。尽管渲染不透明对象肯定有用,但有许多应用程序可以从渲染透光对象的能力中受益。透明度的一个重要应用是体绘制,我们将在本章后面更详细地探讨它。另一个简单的例子是让物体半透明,这样我们就可以看到由表面包围的区域内部,如图 12-4所示)。如本例所示,通过使皮肤半透明,可以看到内脏。

透明度及其补充,不透明度,在计算机图形学中通常被称为alpha 。例如,一个 50% 不透明的多边形在从 0 到 1 的范围内将具有 0.5 的 alpha 值。alpha 值 1 表示不透明对象,0 表示完全透明对象。通常,alpha 被指定为整个 actor 的属性,但它也可以像颜色一样在顶点基础上完成。在这种情况下,颜色的 RGB 规范扩展为 RGBA,其中 A 表示 alpha 分量。在许多显卡上,帧缓冲区可以存储 alpha 值和 RGB 值。更典型的是,应用程序将请求仅存储图形卡上的红色、绿色和蓝色,并使用从后到前的混合来避免存储 alpha 的需要。

不幸的是,拥有透明演员会给渲染过程带来一些复杂性。如果你回想一下光线追踪的过程,观察光线会从摄影机投射到世界上,在那里它们与他们来到的第一个演员相交。对于不透明的演员,将应用照明方程并将生成的颜色绘制到屏幕上。对于一个半透明的actor,我们必须求解这个actor的光照方程,然后继续将光线投射得更远,看看它是否与其他actor相交。生成的颜色是它相交的所有演员的合成。对于每个表面相交,这可以表示为公式 7-1

屏幕截图 2022-11-15 212031

在这个方程下标指演员的表面,而下标指演员背后的东西。该术语称为透射率,表示通过演员透射的光量。例如,考虑从三个红色、绿色和蓝色的多边形开始,每个多边形的透明度为 0.5。如果红色多边形在前面且背景为黑色,则生成的 RGBA 颜色将为 (0.4, 0.2, 0.1, 0.875),范围从零到一 (图 7-1).

需要注意的是,如果我们切换多边形的顺序,结果颜色会发生变化。这是使用透明度的主要技术问题的基础。如果我们对场景进行光线追踪,我们将以明确定义的方式与表面相交——从前到后。使用这些知识,我们可以将光线追溯到它相交的最后一个表面,然后通过将公式7-1 以相反的顺序(即从后到前)应用于所有表面来合成颜色。在对象顺序渲染方法中,硬件通常支持这种合成,但不幸的是,我们不能保证以任何特定顺序渲染多边形。即使我们的多边形位于图 7-1,多边形的渲染顺序可能是蓝色多边形,然后是红色,最后是绿色多边形。因此,生成的颜色不正确。

如果我们查看一个像素的 RGBA 值,我们就会发现问题所在。渲染蓝色多边形时,帧缓冲区和z-buffer 为空,因此 RGBA quad (0,0,0.8,0.5) 与其 z 缓冲区值一起存储。渲染红色多边形时,其 z 值与当前 z 缓冲区的比较表明它位于前一个像素条目的前面。因此使用帧缓冲区的 RGBA 值应用公式 7-1。这导致将 RGBA 值 (0.4,0,0.2,0.75) 写入缓冲区。现在,绿色多边形被渲染,z 比较表明它落后于当前像素的值。再次应用此等式,这次使用表面的帧缓冲区 RGBA 值和后面的多边形值。这导致最终的像素颜色为 (0.3,0.2, 0.175,0.875),这与我们之前计算的不同。一旦红色和蓝色多边形被合成并写入帧缓冲区,

图7-1图 7-1。图像的物理生成。

此问题的一种解决方案是将多边形从后向前排序,然后按此顺序渲染它们。通常,这必须在需要额外计算开销的软件中完成。排序还会干扰actor属性(例如镜面反射功率),这些属性通常在渲染actor的多边形之前发送到图形引擎。一旦我们开始混合不同角色的多边形,我们必须确保为每个渲染的多边形设置正确的角色属性。

另一种解决方案是在帧缓冲区中存储一组以上的 RGBAZ 值。由于额外的内存要求,这是昂贵的,并且仍然受到您可以存储的 RGBAZ 值数量的限制。一些新技术结合使用多个 RGBAZ 值存储和多通道渲染来产生正确的结果,同时将性能影响降至最低[Hodges92]

渲染透明对象的第二个技术问题发生的频率较低,但仍然会产生灾难性的影响。在某些应用中,例如体绘制,需要有数千个具有小 alpha 值的多边形。如果 RGBA 四元组作为四个 8 位值存储在帧缓冲区中,则舍入可能会在许多多边形上累积,从而导致输出图像出现严重错误。如果图形硬件开始为纹理和帧缓冲区的每个组件存储 16 位或更多位,这在未来可能不会成为问题。

7.2 纹理映射

纹理映射是一种无需建模细节即可向图像添加细节的技术。纹理映射可以被认为是将图片粘贴到对象的表面。纹理映射的使用需要两条信息:纹理映射纹理坐标。纹理贴图就是我们粘贴的图片,纹理坐标指定了图片粘贴的位置。更一般地,纹理映射是在渲染对象时应用到对象的颜色、强度和/或透明度的表查找。纹理贴图和坐标通常是二维的,但大多数新的图形硬件都支持三维纹理贴图和坐标。

纹理映射的价值可以通过渲染一张木桌的简单例子来体现。可以轻松创建桌子的基本几何形状,但很难实现木纹细节。将桌子染成棕色是一个好的开始,但图像仍然不切实际。为了模拟木纹,我们需要在桌子表面进行许多小的颜色变化。使用顶点颜色需要我们拥有数百万个额外的顶点才能获得小的颜色变化。对此的解决方案是将木纹纹理贴图应用于原始多边形。这就像将橡木贴面应用到廉价的刨花板上,这是视频游戏使用的策略,用于提供具有少量多边形以进行交互的逼真场景。

有几种方法可以应用纹理数据。对于纹理贴图中的每个像素(通常称为纹理元素的纹素),可能有一到四个组件会影响纹理贴图如何粘贴到底层几何体的表面上。具有一个分量的纹理图称为强度图. 应用强度图会导致结果像素的强度(或 HSV 中的值)发生变化。如果我们拍摄一张木纹的灰度图像,然后将其纹理映射到棕色多边形上,我们将得到一张外观合理的桌子。多边形的色调和饱和度仍由棕色决定,但强度将由纹理贴图决定。使用木材的彩色图像可以获得更好看的桌子。这是一个三分量纹理贴图,其中每个纹素都表示为一个 RGB 三元组。使用 RGB 贴图可以让我们获得更逼真的图像,因为我们将获得的不仅仅是木材的强度变化。

通过将 alpha 值添加到强度图,我们得到两个分量。我们可以对 RGB 纹理贴图执行相同的操作,以获得四分量 RGBA 纹理贴图。在这些情况下,alpha 值可用于使基础几何的部分透明。计算机图形学中的一个常见技巧是使用 RGBA 纹理来渲染树。我们没有尝试对树的复杂几何进行建模,而是渲染一个矩形,并应用了 RGBA 纹理贴图。有树叶或树枝的地方,alpha 为 1,有间隙和开放空间的地方,alpha 为 0。结果,我们可以透过矩形的一部分看到,给人一种透过树枝和树叶观看的错觉。

除了可以定义纹理贴图的不同方式外,还可以选择它如何与对象的原始颜色交互。RGB 和 RGBA 贴图的一个常见选项是忽略原始颜色;也就是说,只需按照指定的方式应用纹理颜色。另一种选择是通过纹理贴图颜色(或强度)调制原始颜色以产生最终颜色。

虽然我们一直专注于 2D 纹理贴图,但它们可以是任何维度,但最常见的是 2D 和 3D。三维纹理贴图用于作为 3D 空间函数的纹理,例如木纹、石头或 X 射线强度(即 CT 扫描)。事实上,体积数据集本质上是一个 3D 纹理。我们可以通过将平面通过 3D 纹理并使用半透明 alpha 值以正确的顺序合成它们来执行高速体积渲染。

图7-2图 7-2。顶点纹理坐标。

本章稍后将讨论使用纹理映射硬件执行体绘制的技术。

纹理映射过程中的一个基本步骤是确定如何将纹理映射到几何体上。为了实现这一点,除了位置、表面法线、颜色和其他点属性之外,每个顶点都有一个关联的纹理坐标。纹理坐标将顶点映射到纹理贴图中,如图所示图 7-2. 纹理坐标系使用参数(u,v)(u,v,t)或等效地() 或者 ()用于指定 2D 和 3D 纹理值。顶点之间的点被线性插值以确定纹理贴图值。

纹理映射的另一种方法使用程序纹理定义而不是纹理映射。在这种方法中,当几何图形被渲染时,会为每个像素调用一个程序来计算一个纹素值。而不是使用(u,v,t)纹理坐标索引到图像中,它们作为参数传递给程序纹理,使用它们来计算其结果。这种方法在纹理设计中提供了几乎无限的灵活性;因此,几乎不可能在专用硬件中实现。最常见的是,程序纹理与不大量使用现有图形硬件的软件渲染系统一起使用。

虽然纹理贴图通常用于向渲染图像添加细节,但也有重要的可视化应用程序。

  • 纹理贴图可以作为数据的函数在程序上生成。一个示例是根据本地数据值更改表面的外观。

  • 纹理坐标可以作为数据的函数在程序上生成。例如,我们可以通过创建一个特殊的纹理贴图,然后根据本地数据值设置纹理坐标来对几何进行阈值处理。纹理贴图由两个条目组成:完全透明()和完全不透明 ()。如果标量值小于某个阈值,则将纹理坐标设置为索引到贴图的透明部分,否则设置为不透明部分。

  • 纹理贴图可以根据时间进行动画处理。通过选择一个强度从暗到亮单调变化的纹理贴图,然后沿着一个对象“移动”纹理,该对象似乎在纹理贴图运动的方向上爬行。我们可以使用这种技术为刺猬之类的物体添加明显的运动,以显示矢量幅度。图 7-3是用于模拟矢量场运动的纹理贴图动画示例。

图 7-3图 7-3。使用纹理的矢量场动画中的一帧。请参阅 AnimateVectors.cxxAnimateVectors.py

第 9 章将更详细地介绍这些技术。(有关更多信息,请参阅“纹理算法”。)

7.3 体绘制

到目前为止,我们一直专注于通过使用点、线和多边形等几何图元来实现数据的可视化。对于许多应用程序,例如建筑漫游或地形可视化,这显然是数据最有效和最有效的表示。相比之下,一些应用程序要求我们可视化本质上是体积的数据(我们将其称为 3D 图像或体积数据集)。例如,在生物医学成像中,我们可能需要可视化从 MR 或 CT 扫描仪、共聚焦显微镜或超声研究中获得的数据。天气分析和其他模拟也会产生大量三维或更多维度的体积数据,这些数据需要有效的可视化技术。由于过去几十年体积数据的普及和实用性,已经出现了称为体绘制的一类广泛的绘制技术。体绘制的目的是在体数据中有效地传达信息。

过去,研究人员试图将体绘制定义为直接在数据集上操作以生成图像而不生成中间几何表示的过程。随着图形硬件和智能实现的最新进展,开发人员已经能够使用几何图元来生成与直接体积渲染技术生成的图像相同的图像。由于这些新技术,几乎不可能以明显不同于几何渲染的方式定义体积渲染。因此,我们选择广义的体绘制定义为对体数据进行操作以生成图像的任何方法。

接下来的几节介绍了各种使用直接渲染技术、几何图元渲染技术或这两种方法的组合来生成图像的体积渲染方法。本章讨论的一些直接体绘制技术生成的图像与前面章节中讨论的几何绘制技术生成的图像几乎相同。例如,使用射线投射方法生成等值面图像与渲染几何图元相似,但并不完全等效,这些几何图元是使用第 6 章中描述的行进立方体轮廓技术提取的。

第 3 章中描述的两种基本表面渲染方法,图像顺序和对象顺序,也适用于体绘制技术。在图像顺序方法中,通过体积为图像平面中的每个像素投射光线以计算像素值,而在对象顺序方法中,遍历体积,通常是从前到后或从后到-前序,处理每个体素以确定其对图像的贡献。此外,还有其他体积渲染技术不能轻易归类为图像顺序或对象顺序。例如,体绘制技术可以同时遍历图像和体,或者可以在频域而不是空间域中计算图像。

由于体积渲染通常用于生成在 2D 图像中表示整个 3D 数据集的图像,因此引入了几个新的挑战。必须执行分类以将颜色和不透明度分配给体积内的区域,并且必须定义体积照明模型以支持着色。此外,由于体积渲染方法的复杂性和典型体积数据集的大小,效率和紧凑性非常重要。由一百万个图元组成的几何模型通常被认为是大的,而具有一百万个体素的体积数据集非常小。典型的卷包含十到几亿体素,十亿或更多体素的数据集变得越来越普遍。

7.4 图像顺序体绘制

图像顺序体绘制通常称为光线投射或光线追踪。其基本思想是,我们通过根据当前相机参数向场景中发送一条穿过像素的射线来确定图像中每个像素的值。然后,我们使用某些指定的函数评估沿射线遇到的数据,以计算像素值。正如我们将在本章中展示的那样,光线投射是一种灵活的技术,可用于渲染任何 3D 图像数据集,并且可以生成各种图像。此外,扩展为具有均匀体素的体积数据集设计的基本射线投射技术相对容易,以在直线或结构化网格上工作。不幸的是,基本的光线投射也相当慢。所以,

光线投射过程如图所示图 7-4. 此示例使用标准正交相机投影;因此,所有光线都相互平行并垂直于视平面。沿每条射线的数据值根据射线函数进行处理,在这种情况下,射线函数确定沿射线的最大值并将其转换为灰度像素值,其中体积中的最小标量值映射为透明黑色,而最大值标量值映射到不透明的白色。

射线投射的两个主要步骤是确定沿射线遇到的值,然后根据射线函数处理这些值。尽管在实施中这两个步骤通常是结合在一起的,但我们暂时将它们分开处理。由于特定的射线函数通常决定了沿射线提取值的方法,因此我们将从考虑一些基本的射线函数类型开始。

图7-4图 7-4图像顺序体绘制。高潜力铁蛋白数据由加利福尼亚州拉霍亚市斯克里普斯诊所提供。

图 7-5显示了光线通过 8 位体积数据时的数据值分布,其中数据值的范围可以在 0 到 255 之间。分布的x轴表示与视图平面的距离,而y轴表示数据值。从四种不同的简单射线函数获得的结果显示在轮廓下方。出于显示目的,我们使用类似于上一个示例中的方法将原始结果值转换为灰度值。

前两个射线函数,最大值和平均值,是对标量值本身的基本运算。第三个射线函数计算沿射线的距离,在该距离处第一次遇到等于或大于 30 的标量值,而第四个函数使用 alpha 合成技术,将沿射​​线的值视为每单位距离累积的不透明度样本。与前三个射线函数不同,合成技术的结果不是可以在射线轮廓上表示的标量值或距离。

最大强度投影或 MIP 可能是可视化体积数据的最简单方法。当涉及到嘈杂的数据时,这种技术相当宽容,并且生成的图像可以直观地理解基础数据。这种方法的一个问题是,无法从静止图像中分辨出沿射线出现最大值的位置。例如,考虑如下所示的颈动脉图像图 7-6. 我们无法从这张静止图像中完全了解血管的结构,因为我们无法确定某个血管是在其他血管的前面还是后面。这个问题可以通过生成显示数据旋转的小图像序列来解决,尽管对于平行相机投影,即使这个动画也是模棱两可的。这是因为从相反方向查看数据的相机生成的两个图像将是相同的,除了围绕图像的 Y 轴反射。

图7-5图 7-5。一个射线轮廓和四个示例射线函数。MRI 头部数据由新泽西州 Iselin 的 Siemens Medical Systems, Inc. 提供。

图7-6图 7-6。使用光线投射技术创建的最大强度投影。强度值在显示前通过图像底部显示的颜色查找表进行映射。

在本章后面的分类和照明讨论中,我们将考虑更复杂的光线函数。尽管新方法生成的彩色阴影图像可能包含更多信息,但它们也可能比前面示例中的简单图像更难以解释,而且往往更容易被误解。出于这个原因,使用多种技术来可视化您的体积数据是有益的。

体积表示为 3D 图像数据集,其中标量值定义在规则网格的点上,但在光线投射中,我们经常需要在任意位置对体积进行采样。为此,我们必须定义一个插值函数,该函数可以为网格点之间的任何位置返回一个标量值。最简单的插值函数,称为零阶、常数或最近邻插值,返回最近网格点的值。该函数定义了一个以网格点为中心的具有相同值的相同矩形框的网格,如左侧的 2D 所示图 7-7. 在右侧的图像中,我们看到了一个三线性插值的示例,其中某个位置的值是通过使用基于沿三个轴中每一个轴的距离的线性插值来定义的。通常,我们将由八个相邻网格点定义的区域称为体素。在离散算法与最近邻插值结合使用的特殊情况下,我们可以将恒定值区域称为体素。

为了沿射线遍历数据,我们可以以均匀的间隔对体积进行采样,或者我们可以通过体积遍历射线的离散表示,检查遇到的每个体素,如图所示图 7-8方法的选择取决于诸如插值技术、光线函数以及图像精度和速度之间所需的折衷等因素。

光线通常以参数形式表示为

屏幕截图 2022-11-20 140725

x0,y0,z0是射线的原点(透视观察变换的相机位置或平行观察变换的视图平面上的像素),并且(a,b,c)是归一化的射线方向向量。如果 t1 和 t2 分别表示光线进入和离开体积的距离,而 delta_t 表示步长,那么我们可以使用以下代码片段进行均匀距离采样:

t = t1;
v = undefined;
while ( t < t2 )
  {
  x = x0 + a * t;
  y = y0 + b * t;
  z = z0 + c * t;
  v = EvaluateRayFunction( v, t );
  t = t + delta_t;
}

图7-7图 7-7。最近邻插值的 2D 示例(左)和三线性插值的 3D 示例(右)。

图7-8图 7-8。体绘制的两种基本光线遍历方法。

图7-9图 7-9。使用三种不同步长的光线投射方法生成的图像。花瓶数据由纽约州立大学石溪分校提供。

均匀距离采样方法的一个难点是选择步长。如果步长太大,那么我们的采样可能会丢失数据中的特征,但是如果我们选择小步长,我们将显着增加渲染图像所需的时间。这个问题在图 7-9使用具有沿 X、Y 和 Z 轴相距一个单位的网格点的体积数据集。这些图像是使用 2.0、1.0 和 0.1 单位的步长生成的,其中 0.1 步长图像的生成时间是 1.0 步长图像的近 10 倍,而 1.0 步长图像的渲染时间是2.0 步长图像。使用合成方法生成图像,其中数据集中的标量值从透明黑色急剧过渡到不透明白色。如果步长太大,图像中会出现条带效应,突出显示沿观察光线与光线原点等距的体积区域。当出于性能原因需要更大的步长时,为了减少这种影响,每条光线的原点可以沿着观察方向向前碰撞一些小的随机偏移量,

在某些情况下,沿射线检查每个体素可能比采集样本更有意义。例如,如果我们使用最近邻插值方法来可视化我们的数据,那么我们可能能够使用离散光线遍历和整数算法来实现更有效的算法。检查体素的另一个原因可能是为了在某些射线函数上获得更好的精度。当使用三线性插值时,我们可以计算沿每个体素内的射线遇到的确切最大值,方法是沿射线取插值函数的一阶导数并求解所得方程以计算极值。类似地,我们可以沿着射线找到第一次遇到选定值的确切位置,以生成体积内等值表面的更好图像。

图7-10图 7-10。离散射线分类。

图7-11图 7-11。使用模板化离散射线进行射线投射。如果光线来自像平面(左),那么体积中的体素会丢失。相反,如果光线源自体积的基平面(右),则每个体素仅被访问一次。

可以使用 3D 扫描转换技术(例如改进的 Bresenham 方法)将连续光线转换为离散表示。离散射线是体素的有序序列v1,v2,...vn可分为 6 连接、18 连接或 26 连接,如图所示图 7-10. 每个体素包含 6 个面、12 条边和 8 个顶点。如果每对体素vi,vi+1沿着射线共享一个面,则该射线是 6 连通的,如果它们共享一个面或一条边,则该射线是 18 连通的,如果它们共享一个面、一条边或一个顶点,则该射线是 26 连通的。扫描转换和遍历 26 条连接的光线比 6 条连接的光线需要更少的时间,但更有可能遗漏体积数据集中的小特征。

如果我们使用并行查看变换,并且可以使用逐体素遍历方法有效地计算我们的光线函数,那么我们可以使用具有 26 条连接光线的模板化光线投射技术[Yagel92b]来生成图像。所有光线的方向相同;因此,我们只需要扫描一次转换,对每条射线使用这个“模板”。当这些光线从像平面上的像素投射出来时,如左图所示图 7-11,那么数据集中的一些体素对图像没有贡献。相反,如果我们将来自体素的光线投射到与图像平面最平行的体积的基本平面中,如右图所示,那么光线会紧密地结合在一起,这样数据集中的每个体素都会被访问一次。图像会出现扭曲,因为它是从基本平面生成的,因此需要最后的重新采样步骤才能将该图像投影回图像平面。

7.5 对象顺序体绘制

对象顺序体绘制方法基于数​​据集中体素的组织和当前相机参数处理体中的样本。当使用 alpha 合成方法时,体素必须以从前到后或从后到前的顺序遍历以获得正确的结果。此过程类似于在每次投影之前对半透明多边形进行排序,以确保正确混合。当使用图形硬件进行合成时,通常首选从后到前的排序,因为这样就可以执行 alpha 混合,而无需帧缓冲区中的 alpha 位平面。如果使用软件合成方法,则从前到后的排序更为常见,因为部分图像结果在视觉上更有意义,并且可用于在像素达到完全不透明度时避免额外处理。

图7-12图 7-12。对象顺序,从后到前的体积渲染。

图 7-12说明了一种简单的对象顺序、从后到前的方法来将体素投影到一个体积中以进行正交投影。体素遍历从距离视平面最远的体素开始,然后逐渐向更靠近的体素继续,直到所有体素都已被访问。这是在三重嵌套循环中完成的,其中从外循环到内循环,遍历体积中的平面,处理平面中的行,最后访问一行中的体素。图 7-12在投影体积时显示前七个体素的有序标记。以这种方式处理体素不会产生从最远到最近体素的严格排序。但是,它对于正交投影就足够了,因为它确实确保以正确的顺序处理投影到单个像素的体素。

图7-13图 7-13 . 将高斯核投影到视图平面上以产生 splat 足迹。

当处理体素时,确定其在视平面上的投影位置,并使用体素和图像信息在该像素位置执行操作。该运算符类似于图像顺序射线投射技术中使用的射线函数。尽管这种投影体素的方法既快速又高效,但由于投影图像像素的离散选择,它通常会产生图像伪影。例如,当我们将相机移近透视投影中的体积时,相邻体素将投影到视图平面上越来越远的像素,从而导致图像中分散注意力的“洞”。

一种称为喷溅的体积渲染技术通过将体素的能量分布在许多像素上来解决这个问题。Splatting 是 Westover [Westover90]提出的一种对象顺序体渲染技术,顾名思义,它一次将体素的能量投射到图像平面上一个 splat 或足迹。在每个数据样本周围放置一个有限范围的内核。足迹是该样本在图像平面上的投影贡献,通过沿观察方向集成内核并将结果存储在 2D 足迹表中来计算。图 7-13说明了高斯核在图像平面上的投影,然后可以用作飞溅足迹。对于平行观察变换和球对称内核,每个体素的足迹是相同的,除了图像空间偏移。因此,足迹表的评估和样本的图像空间范围可以作为体绘制的预处理步骤执行一次。由于所有样本的图像空间范围并不相同,因此透视体渲染更难喷溅。在喷溅方法中准确校正透视效果会使算法的效率大大降低。但是,如果我们用椭圆近似椭圆体的图像平面范围,我们仍然可以使用通用足迹表,但精度损失很小。

在使用 splatting 方法进行体渲染时,有几个重要的考虑因素。内核的类型、内核的半径和封装表的分辨率都会影响最终图像的外观。例如,小于相邻样本之间距离的核半径可能会导致图像出现间隙,而较大的半径会导致图像模糊。此外,低分辨率足迹表的预计算速度更快,但高分辨率表允许我们使用最近邻采样来加快渲染时间,而不会显着降低图像精度。

图7-14图 7-14。使用 2D(左)和 3D(右)纹理映射技术进行体积渲染。

本章前面描述的纹理映射最初是为了在渲染几何表面时提供高表面复杂性的外观而开发的。随着纹理映射方法的成熟并进入标准图形硬件,研究人员开始利用这些新功能来执行体渲染[Cabral94]。基于当前可用的两种主要类型的纹理硬件,有两种主要的纹理映射体渲染技术。二维纹理映射体绘制使用 2D 纹理映射硬件,而 3D 纹理映射体绘制使用不太常用的 3D 纹理映射图形硬件。

我们可以将纹理映射体渲染分解为两个基本步骤。第一个是采样步骤,其中使用某种形式的插值从体积中提取数据样本。根据可用纹理硬件的类型,这可能是最近邻、双线性或三线性插值,并且可以仅在硬件中执行,也可以通过软件和硬件技术的组合来执行。第二步是混合步骤,将采样值与帧缓冲区中的当前图像组合。这可能是一个简单的最大值运算符,也可能是一个更复杂的 alpha 合成运算符。

纹理映射体积渲染器通过投影一组跨越整个体积的纹理映射多边形来采样和混合体积以生成图像。在 2D 纹理映射体渲染中,数据集被分解为一组沿与观察方向最平行的体轴的正交切片。基本的渲染算法包括一个从后到前的顺序在正交切片上的循环,其中对于每个切片,一个 2D 纹理被下载到纹理内存中。每个切片是一个矩形多边形,被投影以显示整个 2D 纹理。如果相邻切片相对于图像大小相距甚远,则可能需要使用软件双线性插值方法从体积中提取额外切片以实现所需的图像精度。左侧的图像图 7-14说明了使用 2D 纹理映射方法渲染的正交切片。使用 2D 纹理映射体积渲染生成的几个示例图像显示在图 7-15.

图7-15图 7-15。2D 纹理映射体渲染。这些图像是使用三种不同的标量值到不透明度的映射生成的。CT 数据 (256x256x225) 由北卡罗来纳纪念医院提供。

该算法的性能可以分解为软件采样率、纹理下载率和纹理映射多边形扫描转换率。软件采样步骤是创建纹理图像所必需的,并且在访问存储在线性阵列中的体积数据时,由于缓存局部性,通常取决于视图方向。一些实现通过预先计算和保存三个主要体积方向的图像,以牺牲内存为代价最小化软件采样成本。纹理下载速率是该图像可以从主存储器传输到纹理映射存储器的速率。多边形的扫描转换通常受到图形硬件处理图像中像素的速率或像素填充率的限制。对于给定的硬件实现,卷的下载时间是固定的,不会根据查看参数而改变。但是,减小投影体积的相对大小将减少图形硬件处理的样本数量,这反过来又会以牺牲图像质量为代价提高体积渲染速率。

与 2D 硬件不同,3D 纹理硬件能够通过利用 3D 插值技术(例如三线性插值)在体积中的多个切片之间加载和插值。如果纹理内存大到足以容纳整个体积,那么渲染算法就很简单。作为预处理步骤,将整个体积下载到纹理内存一次。为了渲染图像,一组沿观察方向且平行于图像平面的等距平面被剪裁到体积上。生成的多边形,如右侧的图像所示图 7-14, 然后使用适当的 3D 纹理坐标以从后到前的顺序投影。

对于大体积,可能无法将整个体积加载到 3D 纹理内存中。这个问题的解决方案是将数据集分成足够小的子体积或砖块,以便每个砖块都适合纹理内存。然后必须以从后到前的顺序处理砖,同时计算砖内适当裁剪的多边形顶点。必须特别注意确保砖块之间的边界不会导致图像伪影。

图7-16图 7-16。在左侧,正交射线从体积的基本平面投射。在右图中,体积被剪切,使得这些射线垂直于基准平面。

与 2D 纹理映射方法类似,3D 算法受到机器的纹理下载和像素填充率的限制。然而,3D 纹理映射在对体积进行采样的能力方面优于 2D 版本,通常会产生更高质量的图像和更少的伪影。由于它能够执行三线性插值,我们能够在体积内的任何位置进行采样。例如,3D 纹理映射算法可以沿着代表同心球的多边形进行采样,而不是更常见的视图对齐平面。

理论上,3D 纹理映射体积渲染器和光线投射体积渲染器执行相同的计算,具有相同的复杂度On3,并产生相同的图像。两者都使用最近邻或三线性插值对整个体积进行采样,并使用例如最大值或合成函数组合样本以形成像素值。因此,我们可以将 3D 纹理映射和标准光线投射方法视为功能等效。使用纹理映射方法的主要优点是能够利用相对快速的图形硬件来执行采样和混合操作。然而,目前使用图形硬件进行体绘制有几个缺点。硬件纹理映射的体积渲染往往比软件光线投射技术具有更多的伪影,因为在混合期间用于在每个像素处存储部分结果的帧缓冲区内的精度有限。此外,硬件仅支持少数光线功能,而阴影等高级技术更难以实现。然而,随着纹理映射硬件的发展,这些限制开始消失。通过使用对 OpenGL 标准的扩展,可以定义每个像素矢量,从而允许硬件阴影体积纹理映射。其他扩展允许最大强度投影,并且更深的帧缓冲区消除了伪影。

7.6 其他体绘制方法

并非所有体绘制方法都完全属于图像顺序或对象顺序类别。例如,体绘制的剪切扭曲方法[Lacroute94]同时遍历图像和对象空间。这种方法背后的基本思想类似于模板化的光线投射。如果我们从体积的基平面投射光线以进行正交投影,则可以剪切体积以使光线垂直于基平面,如图所示图 7-16. 以这种方式看待问题,很明显,如果所有光线都源自基本平面上体素内的同一位置,那么这些光线与体积的每个后续平面上的体素在一致的位置相交。在数据集的 2D 平面上使用双线性插值,我们可以为每个平面预先计算一组插值权重。不是通过评估沿每条射线的样本来遍历体积,而是可以使用对象顺序遍历方法以从前到后的顺序通过体积来访问每个平面中每一行的体素。体积平面上的样本与图像平面上的像素之间存在一一对应的关系,从而可以同时遍历样本和像素。与模板化的光线投射一样,

剪切扭曲体积渲染本质上是光线投射的一种有效变体。样本和像素之间的对应关系使我们能够利用称为早期射线终止的标准射线投射技术。当我们确定一个像素在合成过程中达到完全不透明度时,我们不再需要考虑投射到该像素上的剩余样本,因为它们对最终像素值没有贡献。剪切扭曲体积渲染的最大效率改进来自于对体积进行游程编码。这种压缩方法从数据集中删除所有空的体素,只留下可能对图像有贡献的体素。根据数据的分类,可以实现大于 10:1 的体素减少。当我们逐步浏览压缩卷时,由于运行长度编码而跳过的体素数也表示图像中要跳过的像素数。这种方法的一个缺点是它需要三个压缩卷的副本以允许从所有视图方向从前到后遍历。此外,如果我们希望使用透视变换,那么我们可能需要遍历体积的所有三个压缩副本,以实现正确的遍历顺序。

体绘制也可以使用傅里叶切片投影定理[Totsuka92]执行,该定理指出,如果我们在包含中心且平行于图像平面的频域中提取体积切片,则该切片的 2D 频谱为相当于从图像平面上的像素对体积进行线积分获得的 2D 图像。因此,我们可以通过从 3D 傅里叶体积中提取适当的切片来对数据集进行体渲染,然后计算该切片的 2D 傅里叶逆变换。这使我们可以将图像渲染为O(n2logn)时间相对于O(n3)大多数其他体积渲染算法所需的复杂性。

实现频域体积渲染器时必须解决的两个问题是从傅立叶体积中提取切片时的高插值成本,以及存储傅立叶所需的高内存要求(通常每个样本两个双精度浮点值)体积。尽管可以使用此方法提供一些阴影和深度提示,但无法进行遮挡。

7.7 体积分类

对数据集中的相关感兴趣对象进行分类是生成体积渲染图像的关键步骤。此信息用于确定对象对图像的贡献以及对象的材料属性和外观。例如,数据样本是否对应于 CT 数据集中的骨骼的简单二元分类通常通过指定密度阈值来执行。当一个体素的标量值大于这个阈值时,它被分类为骨骼,否则它被认为是空气。这实质上指定了在空气和骨骼之间过渡的体积中的等值面。如果我们在所有可能的标量值上绘制这个操作,我们将得到左侧显示的二进制阶跃函数图 7-17. 在体绘制中,我们将此函数称为传递函数。传递函数负责将体素位置的信息映射为不同的值,例如材质、颜色或不透明度。体绘制的优势在于它可以处理比二元阶跃函数复杂得多的传递函数。这通常是必要的,因为数据集包含多种材料,而分类方法并不总是以 100% 的概率将单一材料分配给样本。使用先进的图像分割和分类技术,可以将单个成分体积处理成多个材料百分比体积[Drebin88]. 回到我们的 CT 示例,我们现在可以指定一个材料百分比传递函数,它定义了从空气到肌肉,然后从肌肉到骨骼的逐渐过渡,如右图所示图 7-17.

图7-17图 7-17。将 CT 密度分类为材料百分比的传递函数。显示了一个简单的二元分类,用于定义骨骼等值面(左)和从空气到肌肉到骨骼的逐渐过渡(右)。

除了材料百分比传递函数外,我们还可以定义四个独立的传递函数,将标量值映射为数据集中每种材料的红色、绿色、蓝色和不透明度值。为简单起见,这些传递函数集通常在分类阶段结束时被预处理为一个函数,分别用于红色、绿色、蓝色和不透明度。在渲染期间,我们必须决定如何执行插值来计算体积中任意位置的不透明度和颜色。我们可以插入标量值然后评估传递函数,或者我们可以评估网格点处的传递函数然后插入得到的不透明度和颜色。这两种方法会产生不同的图像结果。通常认为在网格点进行分类然后插值以获得颜色和不透明度更准确;虽然如果我们插值然后分类,图像通常看起来更令人愉悦,因为插值可能会去除高频。

仅基于标量值对体积进行分类通常无法隔离感兴趣的对象。Levoy [Levoy88]引入的一种技术将梯度幅度维度添加到传递函数的规范中。使用这种技术,我们可以根据标量值和梯度幅度的组合来指定体积中的对象。这使我们能够定义一个不透明度传递函数,该函数可以针对具有一定密度范围内的标量值和梯度幅度范围内的梯度的体素。这对于避免选择体积中的同质区域和突出显示快速变化的区域很有用。图 7-18显示了人脚的 CT 扫描。显示了体积的急剧变化,例如从空气到皮肤和肉到骨头的过渡。然而,同质区域,例如内部肌肉,大多是透明的。

图7-18图 7-18。使用梯度幅度不透明度传递函数进行体绘制。使用 Kitware 的 VolView 体积渲染系统进行渲染。Visible Man CT 数据由国家医学图书馆提供。

如果我们使用高阶插值函数,例如三三次插值,那么我们可以通过评估插值函数的一阶导数来解析计算数据集中任何位置的梯度向量。尽管我们可以将这种方法用于三线性插值,但它可能会产生不希望的伪影,因为三线性插值在其跨体素边界的一阶导数中是不连续的。另一种方法是采用有限差分技术来近似梯度向量:

屏幕截图 2022-11-20 141738

 

f(x,y,z)表示标量值根据插值函数在数据集中的位置,以及gz分别是该函数沿 x、y 和 z 轴的偏导数。梯度的大小在(x,y,z)是结果向量的长度(gx,gy,gz). 这个向量也可以被归一化以产生一个单位法线向量。$\Delta x、\Delta y、$ 和Δz是关键的,如图所示图 7-19. 如果这些值太小,则从公式7-3 导出的梯度向量场可能包含高频率,但如果这些值太大,我们将丢失数据集中的小特征。

通常情况下,基于标量值甚至梯度幅度的传递函数都不能完全分类体积。超声数据是特别困难的数据的一个例子,这些数据在简单的分割技术中表现不佳。虽然不存在一种普遍适用的技术,但存在多种在每个样本处产生分类信息的技术。例如,[Kikinis96]提供了对人脑进行分类的技术。为了正确处理此信息,体积渲染器必须访问原始体积和分类体积。分类体积通常包含每个样本的材料百分比,以及用于定义外观的每种材料的一组颜色和不透明度传递函数。

图7-19图 7-19。在正常估计期间使用两种不同步长的阴影图像的比较。共聚焦显微镜数据由纽约州立大学石溪分校霍华德休斯医学研究所提供。

7.8 体积照明

到目前为止,我们在本章中展示的体积渲染图像不包括任何光照效果。科学家有时更喜欢使用这些更简单的方法来可视化他们的体积,因为他们担心在图像中添加照明效果会干扰他们的解释。例如,在最大强度投影中,图像中的暗区域清楚地表明体积的相应区域中缺乏高不透明度值,而阴影图像中的暗特征可能表明不透明度值低或具有梯度方向的值远离光源的那个点。

照明有几个优点,通常可以证明图像中的额外复杂性是合理的。首先,考虑体绘制是从 3D 数据创建 2D 图像的过程这一事实。查看该数据的人希望能够从该图像中了解体积的 3D 结构。当然,如果您要查看骨骼的照片,则很容易从 2D 表示中理解其结构。您从图片中获得的两个主要线索是遮挡和照明效果。如果您要观看骨骼的视频,您会收到额外的运动视差线索。显示最大强度投影的静态图像不包括遮挡或照明效果,因此难以理解结构。使用合成技术生成的图像确实包括遮挡,并且可以修改合成光线功能以包括阴影。这三种方法的比较显示在图 7-20用于人脚的 CT 扫描。

图 7-20图 7-20。三种体绘制技术的比较。最大强度投影不包括遮挡或阴影。合成图像包括遮挡并且可以包括阴影。

为了准确捕捉光照效果,我们可以使用描述光强度的传输理论光照模型[Krueger91]I通过沿射线的路径积分到达一个像素:

屏幕截图 2022-11-20 142010

如果我们使用相机剪裁平面,那么t0将替换为到近剪裁平面的距离tnear以及到远裁剪平面的距离tfar分别。贡献Q(t)距离每个样本t沿光线衰减的强度取决于在途中损失的强度t至t0由于吸收σa(t)和散射σsc(t). 的贡献可以定义为:

屏幕截图 2022-11-20 142043

 

贡献包括样品直接发射的光量E(t),再加上来自各个方向的光量,这些光量被该样本沿光线散射回来。来自的光的比例ω分散到方向的方向ω由散射函数定义ρsc(ωω). 为了计算由于多次反射散射而从各个方向到达的光,我们必须递归地计算照明函数。

如果散射被精确建模,那么基于传输理论照明模型的射线函数将产生具有逼真照明效果的图像。不幸的是,这种照明模型太复杂而无法评估,因此实际实现需要近似值。最简单的近似之一是完全忽略散射,产生以下强度方程:

屏幕截图 2022-11-20 142110

 

我们可以进一步简化这个方程,让$\alpha (t) 表示沿射线每单位长度发射的光量和每单位长度吸收的光量。外部积分可以用在某个裁剪范围内沿射线的样本求和来代替,而内部积分可以使用 over 运算符来近似:

屏幕截图 2022-11-20 142136

 

该等式通常以其递归形式表示:

屏幕截图 2022-11-20 142211

 

这相当于前面描述的使用 over 运算符的简单合成方法。显然,在这种情况下,我们已将照明模型简化到该光线函数不会产生看起来逼真的图像的程度。

如果我们在体积数据中可视化等值面,那么我们可以使用第 3 章中描述的表面照明模型来捕捉环境光和漫反射光以及镜面高光。有多种技术可用于估计评估着色方程所需的表面法线。如果体积渲染生成的图像包含每个像素从视平面到表面的距离,那么我们可以使用 2D 梯度估计器对图像进行后处理以获得表面法线。某个像素的渐变xp,yp,是p可以通过中心差分技术估计:

屏幕截图 2022-11-20 142239

 

图7-21a不相交的体积对象

图7-21b对应深度图

图 7-21。用于 2D 梯度估计的场景(左)和相应的深度图像(右)。

结果被归一化以产生单位法线向量。与公式7-3 中给出的 3D 有限差分梯度估计器一样,在选择 $\delta x 和δy通常,这些值只是像素间距x和y以便使用相邻像素值来估计梯度,尽管可以使用较大的值来平滑图像。

上述 2D 梯度估计技术的一个问题是,法线是根据可能表示体积中不相交区域的深度值计算的,如图 7-21. 这可能会导致对象边缘的尖锐特征模糊。为了减少这种影响,我们可以在深度图像中定位连续曲率的区域,然后仅使用落在同一曲率区域内的其他像素值来估计像素的法线 [Yagel92a]。这可能需要减少我们的Δx和Δy值,或使用偏离中心的差异技术来估计梯度的分量。例如,梯度的 x 分量可以用前向差分计算:

屏幕截图 2022-11-20 142305

 

或向后的差异

屏幕截图 2022-11-20 142328

 

虽然 2D 梯度估计不如 3D 版本准确,但它通常更快,并且允许快速照明和表面属性更改,而无需我们重新计算深度图像。但是,如果我们希望在使用合成技术计算的图像中包含阴影效果,我们需要为每个像素估计体积内许多位置的梯度。3D 梯度估计技术更适合此目的。用于合成的照明方程可以写成:

屏幕截图 2022-11-20 142354

 

环境照明Ia, 漫射照明Id,以及镜面照明Is使用估计的体积梯度代替表面法线在表面着色中计算。在这个等式中,α(t)表示沿光线单位长度反射的光量,用1α(t)表示每单位长度透射的光的比例。

与分类一样,我们必须决定是直接计算体积中任意位置的光照,还是先计算网格点的光照,然后进行插值。根据准确性做出这不是一个困难的决定,因为在所需位置估计梯度显然比从相邻估计中插值更好。另一方面,如果我们从网格点进行插值,那么我们可以预先计算整个数据集的梯度,并使用它来提高分类和照明的渲染性能。主要问题是存储预先计算的梯度所需的内存量。一个简单的实现会为每个标量值的梯度的每个分量存储一个浮点值(通常是四个字节)。对于一个数据集2563单字节标量,这会将存储需求从 16 MB 增加到 218 MB。

为了减少存储需求,我们可以通过使用一些比特来表示向量的大小和一些其他数量的比特来编码向量的方向来量化预先计算的梯度。量化可以很好地存储梯度的大小,但如果我们简单地将位划分为向量的三个分量,则不能提供良好的方向分布。更好的解决方案是使用八面体均匀分形细分为球体作为方向编码的基础,如图所示图 7-22. 左上图显示了用四个新三角形递归替换每个三角形后获得的结果,递归深度为 2。在此表示中编码的矢量方向是通过创建源自球体中心并穿过球体顶点的射线形成的所有方向。此图中的其余图像说明了如何将这些方向映射到索引中。首先我们将所有顶点推回到八面体的原始面上,然后我们将这个球体展平到平面上z=0. 最后,我们将生成的网格旋转 45. 我们用从左上角顶点处的 0 开始的索引标记网格中的顶点,然后继续穿过行,然后沿着列向下到右下角顶点处的索引 40。这些索引仅代表编码法线的一半,因为当我们展平八面体时,除了边缘位置外,我们将两个顶点放在彼此的顶部。因此,我们可以使用索引 41 到 81 来表示具有负z分量的向量。边上的顶点表示没有z分量的向量,尽管我们可以用单个索引来表示它们,但使用两个可以使索引方案更加一致,因此更容易实现。

上面的简单示例只需要 82 个值来编码 66 个唯一向量方向。如果我们使用 unsigned short 来存储编码方向,那么我们可以在生成顶点时使用 6 的递归深度。这导致 16,642 个索引代表 16,386 个唯一方向。

一旦为我们的体积编码了梯度,我们只需要为每个可能的索引计算一次光照并将结果存储在一个表中。由于具有相同编码梯度方向的数据样本可能具有不同的颜色,因此该照明值表示阴影方程中与颜色无关的部分。每个标量值可以为环境、漫反射和镜面照明定义单独的颜色;因此,预先计算的光照通常是一个值数组。

尽管使用着色表会导致更快的渲染时间,但此方法存在一些限制。只有无限的光源才能被准确地支持,因为位置光源会因为它们在体积中的不同位置而导致具有相同梯度的数据样本的不同光矢量。此外,镜面高光仅针对正交观察方向准确捕获,其中视图矢量不随样本位置而变化。在实践中,位置光源通常由无限光源近似,并且单个视图方向用于计算镜面高光,因为对快速渲染的需求通常超过对准确照明的需求。

图7-22图 7-22。梯度方向编码。

图7-23

7.9 感兴趣区域

使用迄今为止提出的方法可视化体积数据的一个困难是,为了研究体积中心的某些特征,我们必须查看数据集中的其他特征。例如,如果我们正在可视化番茄数据集,那么我们将无法使用最大强度投影看到番茄内的种子,因为种子的强度低于周围的果肉。即使使用合成技术,也很难将种子可视化,因为在到达数据集的这个区域之前可能会获得完全的不透明度。

我们可以通过在我们的体积内定义一个感兴趣的区域来解决可视化内部特征的问题,并只渲染数据集的这一部分,如图所示图 7-23. 定义感兴趣区域的技术有很多。我们可以使用相机的近和远剪裁平面来排除体积的一部分。或者,我们可以使用六个正交剪裁平面来定义一个矩形子体积;我们可以使用一组任意方向的半空间剪裁平面;或者我们可以将感兴趣区域定义为包含在一组封闭几何对象中的体积部分。另一种方法是创建一个具有二进制标量值的辅助体积,该值定义一个掩码,指示在渲染期间应考虑体积中的哪些值。

图7-24图 7-24。使用几何和体积技术渲染的两个卷。可见女性 CT 数据由国家医学图书馆提供。

所有这些感兴趣区域的方法都非常容易使用图像顺序射线投射方法来实现。作为光线投射的预处理步骤,光线会根据所有几何区域定义进行裁剪。然后仅沿感兴趣区域内的射线段评估射线函数。在每个样本中查询掩码值以确定是否应包括或排除其贡献。

对于对象顺序方法,我们必须先确定每个样本是否在感兴趣区域内,然后再将其贡献到图像中。如果底层图形硬件被用于对象顺序体积渲染,就像纹理映射方法的情况一样,硬件裁剪平面可以用来帮助支持感兴趣的区域。

7.10 混合体积和几何

尽管体积通常是体积可视化中图像的焦点,但将几何对象添加到场景中通常很有帮助。例如,显示数据集的边界框或剖切面的位置和方向可以提高查看者对体积数据的理解。此外,在同一图像中使用几何和体积方法可视化体积数据也很有用。左图在图 7-24显示了人类膝盖的 CT 扫描,其中使用轮廓方法提取皮肤等值面。使用标准图形硬件将此等值面渲染为三角形。皮肤的右上部分被切割以露出下面的骨骼,这是使用具有合成射线功能的软件射线投射技术渲染的。在右图中,铁蛋白的波函数值使用几何等值面和体积渲染技术进行可视化。

当使用图形硬件来执行体渲染时,就像纹理映射方法一样,在场景中混合不透明的几何体是微不足道的。首先渲染所有不透明的几何体,然后将半透明的纹理映射多边形以从后到前的顺序混合到图像中。如果我们希望在场景中包含半透明几何体,则必须在渲染之前对该几何体和纹理映射的多边形进行排序。类似于纯几何场景,这可能涉及分割多边形以获得排序顺序。

如果使用软件体绘制方法,例如对象顺序喷射法或图像顺序射线投射法,则可以通过渲染几何体、捕获存储在硬件深度缓冲区中的结果并将不透明几何体合并到图像中,以及然后在体积渲染阶段使用这些结果。对于光线投射,我们只需将像素的深度值转换为沿视线的距离,并使用它来限制我们在体积渲染期间考虑的光线段。然后,在体积渲染期间为像素计算的最终颜色与使用 over 运算符通过几何渲染产生的颜色混合。在对象顺序方法中,我们必须考虑每个样本的深度,并将其与该样本图像范围内每个像素的深度缓冲区中存储的值进行比较。仅当样本位于该像素的几何图形前面时,我们才会在每个像素处累积该样本对体积渲染图像的贡献。最后,体积渲染图像与几何图像混合。

7.11 高效的体积渲染

渲染体积数据集是一项计算密集型任务。如果n是体积在所有三个维度上的大小,我们在投影期间访问每个体素一次,体积渲染的复杂性是O(n3). 即使是高度优化的软件算法也很难投射中等大小的体积或512×512×512以交互速率大约有 1.34 亿体素。如果体积中的每个体素都以某种方式对最终图像做出贡献,并且我们不愿意牺牲图像质量,那么我们提高效率的选择就会受到限制。然而,已经观察到许多体积数据集包含大面积的空数据或无趣数据,这些数据的不透明度值为0分类。此外,那些包含有趣数据的区域可能被连贯或几乎同质的区域占据。已经开发了许多利用这些观察结果的技术。

空间跳跃是指一类通用的效率改进技术,它试图避免处理对最终图像没有贡献的体积区域。一种经常使用的技术是构建一个八叉树数据结构,它分层地包含卷中的所有重要区域。八叉树的根节点包含整个卷,并有八个子节点,每个子节点代表1/8的音量。这八个子区域是通过沿xyz轴将体积分成两半来创建的。这种细分递归地继续,直到八叉树中的一个节点代表体积的同质区域。使用对象顺序渲染技术,在渲染期间将仅遍历八叉树的非空叶节点,从而避免所有空区域,同时有效地处理所有贡献的同质区域。类似地,图像顺序光线投射技术会通过叶节点投射光线,八叉树的规则结构允许我们快速跨过空节点。

混合空间跳跃技术[Sobierajski95]利用图形硬件在软件射线投射期间跳过体积的一些空白区域。首先,创建一个完全包含或包围体积中所有重要区域的多边形表示。然后将这个多边形表示投影两次——第一次在深度缓冲区上使用通常的小于运算符,第二次在深度缓冲区上使用大于运算符。这会生成两个深度图像,其中包含与图像中每个像素的相关数据的最近和最远距离。然后,这些距离用于在光线投射期间剪辑光线。

用于射线投射的另一种空间跳跃技术涉及使用辅助距离体积[Zuiderveld92],每个值表示与数据集中不透明样本的最近距离。这些距离值用于在体积的空白区域采取更大的步骤,同时确保我们不会跨过体积中的任何非透明特征。不幸的是,要准确计算距离体积的计算成本很高,需要额外的存储空间,并且每次修改体积的分类时都必须重新计算。

这些空间跳跃技术的一个困难是它们高度依赖数据。在具有少量连贯数据的大部分空体积上,我们可以显着加快体积渲染。但是,当遇到完全由高频信息组成的数据集(例如典型的超声数据集)时,这些技术就会失效,通常会导致渲染时间增加而不是减少。

7.12 交互式体绘制

根据包括硬件平台、图像大小、数据大小和渲染技术在内的各种因素,生成体积渲染图像可能需要几分之一秒到几十分钟不等。如果我们出于医疗诊断的目的生成图像,我们显然希望生成高质量的图像。另一方面,如果图像是在交互式会话期间生成的,那么实现所需的渲染更新速率可能更重要。因此,很明显,我们需要能够根据应用程序在必要时权衡质量与速度。与我们关于提高效率的讨论相反,这里描述的技术不能保持图像质量。相反,它们允许控制质量下降以实现速度。

由于图像顺序射线投射所需的时间主要取决于图像的像素大小和沿射线采集的样本数量,因此我们可以调整这两个值以实现所需的更新速率。可以使用最近邻或双线性插值方法从分辨率降低的图像生成全尺寸图像。如果使用双线性插值,则在交互过程中,沿每个图像维度投射的光线数量通常可以减少两倍,从而实现四倍的加速,而图像质量不会明显下降。通过更大的减少可以实现进一步的加速,但代价是图像模糊、细节较少。

如果我们不减少沿每条光线采集的样本数量,我们可以实现光线投射的渐进细化方法。在交互过程中,我们只能计算每条射线nth沿着每个图像维度并使用插值填充剩余的像素。当用户停止与场景交互时,内插像素逐渐填充其实际值。

有几种对象顺序技术可用于以牺牲图像质量为代价实现交互式渲染速率。如果使用喷溅算法,则渲染速度取决于数据集中体素的数量。可以预先计算数据的降低分辨率版本,并且可以在交互期间根据所需的帧速率选择分辨率级别。如果我们使用基于八叉树表示的喷溅方法,那么我们可以在每个父节点中包含一个近似标量值和一个误差值,其中误差值表示子节点中的标量值与父节点中的近似值有多少偏差节点。分层喷溅 [Laur91]只能通过降低八叉树来执行,直到遇到小于给定容错的节点。该体积区域对图像的贡献可以通过渲染 splat [Shirley90][Wilhelms91]的几何图元来近似。增加允许的误差将通过允许在八叉树中的更高级别近似更大的区域来减少渲染数据所需的时间。

当使用纹理映射方法进行体积渲染时,可以通过减少用于表示体积的纹理映射多边形的数量来实现更快的渲染速度。这基本上等同于减少图像顺序射线投射方法中沿射线拍摄的样本数量。此外,如果纹理下载速率是一个瓶颈,则可以将体积的降低分辨率版本加载到纹理内存中进行交互。这类似于在图像顺序方法中同时减少投射的光线数量和沿光线采集的样本数量。

7.13 体绘制的未来

在过去的二十年里,体绘制已经从一个需要数分钟才能在高端工作站上生成图像的算法的研究课题,发展成为一个活跃的开发领域,其中包括可用于家用计算机的商业软件。然而,随着对体积渲染的需求增加,挑战也随之增加。由于采集硬件的进步和体绘制在模拟和体图形等领域的日益普及,典型数据集中的体素数量正在增长[Kaufman93]. 需要新的方法来满足对这些大型数据集的高质量图像和交互性的冲突需求。此外,包含以离散时间间隔采样的体积数据的时间相关数据集对插值、图像精度和交互性提出了新的挑战,同时为分类和插值方法提供了新的机会。

本章中的大部分体积渲染讨论都集中在常规体积数据集上。尽管显然可以扩展大多数光线投射和对象顺序方法以可视化直线网格、结构化网格甚至不规则数据,但实际上很难同时提供高质量的图像和这些方法的交互性。这些数据类型的渲染技术仍然是体积可视化[Cignoni96][Silva96][Wilhelms96]中的一个活跃研究领域。

7.14 立体渲染

到目前为止,在我们的计算机图形实践中,我们使用了多种技术在 2D 显示设备上模拟 3D 图形。这些技术包括使用透视和比例、阴影赋予深度,以及运动/动画来查看对象的所有方面。然而,模拟 3D 观看的最有效技术之一是双眼视差

双目视差是用我们的两只眼睛观看 3D 物体的结果。由于每只眼睛看到的图片略有不同,我们的大脑会解释这些差异以确定我们视野中物体的深度。已经制作了许多利用我们的双眼视差的“3D”电影。通常,这些涉及在观看电影时佩戴一副特殊的眼镜。

这种效果在我们可视化复杂数据集和 CAD 模型的工作中可能很有价值。立体观看提供的附加深度提示有助于我们确定场景几何的相对位置以及形成场景的心理图像。有几种不同的方法可以将双目视差引入渲染。我们将整个过程称为立体渲染,因为在该过程中的某个时刻涉及到一对立体图像。

图7-25图 7-25。立体渲染和双目视差。

为了生成正确的左右眼图像,我们需要超出我们在第 3 章中介绍的相机参数的信息. 我们需要的第一条信息是眼睛之间的距离。可以通过调整这个距离来控制产生的视差量。我们还需要知道生成的图像是否会在一台或两台显示器上查看。对于使用两个显示器(因此使用两个视图平面)的系统,可以通过执行相机方位角以达到左眼和右眼位置来正确产生视差。头戴式显示器和吊杆是两种显示系统的示例。不幸的是,这不适用于只有一个视图平面的系统。如果您尝试在单个显示器上同时显示左视图和右视图,它们将被迫共享同一个视图平面,如图 7-25. 我们早期的相机模型假设视平面垂直于投影方向。为了处理这种非垂直情况,我们必须平移和剪切相机的视锥。Hodges 提供了该操作的一些细节以及立体渲染的一个很好的概述[Hodges92]

现在让我们看看向用户呈现立体图像的一些不同方法。大多数方法基于两个主要类别之一:时间复用技术时间并行技术。时间复用方法通过在左眼图像和右眼图像之间交替来工作。时间并行方法同时显示两个图像,并结合提取左眼和右眼视图的过程。一些方法可以实现为时间复用或时间并行技术。

时间复用技术最常见于单一显示系统,因为它们依赖于交替图像。通常,这与用于交替观察图像的哪只眼睛的方法相结合。一种具有成本效益的时间复用技术利用了现有的电视标准,例如 NTSC 和 PAL。这两种标准都使用隔行扫描,这意味着首先在屏幕上绘制偶数行,然后是奇数行。通过将左眼图像渲染到屏幕的偶数行,将右眼图像渲染到奇数行,我们可以生成适合在标准电视上显示的立体视频流。当用双眼观看时,它显示为一个不断从左到右跳跃的图像。必须佩戴一套特殊的眼镜,以便在显示左眼图像时,用户’ 左眼可以看到,右眼也可以看到。眼镜的设计使每个镜片都包含一个液晶快门,该快门可以是透明的,也可以是不透明的,具体取决于施加在其上的电压。通过以与电视隔行扫描相同的速度关闭眼镜,我们可以确保正确的眼睛正在观看正确的图像。

这个系统有几个缺点。NTSC 和 PAL 的分辨率都比电脑显示器低。NTSC (60 Hz) 和 PAL (50 Hz) 的刷新率会产生相当多的闪烁,尤其是当您考虑到每只眼睛都以该频率的一半更新时。此外,此方法需要在电视上查看图像,而不是在连接到计算机的显示器上。

为了克服这些困难,一些计算机制造商提供了支持立体声的图形卡。这些系统使用液晶快门眼镜直接查看计算机显示器。为了获得交替的立体图像,左眼图像被渲染到屏幕的上半部分,右眼图像被渲染到屏幕底部。然后显卡进入一种特殊的立体模式,它使显示器的刷新率加倍。因此,最初以 60Hz 显示两个图像的显示器开始以 120Hz 的速率在左眼和右眼之间交替显示。这导致每只眼睛以 60Hz 的频率更新,其原始水平分辨率和原始垂直分辨率的一半。要使此过程正常工作,您的应用程序必须在渲染时占据整个屏幕。

一些较新的图形卡具有用于立体渲染的左图像缓冲区和右图像缓冲区。虽然这需要更多的内存或更低的分辨率,但它确实提供了立体渲染,而无需占用整个屏幕。对于这样的卡,双缓冲与立体渲染相结合会产生四重缓冲,这会导致每个像素有大量的比特。例如:24 位用于 RGB 颜色,另外 24 位用于后台缓冲区的颜色,再加上 24 位用于 z 缓冲区,结果是每像素 72 位。现在,对于两个不同的视图,将其翻倍,对于 1K x 1K 显示器,您有每像素 144 位或 18 兆字节。

时间并行技术同时显示两个图像。头戴式显示器和吊杆有两个独立的屏幕,每只眼睛一个。要生成两个视频流,需要两个显卡或一个可以生成两个单独输出的显卡。然后,渲染过程只涉及将每只眼睛渲染到正确的图形卡或输出。目前,这种方法的最大缺点是所需的硬件成本。

相比之下,SIRDS(单图像随机点立体图)不需要特殊的硬件。两个视图都显示在一个图像中,如图 7-26. 要查看这样的图像,用户必须将注意力集中在图像的前面或后面。当用户的焦点正确时,图像顶部的两个三角形切口将显示为一个,并且图像应该显示为焦点。这是有效的,因为点图案以一定的间隔重复。在这里,结果图像中仅存在深度信息。这是通过改变模式之间的间隔来实现的,就像我们的视觉差异随着深度而变化一样。

接下来的两种立体渲染技术可以使用时间并行或时间复用方法来实现。区别有点模糊,因为大多数时候并行方法可以多路复用,尽管通常没有优势。电影业已使用这两种方法来制作“3D”电影。第一种通常称为红蓝(或红绿或红青)立体声,要求用户佩戴一副能过滤进入光的眼镜。左眼只能通过红色滤镜看到图像,而右眼只能通过蓝色滤镜看到图像。渲染过程通常涉及为两个视图生成图像,将它们的 RGB 值转换为强度,然后创建结果图像。该图像的红色值取自左眼图像强度。同样,蓝色值(蓝色和绿色的混合)取自右眼图像强度。生成的图像没有原始色调或饱和度,但它确实包含原始图像的强度。(附加说明:也使用红绿方法,因为人眼对绿色比对蓝色更敏感。)这种技术的好处是生成的图像可以显示在监视器、纸张或胶片上,而且所有一个需要查看它们的是一副便宜的眼镜。

图7-26图 7-26。四面体的单幅图像随机点立体图。

第二种技术类似于第一种,但它保留了原始图像中的所有颜色信息。它通过使用偏振光来分离不同的视图。通常,我们看到的光具有混合偏振角,但有些镜头可以滤除这些角度的一个子集。如果我们通过垂直偏振滤光片投影彩色图像,然后通过另一个垂直滤光片查看它,我们将看到原始图像,只是稍微暗一些,因为我们已经过滤掉了所有的水平偏振光。如果我们将水平滤镜和垂直滤镜放在一起,所有的光线都会被阻挡。偏振立体渲染通常通过垂直过滤器投射一只眼睛的图像,而通过水平过滤器投射另一只眼睛的图像。用户佩戴一副眼镜,其中一只眼睛上方有一个垂直滤光片,另一只眼睛上方有一个水平滤光片。这样,每只眼睛都能看到正确的图像。

图7-27图 7-27。线框图像和抗锯齿等效物。

我们讨论的所有立体渲染方法都有其优点和缺点,通常围绕成本和图像质量展开。在本章的最后,我们将看一个使用红蓝技术渲染立体图像的示例程序。

7.15 别名

在某一时刻,大多数计算机用户都遇到了别名问题。之所以会出现这种“阶梯式”,是因为我们用离散像素表示连续的表面几何形状。在计算机图形学中,最常见的锯齿问题是在渲染线或曲面边界时出现锯齿状边缘,如图 7-27.

锯齿问题源于光栅化过程,因为图形系统将图元(例如线段)转换为屏幕上的像素。例如,栅格化线的最快方法是使用全有或全无策略。如果线穿过像素,则将像素设置为线的颜色;否则,它不会被改变。可以看出图 7-28,这会导致阶梯状外观。

有几种处理锯齿问题的技术,它们统称为抗锯齿技巧。抗锯齿的一种方法是改变图形系统光栅化图元的方式。我们不是使用全有或全无的方法来光栅化一条线,而是查看这条线占据了多少像素。该像素的最终颜色是其原始颜色和线条颜色的混合。这两种颜色的比例由线路的占用率决定。这在主要使用线框模型时特别有效。类似的方法将每个像素分解为更小的子像素。使用全有或全无策略渲染基元,但采用亚像素分辨率。然后对子像素进行平均以确定最终像素的颜色。这往往需要更多的内存。

将每个像素分解为 10 个子像素可以获得不错的效果,这需要大约 10 倍的内存和渲染时间。如果您无法访问硬件亚像素渲染,您可以通过渲染大图像然后将其缩小来近似它。使用诸如 pnmscale 之类的程序进行双线性插值,您可以拍摄 1000 x 1000 像素的图像并将其缩小为 500 x 500 的抗锯齿图像。如果您有一个可以渲染到内存而不是屏幕的图形库,则可以将大图像(例如 6000 x 6000 像素)按比例缩小为高质量的结果,仍然是高分辨率(例如 2000 x 2000)。这似乎有点过头了,但是在标准的 600dpi 彩色打印机上,这将导致图像的一侧仅超过 3 英寸。

图7-28图 7-28。使用赢家通吃法(左)和覆盖法(右)绘制一个像素宽的线(灰色轮廓)。

我们将研究的最后一种抗锯齿方法使用累积缓冲区将几个可能出现锯齿的图像平均在一起,以产生一个抗锯齿结果。累积缓冲区只是为执行图像操作和存储而留出的一段内存。下面的 C++ 代码片段说明了这个过程。

for (imageNum = 0; imageNum < imageTotal; imageNum++)
  {
  //   Jitter the camera and focal point by less than one pixel
  //   Render an image
  //   add the image to the accumulation buffer
  }
//   Divide the accumulation buffer by imageTotal
//   Display the resulting anti-aliased image

我们可以使用八个没有子像素的图像,而不是使用每个像素有八个子像素的图像。抗锯齿是通过稍微平移每个图像之间的相机位置和焦点来实现的。平移量应在一个像素大小范围内,并垂直于投影方向。当然,相机的位置是在世界坐标而不是像素中指定的,但公式7-13 可以解决问题。我们根据偏移量计算新的相机位置和焦点(即p~new~f~new~)以避免围绕相机位置的变换矩阵的困难。

屏幕截图 2022-11-20 143317

 

图7-29图 7-29。显示焦深的三幅图像。第一张没有焦深,第二张聚焦在中心物体上,第三张图像聚焦在最远的物体上。

在这个等式中是Op像素坐标的偏移量,是世界坐标的偏移量,f相机焦点,p是相机位置,以及变换矩阵MWD和 $M_(DW} 分别从世界坐标转换为显示坐标和从显示坐标转换为世界坐标。

7.16 相机技巧

在上一节中,我们看到了如何结合累积缓冲区和小型相机平移来生成抗锯齿图像。在本节中,我们将介绍一些其他感兴趣的相机技术。您可能已经注意到,使用计算机生成的图像,所有演员都是焦点。使用真正的相机,您必须设置焦深以匹配您正在拍摄的物体的距离。任何比您的焦深更近或更远的东西都会显得失焦。这是因为真正的相机有一个镜头,可以让光线通过有限的区域。我们介绍的相机型号有一个点透镜,所有的光都在完全相同的点通过。(看图 7-29进行比较。)

我们可以通过渲染许多图像来模拟有限的相机镜头,每个图像的相机位置略有不同,但焦点相同。然后我们累积这些图像并取平均值。生成的图像模拟具有焦深的相机镜头。通过从您尝试模拟的镜头中选择随机点来确定不同的相机位置。较大直径的镜头会产生更多的失真,反之亦然。增加随机点的数量将提高结果的精度。通常需要 10 到 30 个样本。中的图像图 7-29使用 30 个样本点创建。

真实相机和计算机相机之间的另一个区别在于快门速度。我们的模型会在某一时刻生成一张图像;相比之下,照片在快门打开时捕捉相机所看到的内容。快速移动的物体由于在快门打开的一小段时间内位置发生变化而显得模糊。这种效果,称为运动模糊,也可以用我们的相机模型来模拟(图 7-30)。我们不是渲染一张图像并显示它,而是渲染一些累积、平均并最终显示的子帧。这类似于我们刚刚讨论的抗锯齿和焦点深度技术。在这两种技术中,相机都会抖动,而演员则保持固定的时间。为了实现运动模糊,我们不抖动相机;我们在每个子帧之间增加场景的时间。移动物体或相机移动将导致每个子帧之间的差异。生成的图像近似于在有限时间内拍摄移动物体的效果。

图7-30图 7-30。运动模糊。在胶片或录像带上录制时,快速移动的物体会显得模糊。为了用计算机相机模拟运动模糊,可以累积和平均多个图像(或子帧)。这个数字是通过累积 21 个子帧生成的。

7.17 基于鼠标的交互

毫无疑问,能够以交互方式查看对象有助于理解和识别其重要特征。使用定点设备(例如,鼠标或轨迹球)当然是控制此类移动的最常用方法。本书附带的软件包含vtkRenderWindowInteractor对象,该对象将鼠标和键盘事件转换为对摄像机和演员的修改。例如,当用户按住鼠标左键时,vtkRenderWindowInteractor将相机向当前指针位置旋转。指针离窗口中心越远,相机旋转得越快。

大多数这些交互都很简单,但也有一些与旋转相关的问题。当围绕一个对象旋转时,必须决定如何处理向上视图向量。我们可以在旋转时保持它与投影方向垂直,也可以保持不变。这导致两种不同类型的旋转。如果我们保持仰视向量与投影方向正交,我们将围绕对象旋转,就像飞机在地球上飞行一样。这显示在左半部分图 7-31. 如果我们保持仰视矢量不变,我们的飞机将在北极和南极开始向后飞行,如右半部分所示图 7-31.

恒定向上视图向量的优点是某些对象具有自然的上下感觉(例如,地形)。当我们在物体周围移动时,仰角和方位角操作保持一致。另一方面,存在一些奇异点,其中向上视图矢量和投影方向平行。在这些情况下,相机观察变换矩阵是未定义的。然后我们必须修改仰视向量或使用垂直仰视/投影方向的方法来处理这种情况。如果您正在处理的数据具有明确定义的上下,那么在旋转期间保持向上视图不变可能是有意义的;否则,保持它与投影方向正交是有意义的。

图7-31图 7-31。使用正交的向上视图向量(左)和恒定的向上视图向量(右)进行旋转。

7.18 3D 小部件和用户交互

第 3 章介绍了图形交互技术(参见“RenderWindowInteractor”)。在可视化的背景下,交互是提供数据探索和查询方法的系统的基本特征。vtkRenderWindowInteractorvtkInteractorStyle类是 VTK 中使用的核心构造,用于捕获渲染窗口中的窗口系统特定事件,将它们转换为 VTK 事件,然后根据该事件调用采取适当的行动。在第 3 章我们看到了如何使用这些类来操纵摄像机和演员以交互方式产生所需的视图。但是,此功能与数据交互的能力相对有限。例如,用户经常希望以交互方式控制流线起点的定位、控制剪切平面的方向或变换演员。虽然使用解释型语言(请参阅“解释型代码”)可以大大提供这种交互,但在某些情况下,在放置对象时查看您正在做什么的能力是必不可少的。因此,如果要成功支持现实世界的应用程序,可视化系统显然需要各种用户交互技术。

3D 小部件是大多数计算机系统上普遍存在的 2D 小部件的逻辑扩展,提供与 2D 对应物相似的交互功能,只是它们在更丰富的 3D 空间中运行。3D 小部件能够提供可视化系统所需的各种用户交互技术。然而,与 2D 小部件不同的是,3D 小部件是相对较新的技术,并且由于它们的应用处于更丰富的空间环境中,因此对于哪些小部件可能构成一套完整的功能还没有达成共识。几个流行的 3D 小部件集,以及犹他大学的 SCIRUN 3D 小部件[Purciful95], 在其小部件工具箱中有明显不同的组件。小部件集根据它们存在的图形环境的感知目的而有所不同,例如 Open Inventor [Wernecke94]、布朗大学 3D 小部件库[Zeleznik93]

3D 小部件是最近添加的(请参阅图 7-32) 到 VTK。原则上,核心功能很简单:由vtkRenderWindow捕获的事件依次转换为 VTK 事件。已经向vtkRenderWindow注册的观察者接收这些 VTK 事件,采取适当的行动,然后可以将事件传递给列表中的下一个观察者,或者中止对事件的进一步处理。(注意:观察者可以根据他们希望接收事件的顺序进行优先级排序。)

正是 3D 小部件的实现,即它们可以对事件做什么,才使它们如此强大。作为图 7-32如图所示,小部件通常在场景中提供可以选择和操作的表示。例如,vtkLineWidget可用于定位流线种子点的耙子,并用粗线(管)和两个球形端点表示自己。小部件也可以直接操作底层类——vtkScalarBarWidget使用户能够交互地调整大小、方向(水平或垂直)和定位 vtkScalarBar。小部件还提供附加功能,例如管理内部隐式函数或转换矩阵(例如vtkBoxWidget)。以下是当前在 VTK 中找到的小部件列表及其功能的简要说明。

  • vtkScalarBarWidget — 管理一个 vtkScalarBar,包括定位、缩放和定向。

  • vtkPointWidget —在 3D 空间中定位点xyz位置。小部件产生一个多边形输出。

  • vtkLineWidget — 放置一条具有指定细分分辨率的直线。小部件产生一个多边形输出。

  • vtkPlaneWidget — 定位和定位一个有限平面。平面分辨率是可变的,小部件产生隐式函数和多边形输出。

  • vtkImplicitPlaneWidget — 定向和定位一个无界平面。该小部件产生一个隐式函数和一个多边形输出。通过使用边界框裁剪平面来创建多边形输出。

  • vtkBoxWidget — 定位和定位边界框。小部件产生一个隐式函数和一个变换矩阵。

  • vtkImagePlaneWidget — 操作 3D 体积数据集中的三个正交平面。探测平面以获得数据位置、像素值和窗口级别是可能的。

  • vtkSphereWidget — 操纵可变分辨率的球体。该小部件生成一个隐式函数,一个转换矩阵,并启用对焦点和位置的控制,以支持诸如vtkCameravtkLight之类的类。

  • vtkSplineWidget — 操作一个插值的 3D 样条线。该小部件生成由一系列指定分辨率的线段表示的多边形数据。该小部件还直接管理每个xyz坐标值的底层样条线。

小部件设计的关键是仔细实施直观、简单的用户交互技术。例如,可以选择vtkLineWidget上的端点(表示为小球体)并将其拖动到新位置。vtkLineWidget支持修饰符“Shift”键来锁定端点沿坐标xyz轴的运动。初始运动方向用于确定用户正在沿哪个轴移动终点。这种对细节的关注对于成功的小部件设计至关重要,并将随着未来技术的发展而不断变化。

图7-32avtkScalarBarWidget

图7-32bvtkPointWidget

图7-32cvtkLineWidget

图7-32dvtkPlaneWidget

图 7-32evtkImplicitPlaneWidget

图7-32fvtkBoxWidget

图7-32gvtkImagePlaneWidget

图7-32hvtkSphereWidget

图7-32ivtkSplineWidget

图 7-32。在 VTK 中找到的一些 3D 小部件的应用。

7.19 放在一起

本章涵盖了广泛的主题。在本节中,我们将演示每个主题在一些简单问题上的应用。

纹理映射

图 7-33显示了一个简单纹理映射示例的完整源代码。您会注意到大部分代码与我们在前面的示例中使用的代码相似。这里的关键步骤是创建一个vtkTexture对象。该对象在其数据输入和图形库的纹理映射函数之间进行接口。vtkTexture实例与演员相关联多个actor之间可以共享多个纹理实例。为了使纹理映射正常工作,纹理坐标必须由演员的建模者定义。

关于vtkTexture对象的一个​​有趣的注释。此类的实例是具有在每次渲染期间更新的 Input 实例变量的映射器。输入类型是vtkImageData数据集类型。因此,可以构建可视化管道以读取、处理和/或生成纹理图。这包括使用对象vtkRendererSource,它将渲染器的图像转换为图像数据数据集。输入纹理贴图可以是 2D(像素图)或 3D(体积)。

使用纹理时的几句警告。一些渲染器只支持 2D 纹理,或者可能不支持 alpha 纹理。此外,许多渲染系统要求图像数据集的每个维度都是 2 的精确幂。在 VTK 中,两个纹理的非幂次自动转换为 2 的幂次,代价是额外的计算。

体积渲染

此示例侧重于体积渲染。示例中显示的源代码图 7-34首先创建通常的对象。然后我们使用vtkStructuredPointsReader读取高潜力铁蛋白的体积数据集。我们创建一个vtkPiecewiseFunction对象来将体积数据集中的标量值映射到不透明度,并创建一个vtkColorTransferFunction对象来将标量值映射到颜色。这两个传递函数引用自[vtkVolume](https://www.vtk.org/doc/nightly/html/classvtkVolume.html#details)Property对象。另外,我们使用[vtkVolume](https://www.vtk.org/doc/nightly/html/classvtkVolume.html#details)Property的ShadeOn()方法启用此体积的着色,并使用 SetInterpolationTypeToLinear() 方法请求三线性插值。由于我们使用的是光线投射方法,因此我们需要创建一个光线函数。在这个例子中,我们为此使用了一个vtkVolume RayCastCompositeFunction 对象。reader 的输出作为标量输入给vtkVolume RayCastMapper,使用 SetVolumeRayCastFunction() 方法分配光线函数。vtkVolume对象与vtkActor非常相似,并且使用 SetVolumeMapper() 和 SetVolumeProperty() 方法就像 vtkActor 的 SetMapper() 和 SetProperty() 方法一样. 最后,我们将这个体积添加到渲染器,调整相机,设置所需的图像更新率并启动交互器。

产生最大强度投影图 7-34,我们只需将光线函数的类型更改为 vtkVolumeRayCastMIPFunction。我们还可以使用 vtkVolumeRayCastIsosurfaceFunction 生成表面图像,其中 IsoValue 实例变量将设置为定义表面。

vtkBMPReader bmpReader
  bmpReader SetFileName "$VTK_DATA_ROOT/Data/masonry.bmp"
vtkTexture atext
 atext SetInputConnection [bmpReader GetOutputPort]
 atext InterpolateOn

vtkPlaneSource plane
vtkPolyDataMapper planeMapper
  planeMapper SetInputConnection [plane GetOutputPort]
vtkActor planeActor
  planeActor SetMapper planeMapper
  planeActor SetTexture atext

vtkRenderer ren1
vtkRenderWindow renWin
 renWin AddRenderer ren1
vtkRenderWindowInteractor iren
  iren SetRenderWindow renWin

#   Add the actors to the renderer ]
ren1 AddActor planeActor

图 7-33图 7-33。纹理映射示例。请参阅 TexturePlane.cxxTexturePlane.py

红蓝立体声

在我们的第一个示例中,我们将研究使用红蓝立体渲染。我们从中所示的示例开始图 7-35,它呈现类似于狼牙棒的东西。那么,在图 7-35我们通过在底部附近添加两条调用 StereoRenderOn() 和 SetStereoType() 方法的行来添加红蓝立体渲染。一旦这两个方法被调用,进一步的渲染将在立体中完成。右上角的图片显示了生成图像的灰度版本。

运动模糊

在我们的第二个示例中,我们展示了如何使用Visualization Toolkit模拟运动模糊。如图所示图 7-36,我们从前面的例子开始。然后我们移除控制立体渲染的两条线并添加几条线来创建另一个钉头锤。我们将第一个 mace 放在渲染窗口的顶部,将第二个 mace 放在底部。然后我们使用 SetSubFrames() 方法开始执行子帧累积。在这里,我们将执行 21 次渲染以生成最终图像。为了使运动模糊变得明显,必须有东西在移动,所以我们设置了一个循环,在每个子帧之间将底部的钉头锤旋转 2 度。在 21 个子帧上,它将从初始位置旋转 40 度。重要的是要记住,在渲染所需数量的子帧之前,不会显示结果图像。

#  Create the standard renderer, render window and interactor
vtkRenderer ren1
vtkRenderWindow renWin
  renWin AddRenderer ren1
vtkRenderWindowInteractor iren
   iren SetRenderWindow renWin

#  Create the reader for the data
vtkStructuredPointsReader reader
  reader SetFileName "$VTK_DATA_ROOT/Data/ironProt.vtk"

#  Create transfer mapping scalar value to opacity
vtkPiecewiseFunction opacityTransferFunction
  opacityTransferFunction AddPoint 20 0.0
  opacityTransferFunction AddPoint 255 0.2

# Create transfer mapping scalar value to color
vtkColorTransferFunction colorTransferFunction
  colorTransferFunction AddRGBPoint 0.0 0.0 0.0 0.0
  colorTransferFunction AddRGBPoint 64.0 1.0 0.0 0.0
  colorTransferFunction AddRGBPoint 128.0 0.0 0.0 1.0
  colorTransferFunction AddRGBPoint 192.0 0.0 1.0 0.0
  colorTransferFunction AddRGBPoint 255.0 0.0 0.2 0.0

#   The property describes how the data will look
vtkVolumeProperty volumeProperty
  volumeProperty SetColor colorTransferFunction
  volumeProperty SetScalarOpacity opacityTransferFunction
  volumeProperty ShadeOn
  volumeProperty SetInterpolationTypeToLinear

#  The mapper / ray cast function know how to render the data
vtkVolumeRayCastCompositeFunction compositeFunction
vtkVolumeRayCastMapper volumeMapper
  volumeMapper SetVolumeRayCastFunction compositeFunction
  volumeMapper SetInputConnection [reader GetOutputPort]

# Set the mapper and the property and
vtkVolume volume
  volume SetMapper volumeMapper
  volume SetProperty volumeProperty

ren1 AddVolume volume
renWin Render

图 7-34图 7-34。高潜力铁蛋白的体积渲染。请参阅 SimpleRayCast.cxxSimpleRayCast.py

vtkRenderer *ren1 = vtkRenderer::New();
vtkRenderWindow *renWin =
      vtkRenderWindow::New();
  renWin->AddRenderer(ren1);
vtkRenderWindowInteractor *iren =
       vtkRenderWindowInteractor::New();
  iren->SetRenderWindow(renWin);

//   create the pipline, ball and spikes
vtkSphereSource *sphere =
      vtkSphereSource::New();
  sphere->SetThetaResolution(7);
  sphere->SetPhiResolution(7);
vtkPolyDataMapper *sphereMapper = vtkPolyDataMapper::New();
  sphereMapper->SetInputConnection(sphere->GetOutputPort());
vtkActor*sphereActor = vtkActor::New();
  sphereActor->SetMapper(sphereMapper);

vtkConeSource *cone = vtkConeSource::New();
  cone->SetResolution(5);
vtkGlyph3D *glyph = vtkGlyph3D::New();
  glyph->SetInputConnection(sphere->GetOutputPort());
  glyph->SetSourceConnection(cone->GetOutputPort());
  glyph->SetVectorModeToUseNormal();
  glyph->SetScaleModeToScaleByVector(); glyph->SetScaleFactor(0.25);
vtkPolyDataMapper *spikeMapper = vtkPolyDataMapper::New();
  spikeMapper->SetInputConnection(glyph->GetOutputPort());
vtkActor *spikeActor = vtkActor::New();
  spikeActor->SetMapper(spikeMapper);

ren1->AddActor(sphereActor);
ren1->AddActor(spikeActor);
ren1->SetBackground(0.2,0.3,0.4);
renWin->SetSize(300,300);

renWin->Render();
ren1->GetActiveCamera()->Zoom(1.4);
renWin->StereoRenderOn();
renWin->SetStereoTypeToRedBlue();
renWin->Render();

图7-35图 7-35。红蓝立体渲染的一个例子。

//changes and additions to the
// preceding example's source
vtkActor *spikeActor2 = vtkActor::New();
  spikeActor2->SetMapper(spikeMapper);

spikeActor2->SetPosition(0,-0.7,0);
sphereActor2->SetPosition(0,-0.7,0);

ren1->AddActor(sphereActor2);
ren1->AddActor(spikeActor2);

// zoom in a little ren1->GetActiveCamera()->Zoom(1.5);
renWin->SetSubFrames(21);

for (i = 0; i <= 1.0; i = i + 0.05)
  {
  spikeActor2->RotateY(2);
  sphereActor2->RotateY(2);
  renWin->Render();
}

iren->Start();

图 7-36图 7-36。运动模糊的示例。请参阅 MotionBlur.cxxMotionBlur.py

焦深

现在我们将改变前面的例子来说明焦点深度。首先,我们改变底部狼牙棒的位置,使其远离我们。由于距离较远,它会显得更小,因此我们将其缩放两倍以保持合理的图像尺寸。然后我们删除渲染子帧的代码,而是设置焦深渲染的帧数。我们还将相机的焦点和焦点设置为适当的值。生成的图像和源代码所需的更改显示在图 7-37.

图 7-37图 7-37。轮廓示例。请参阅 CameraBlur.cxxCameraBlur.py

vtkLineWidget

VTK 中有多种 3D 小部件,它们的功能都类似。3D 小部件是vtkInteractorObserver的子类,这意味着它们与vtkRenderWindow相关联并观察渲染窗口()中的事件。(注意:vtkInteractorStyle —参见“RenderWindowInteractor”)—也是vtkInteractorObserver的子类。交互器样式与 3D 小部件的不同之处在于它在场景中没有表示形式。)以下示例以vtkLineWidget为例展示了使用 3D 小部件的一般方法(图 7-39)。首先,小部件被实例化,然后被放置。放置意味着定位、缩放和定向与它们操作的对象一致的小部件。默认情况下,小部件通过“keypress-i”事件启用,但可以修改启用小部件的特定事件。

图7-38
图 7-38。3D 小部件的部分类层次结构。每个 3D 小部件都观察一个特定的 vtkRenderWindow,类似于 vtkInteractorStyle。与用于操纵相机的 vtkInteractorStyle 不同,3D 小部件在场景中具有可以直接操纵的表示。多个 vtkInteractorObserver 可以在给定时间观看一个 vtkRenderWindow,因此像 vtkInteractorEventRecorder 这样的类可以记录一个事件并将它们传递给下一个观察 vtkRenderWindow 的 vtkInteractorObserver。

小部件通过命令/观察者事件处理机制与应用程序交互(请参阅“事件和观察者”。3D 小部件调用多个事件,最重要的是 StartInteractionEvent、InteractionEvent 和 EndInteractionEvent。这些事件通常被调用,例如,在分别是鼠标按下、鼠标移动和鼠标抬起。在此处显示的示例中,Tcl 过程使用 AddObserver() 方法与 StartInteractionEvent 和 InteractionEvent 相关联,以在操作小部件时产生流线。注意,流线以每次使用 GetPolyData() 方法调用 InteractionEvent 时,多边形数据集。管道更新机制会在下一次渲染时自动执行,因为流线的输入被修改。

图 7-39使用vtkLineWidget在燃烧器数据集中生成流管。StartInteractionEvent 打开流线的可见性;InteractionEvent 导致流线重新生成自身( LineWidget.tcl )。

7.20 章节总结

Alpha opacity 是一种模拟透明对象的图形方法。合成是按顺序混合半透明样本的过程。Alpha 合成需要正确排序数据。

纹理映射是一种强大的技术,可以在没有大量几何建模的情况下将额外的细节引入图像。将 2D 纹理贴图应用于对象表面类似于粘贴图片。纹理贴图的位置通过纹理坐标指定。

体绘制是一种强大的渲染技术,用于查看不均匀对象的内部。大多数体绘制技术可以分为图像顺序或对象顺序,尽管有些是两者的组合,而另一些则不属于任一类别。对象顺序技术通常以从前到后或从后到前的顺序合成体素。图像顺序技术通过图像平面中的像素投射光线以对体积进行采样。其他方法可以同时遍历图像和体积,或者可以在频域中操作。对于体积数据的有效可视化,分类和着色是重要的考虑因素。感兴趣区域可用于减少图像中可见的数据量。由于体绘制算法的复杂性,

立体渲染技术为右眼和左眼创建两个独立的视图。这模拟了双眼视差,让我们能够看到图像的深度。时间复用技术快速连续交替左眼视图和右眼视图。时间并行技术同时显示两个图像。

光栅设备经常受到混叠效应的影响。抗锯齿技术用于最小化锯齿的影响。这些技术创建了柔化硬边缘边界的混合图像。

通过使用累积缓冲区,我们可以创建有趣的效果,包括运动模糊和焦点模糊。在运动模糊中,我们在演员移动时累积多个渲染。为了模拟焦点模糊,我们抖动相机位置并保持其焦点不变。

有效的可视化本质上是交互式的。不仅不同类型的数据需要相机操作模型,交互、查询和修改数据的方法也必不可少。3D 小部件是为此做出的重要贡献。它们通过可以轻松操作的场景中的表示形式为数据提供直观的图形界面。3D 小部件还生成补充信息,例如可应用于场景中对象的隐式函数、输出多边形数据和变换矩阵。

7.21 书目注释

体绘制和体可视化技术的概述可以在 Kaufman [Kaufman91]的教程中找到。本章讨论的许多体绘制技术也可以从研究机构作为源代码访问。剪切扭曲算法在 VolPack 渲染库中提供,可在 Web 上的 graphics.stanford.edu/software/volpack/ 上获得。纽约州立大学石溪分校为非营利组织和政府组织提供称为 VolVis 的交钥匙体积可视化系统。源代码和可执行版本可在http://www.cs.sunysb.edu/\~volvis获得。此外,还提供了一个名为 Vis5D 的应用程序,该应用程序将体积可视化技术应用于随时间变化的大气天气数据。Vis5D 可从 Web 位置获得http://vis5d.sourceforge.net在 The Visualization Toolkit之上开发的商业体绘制应用程序 VolView可从 Kitware 获得 30 天试用版,网址为http://www.kitware.com/products/volview.html

7.22 参考文献

[Cabral94] B. Cabral,N. Cam,J. Foran。“使用纹理映射硬件加速体积渲染和断层重建。” 在1994 年体可视化研讨会论文集上。第 91–98 页,1994 年 10 月。

[Cignoni96] P. Cignoni、C. Montani、E. Puppo、R. Scopigno。“从不规则体积数据中提取最佳等值面。” 在1996 年体可视化研讨会论文集上。第 31–38 页,IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1996 年 10 月。

[Drebin88] RA Drebin、L. Carpenter、P. Hanrahan。“体积渲染。” 计算机图形学。22(4):64–75 (Siggraph 1988)。

[Hodges92] LF 霍奇斯。“教程:时间复用立体计算机图形学。” IEEE 计算机 图形与应用。1992 年 3 月。

[考夫曼91] A.考夫曼(编辑)。体积可视化。IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1991 年。

[考夫曼93] A.考夫曼,R.雅格尔,D.科恩。“体积图形。” IEEE 计算机。26(7):51–64,1993 年 7 月。

[Kelly94] M. Kelly、K. Gould、S. Winner、A. Yen。“硬件加速渲染 CSG 和透明度。” 计算机图形学(SIGGRAPH ’94)。第 177-184 页。

[Kikinis96] R. Kikinis, M. Shenton, D. Iosifescu, R. McCarley, P. Saiviroonporn, H. Hokama, A. Robatino, D. Metcalf, C. Wible, C. Portas, R. Donnino, F. Jolesz . “用于手术计划、模型驱动的分割和教学的数字脑图谱。” IEEE 可视化和计算机图形学汇刊。2(3),1996 年 9 月。

[克鲁格91] W.克鲁格。“传输理论在 3D 标量数据场可视化中的应用。” 物理学中的计算机。第 397-406 页,1994 年 7 月/8 月。

[Lacroute94] P. Lacroute 和 M. Levoy。“使用视图转换的剪切-扭曲分解进行快速体积渲染。” 在SIGGRAPH ’94 会议记录中。第 451-458 页,Addison-Wesley,马萨诸塞州雷丁,1994 年。

[Laur91] D. Laur 和 P. Hanrahan。“分层喷溅:一种用于体积渲染的渐进细化算法。” 在SIGGRAPH ’91 会议记录中。25:285–288, 1991。

[Levoy88] M. Levoy。“从体积数据显示表面。” IEEE 计算机图形学和应用程序8 (3),第 29–37 页,1988 年 5 月。

[Purciful95] JT Purciful。“用于科学可视化和动画的三维小部件。”硕士论文,计算机科学系,联合国 iv。犹他州,1995 年。

[Shirley90] P. 雪莉和 A. Tuchman。“直接体积渲染的多边形近似。” 计算机图形学。24(5):63–70, 1990。

[Silva96] C. Silva,JSB 米切尔,AE 考夫曼。“不规则网格的快速渲染。” 在1996 年体可视化研讨会论文集 。第 15–22 页,IEEE 计算机学会出版社,加利福尼亚州洛斯阿拉米托斯,1996 年 10 月。

[Sobierajski95] L. Sobierajski 和 R. Avila。“一种用于体积光线追踪的硬件加速方法。” 在可视化论文集 ’95中。第 27-34 页,IEEE 计算机协会出版社,加利福尼亚州洛斯阿拉米托斯,1995 年 10 月。

[Totsuka92] T. Totsuka 和 M. Levoy。“频域体绘制”。计算机图形学(SIG-GRAPH ’93)。第 271–278 页,1993 年 8 月。

[Wernecke94] J. Wernecke。发明家导师。Addison-Wesley,读 MA,1994 年。

[Westover90] L. Westover。“体积渲染的足迹评估”。计算机图形学(SIGGRAPH ’90)。24(4):36,1990。

[Wilhelms91] J. Wilhelms 和 A. Van Gelder。“一种用于直接体积渲染的相干投影方法。” 计算机图形学(SIGGRAPH ’91)。25(4):275–284, 1991。

[Wilhelms96] JP Wilhelms、A. Van Gelder、P. Tarantino、J. Gibbs。“不规则和多个网格的分层和可并行化直接体积渲染。” 在可视化 ’96 论文集。第 73-80 页,IEEE 计算机协会出版社,加利福尼亚州洛斯阿拉米托斯,1996 年 10 月。

[Yagel92a] R. Yagel、D. Cohen 和 A. Kaufman。“3D 离散空间中的正态估计。” 视觉 计算机。第 278–291 页,1992 年。

[Yagel92b] R. Yagel 和 A. Kaufman。“基于模板的体积查看”。在Eurographics ’92 的会议记录中。第 153–167 页,1992 年 9 月。

[Zeleznik93] RC Zeleznik、KP Herndon、DC Robbins、N. Huang、T. Meyer、N. Parker、JF Hughes。“用于构建 3D 界面的交互式工具包。” 计算机图形学(Siggraph ’93 论文集)。27(4):81–84。1993 年 7 月。

[Zuiderveld92] KJ Zuiderveld、A. hj Koning 和 MA Viergever。“使用 3D 距离变换加速光线投射。” In Proceedings of Visualization and Biomedical Computing,第 324–335 页,1992 年 10 月。

© 版权声明
THE END
喜欢就支持一下吧
点赞1.1W+ 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容