Visualization Toolkit官方开发文档翻译(4)–第三章计算机图形学入门

计算机图形是数据可视化的基础。实际上,可视化是将数据转换为一组图形基元的过程。然后使用计算机图形学的方法将这些图元转换为图片或动画。本章讨论基本的计算机图形学原理。我们首先描述光和物理对象如何相互作用以形成我们所看到的。接下来我们研究如何使用计算机图形技术模拟这些交互。硬件问题在这里起着重要作用,因为现代计算机具有对图形的内置硬件支持。本章以一系列示例结束,这些示例说明了我们面向对象的 3D 计算机图形模型。

3.1 简介

计算机图形学是使用计算机生成图像的过程。我们将此过程称为渲染。有许多类型的渲染过程,从 2D 绘图程序到复杂的 3D 技术。在本章中,我们将重点介绍用于可视化的基本 3D 技术。

我们可以将渲染视为将图形数据转换为图像的过程。在数据可视化中,我们的目标是将数据转换为图形数据或图形基元,然后进行渲染。我们渲染的目标与其说是照片真实感,不如说是信息内容。我们还努力实现交互式图形显示,通过它可以直接操作基础数据。本章解释了从图形数据渲染图像的过程。我们首先观察灯光、相机和物体(或actor)在我们周围世界中的交互方式。在此基础上,我们解释如何在计算机上模拟此过程。

渲染的物理描述

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

图 3-1展示了当我们查看一个对象时会发生什么的简化视图,在本例中是一个立方体。光线从光源向各个方向发射。(在这个例子中,我们假设光源是太阳。)这些光线中的一些碰巧照射到立方体上,立方体的表面吸收了一些入射光并反射了其余的光。其中一些反射光可能会朝向我们并进入我们的眼睛。如果发生这种情况,那么我们“看到”了该对象。同样,来自太阳的一些光会照射到地面,其中一小部分会反射到我们的眼睛中。

正如你可以想象的那样,一束光线从太阳穿过太空撞击相对较小行星上的小物体的可能性很低。光线会从物体反射并进入我们眼睛的可能性很小,这使情况更加复杂。我们能看到的唯一原因是太阳产生的光量如此之大,以至于它压倒了可能性。虽然这可能在现实生活中有效,但尝试用计算机模拟它可能很困难。幸运的是,还有其他方法可以解决这个问题。

一种常见且有效的 3D 计算机图形技术称为光线追踪光线投射. 光线追踪通过跟踪每条光线的路径来模拟光与物体的相互作用。通常,我们从观察者的眼睛向后跟踪光线并进入世界以确定光线所击中的内容。光线的方向是我们正在看的方向(即观察方向),包括透视效果(如果需要)。当光线与物体相交时,我们可以确定该点是否被我们的光源照亮。这是通过从交点向光线追踪光线来完成的。如果光线与光相交,则该点被照亮。如果光线在到达光线之前与其他物体相交,那么光线将不会有助于照亮该点。对于多个光源,我们只需对每个光源重复此过程。所有光源的总贡献,加上任何环境散射光,将确定该点的总照明或阴影。通过向后跟踪光线的路径,光线追踪只查看最终进入观察者眼睛的光线。这大大减少了必须由模拟程序计算的光线数量。

将光线追踪描述为渲染过程后,图形社区的许多成员不使用它可能会令人惊讶。这是因为光线追踪是一种相对较慢的图像生成方法,因为它通常在软件中实现。已经开发了使用专用计算机硬件生成图像的其他图形技术。要了解为什么会出现这种情况,简要检查计算机图形学的分类和历史是有益的。

图像顺序和对象顺序方法

渲染过程可以分为两类:图像顺序对象顺序。光线追踪是一个图像排序过程。它的工作原理是确定每一束光线会发生什么,一次一束。对象顺序过程通过一次渲染每个对象来工作。在上面的示例中,对象顺序技术将首先渲染地面,然后渲染立方体。

以另一种方式看待它,考虑画一张谷仓的照片。使用图像顺序算法,您将从画布的左上角开始,并放下一滴正确颜色的颜料。(每一滴颜料称为一个图片元素或像素。)然后你会向右移动一点并放下另一滴颜料。您将继续直到到达画布的右边缘,然后您将向下移动一点并从下一行开始。每次你放下一滴颜料,你都要确定它是画布上每个像素的正确颜色。完成后,您将拥有一幅谷仓的画作。

另一种方法是基于更自然的(至少对许多人来说)对象顺序过程。我们通过绘制场景中的不同对象来工作,与对象在场景中的实际位置无关。我们可以从后到前、从前到后或以任意顺序进行绘制。例如,我们可以从绘制天空开始,然后添加地面。画完这两个物体后,我们将在谷仓中添加。在图像排序过程中,我们以非常有序的方式在画布上工作;从左到右,从上到下。在对象顺序过程中,我们倾向于从画布的一个部分跳到另一部分,这取决于我们正在绘制的对象。

计算机图形领域开始使用对象顺序过程。早期的大部分工作都与硬件显示设备密切相关,最初是矢量显示。这只不过是一台示波器,但它鼓励将图形数据绘制为一系列线段。随着原始矢量显示让位于当前无处不在的光栅显示,将图形数据表示为一系列要绘制的对象的概念得以保留。Bresenham 开创的许多早期工作[Bresenham65]在 IBM 的工作重点是如何正确地将线段转换为适合线绘图仪的​​形式。同样的工作也适用于将线条渲染到取代示波器的光栅显示器上的任务。从那时起,硬件变得更加强大,并且能够显示比线条更复杂的图元。

直到 1980 年代初,Turner Whitted [Whitted80]的一篇论文才促使许多人从更物理的角度看待渲染。最终,光线追踪成为传统对象顺序渲染技术的重要竞争对手,部分原因在于它可以生成高度逼真的图像。对象顺序渲染一直保持其流行,因为有大量的图形硬件设计用于快速渲染对象。光线追踪往往在没有任何专门硬件的情况下完成,因此是一个耗时的过程。

表面与体积渲染

文中对这一点的讨论默认假设当我们渲染一个对象时,我们正在查看对象的表面及其与光的相互作用。然而,常见的物体,如云、水和雾,是半透明的,或散射穿过它们的光。此类对象不能使用仅基于表面交互的模型来渲染。相反,我们需要考虑对象内部不断变化的属性才能正确渲染它们。我们将这两种渲染模型称为表面渲染(即渲染物体的表面)和体渲染(即渲染物体的表面和内部)。

一般而言,当我们使用表面渲染技术渲染对象时,我们会使用点、线、三角形、多边形或 2D 和 3D 样条等表面描述对对象进行数学建模。物体的内部没有被描述,或者只是从表面表示中隐含地表示出来(即表面是体积的边界)。尽管确实存在允许我们使表面透明或半透明的技术,但仍有许多现象无法单独使用表面渲染技术来模拟(例如,散射或光发射)。如果我们试图将数据呈现到对象内部,例如 CT 扫描的 X 射线强度,则尤其如此。

体绘制技术使我们能够看到物体内部的不均匀性。在之前的 CT 示例中,我们可以通过考虑数据表面和内部的强度值来真实地再现 X 射线图像。虽然现在在本文中描述这个过程还为时过早,但您可以想象从上一节扩展我们的光线追踪示例。因此,光线不仅与物体的表面相互作用,而且还与内部相互作用。

在本章中,我们将重点介绍表面渲染技术。虽然不如体积渲染强大,但表面渲染被广泛使用,因为它与体积技术相比相对较快,并且允许我们为各种数据和对象创建图像。第 7 章 – 高级计算机图形学更详细地描述了体绘制。

可视化不是图形

尽管作者很乐意提供关于计算机图形学的详尽论文,但这样的论述超出了本文的范围。相反,我们区分了可视化(探索、转换和映射数据)和计算机图形(映射和渲染)。重点将放在可视化的原理和实践上,而不是 3D 计算机图形。在本章和第 7 章 – 高级计算机图形学中,我们介绍了基本概念并提供了 3D 计算机图形学的实用知识。对这个领域比较感兴趣的,可以参考本章末尾“书目注释”中推荐的文本。

我们对这种姿势的遗憾之一是某些渲染技术本质上是可视化技术。我们在上一段中看到了这一点,我们使用术语“映射”来描述可视化和计算机图形。目前并可能永远不会在可视化和图形之间有明确的区别。例如,许多研究人员认为体绘制完全属于可视化领域,因为它解决了最重要的可视化数据形式之一。我们的区别主要是为了我们自己的方便,并为我们提供了完成本文的机会。我们建议认真学习可视化的学生用更深入的计算机图形学和体绘制书籍来补充这里介绍的材料。

在接下来的几页中,我们将更详细地描述渲染过程。我们首先描述几种颜色模型。接下来我们检查渲染过程的主要组成部分。有诸如太阳之类的光源,诸如立方体或球体之类的我们希望渲染的对象(我们将这些对象称为actor),并且有一个向外看世界的相机。这些术语取自电影业,大多数人都熟悉。actor代表图形数据或对象,灯光照亮actor,相机通过将actor投影到视图平面上来构建图片。我们将灯光、摄像机和actor的组合称为场景,将渲染过程称为渲染场景。

3.2 颜色

人类可见的电磁波谱包含约 400 至 700 纳米的波长。进入我们眼睛的光由这些波长的不同强度组成,示例如下图 3-2. 该强度图定义了光的颜色,因此不同的图会产生不同的颜色。不幸的是,我们可能不会注意到差异,因为人眼会丢弃大部分信息。人眼中有三种颜色感受器,称为视细胞。每种类型都响应 400 到 700 纳米波长范围的子集,如图所示图 3-3. 我们看到的任何颜色都会被我们的眼睛编码成这三个重叠的反应。这大大减少了实际进入我们眼睛的信息量。结果,人眼无法识别任何颜色的差异,其强度曲线在应用于人眼的响应曲线时会产生相同的三重响应。这也意味着我们可以使用简化的形式在计算机中存储和表示颜色,而人眼无法识别差异。

图 3-21图 3-2。波长与强度图。

我们用来描述颜色的两个简化组件系统是 RGB 和 HSV 颜色系统。RGB 系统根据其红色、绿色和蓝色的强度来表示颜色。这可以被认为是一个三维空间,轴是红色、绿色和蓝色。一些常见的颜色及其 RGB 分量显示在图 3-4.

HSV 系统根据颜色的色调、饱和度和值来表示颜色。值分量也称为亮度或强度分量,表示颜色中有多少光。0.0 的值总是会给你黑色,1.0 的值会给你一些明亮的东西。色调代表颜色的主波长,通常用圆圈表示,如图 3-5. 该圆圆周上的每个位置代表不同的色调,可以使用角度指定。当我们指定色调时,我们使用从零到一的范围,其中零对应于色调圆上的零度,一对应于 360 度。饱和度表示有多少色调混合到颜色中。例如,我们可以将值设置为 1,这给我们一个明亮的颜色,将色调设置为 0.66,给我们一个蓝色的主波长。现在,如果我们将饱和度设置为 1,颜色将是明亮的原蓝色。如果我们将饱和度设置为 0.5,颜色将是天蓝色,一种混合​​了更多白色的蓝色。如果我们将饱和度设置为零,这表明颜色中的主波长(色调)不超过任何颜色。其他波长。结果,最终颜色将为白色(无论色调值如何)。图 3-4列出了一些常见颜色的 HSV 值。

图 3-3图 3-3。人类视网膜中三种视锥细胞对光的相对吸光度。

颜色 RGB 单纯疱疹病毒
黑色的 0,0,0 ,,0
白色的 1,1,1 *,0,1
红色的 1,0,0 0,1,1
绿色的 0,1,0 1/3,1,1
蓝色的 0,0,1 2/3,1,1
黄色 1,1,0 1/6,1,1
青色 0,1,1 1/2,1,1
品红 1,0,1 5/6,1,1
天蓝色 1/2,1/2,1 2/3,1/2,1

图 3-4。RGB 和 HSV 空间中的常见颜色。

图 3-5a
s图 3-5b

图 3-5。在顶部,色调的圆形表示。底部的另外两个图像是通过 HSV 颜色空间的切片。第一个切片的值为 1.0,另一个切片的值为 0.5。

3.3 灯

控制渲染过程的主要因素之一是光与场景中actor的交互。如果没有灯光,则生成的图像将是黑色的,而且信息量不大。在很大程度上,定义了我们所看到的场景是发射光与场景中actor的表面(在某些情况下是内部)之间的相互作用。一旦光线与场景中的actor互动,我们就有了一些东西供我们的相机查看。

在计算机图形学中使用的许多不同类型的灯光中,我们将讨论最简单、无限远的点光源。与我们在家和工作中使用的灯相比,这是一个简化的模型。我们习惯使用的光源通常从空间区域(白炽灯泡中的灯丝,或荧光灯中的发光气体)辐射。点光源照明模型假设光从空间中的一个点向所有方向发射。对于无限大的光源,我们假设它的位置与它所照亮的物体相距无限远。这很重要,因为它意味着来自这样一个源的入射光线将彼此平行。局部光源(例如房间中的灯)的发射不是平行的。图 3-6说明了具有有限体积的局部光源与无限点光源之间的差异。我们的无限光源发出的光的强度在传播时也保持不变,这与物理光遵循的实际 1/距离2关系相反。如您所见,这是一个很好的简化,稍后将允许我们使用不太复杂的照明方程。

图 3-6图 3-6。具有有限体积的局部光源与无限点光源。

3.4 表面特性

当光线穿过太空时,其中一些会与我们的actor相交。当这种情况发生时,光线与actor的表面相互作用以产生颜色。这种产生的颜色的一部分实际上不是由于直射光,而是来自环境从其他物体反射或散射的光。环境照明模型解释了这一点,并且是现实世界中发生的复杂光散射的简单近似。它将光源的强度曲线应用于物体的颜色,也表示为强度曲线。结果是我们看到那个物体时看到的光的颜色。对于这样的模型,重要的是要认识到照射在蓝色球上的白光与照射在白球上的蓝色光是无法区分的。环境光照方程为

Ra=LcOa (3-1)

在哪里是由于环境照明而产生的强度曲线,是环境光的强度曲线,和是物体的颜色曲线。为了帮助保持方程简单,我们假设所有的方向向量都被归一化(即,大小为 1)。

结果颜色的两个组成部分取决于直接照明。漫射照明,也称为朗伯反射,考虑了光入射到物体上的角度。图 3-7显示了随着您从中心横向移动而变暗的圆柱体图像。圆柱体的颜色是恒定的;撞击圆柱体表面的光量会发生变化。在中心,入射光几乎垂直于圆柱体表面,它在每个表面积上接收到更多光线。当我们向侧面移动时,它会下降,直到最终入射光平行于圆柱体的侧面并且产生的强度为零。

图 3-7
图 3-7。平面和 Gouraud 着色。不同的着色方法可以显着改善用多边形表示的对象的外观。在顶部,平面着色在每个多边形上使用恒定的表面法线。在底部,Gouraud 着色从多边形顶点插入法线以提供更平滑的外观。请参阅 FlatVersusGouraud.cxxFlatVersusGouraud.py

漫射照明的贡献如公式 3-2所示,并在图 3-8.

屏幕截图 2022-11-06 142446在哪里是由于漫射照明而产生的强度曲线,Lc是光的强度曲线,和Oc是对象的颜色曲线。请注意,漫射光是入射光矢量与物体表面法线之间的相对角度的函数。因此,漫射照明与观看者的位置无关。

镜面照明表示光源从闪亮物体上的直接反射。图 3-10显示了一个具有不同镜面反射的漫射球。高光强度(在顶行和底行之间变化)控制高光照明的强度。镜面反射功率O_{sp}表示物体的光泽度,更具体地说,它表示当反射角偏离完美反射时镜面 sp 反射减少的速度。值越高表示衰减越快,因此表面越亮。参考图 3-9,镜面光照的方程是

屏幕截图 2022-11-06 142724在哪里Cn→是相机的投影方向,是S是镜面反射的方向。

图 3-81图 3-8。漫射照明。

图 3-9图 3-9。镜面照明。

我们已经独立地提出了不同照明模型的方程。我们可以同时或组合应用所有照明模型。公式3-4 将环境光、漫反射光和镜面光合并为一个公式。

屏幕截图 2022-11-06 142907结果是对象表面上某个点的颜色。常数Oai、Odi, 和Osi控制对象的环境光、漫反射光和镜面光的相对量。常数Oac,OdcOsc指定要用于每种照明的颜色。这六个常数以及镜面反射功率是表面材料属性的一部分。(其他属性,如透明度将在本文后面部分介绍。)这些属性值的不同组合可以模拟无光泽的塑料和抛光金属。该方程假定如“Lights”中所述的无限点光源。但是,可以轻松修改该等式以合并其他类型的定向照明。
图 3-10
图 3-10。镜面反射系数的影响。镜面反射系数控制物体的明显“光泽度”。第一行的高光强度值为 0.5;底行 1.0. 沿着水平方向,镜面反射功率发生变化。值(从左到右)是 5、10、20 和 40。请参阅 SpecularSpheres.cxxSpecularSpheres.py

3.5 相机

我们有发射光线的光源和具有表面特性的actor。在我们actor表面上的每一点,这种交互都会产生一些复合颜色(即,来自光、物体表面、镜面反射和环境效果的组合颜色)。我们现在渲染场景所需要的只是一台摄像机。有许多重要因素决定了 3D 场景如何投影到平面上以形成 2D 图像(参见图 3-11)。这些是相机的位置、方向和焦点、相机投影的方法以及相机剪裁平面的位置。

图 3-1图 3-11相机属性。.

摄像机的位置和焦点定义了摄像机的位置及其指向的位置。从相机位置到焦点定义的矢量称为投影方向。相机图像平面位于焦点处,通常垂直于投影矢量。相机方向由位置和焦点加上相机向上视图矢量控制。这些一起完全定义了相机视图。

投影方法控制actor如何映射到图像平面。正交投影是一个并行映射过程。在正交投影(或平行投影)中,所有进入相机的光线都平行于投影矢量。当所有光线都经过一个公共点(即,视点或投影中心)时,就会发生透视投影。要应用透视投影,我们必须指定透视角或相机视角。

前后剪裁平面与投影矢量相交,并且通常垂直于它。剪裁平面用于消除距离相机太近或太远的数据。结果,只有剪裁平面内的actor或actor的一部分是(潜在的)可见的。剪切平面通常垂直于投影方向。可以使用相机的剪辑范围设置它们的位置。平面的位置是从相机沿投影方向的位置开始测量的。前剪切平面处于最小范围值,后剪切平面处于最大范围值。稍后在第 7 章 – 高级计算机图形学,当我们讨论立体渲染时,我们会看到不垂直于投影方向的剪切平面的例子。

这些相机参数一起定义了一个矩形金字塔,其顶点位于相机的位置并沿投影方向延伸。金字塔顶部被前剪裁平面截断,底部被后剪裁平面截断。生成的视锥定义了相机可见的 3D 空间区域。

虽然可以通过直接设置上述属性来操作相机,但有一些常见的操作可以使工作变得更容易。图 3-12图 3-13将有助于说明这些操作。更改相机的方位角会围绕其视图向上矢量旋转其位置,以焦点为中心。可以将其想象为将相机向左或向右移动,同时始终保持与焦点的距离不变。更改相机的高度会围绕其投影方向的叉积旋转其位置,并以焦点为中心向上查看。这对应于上下移动相机。为了滚动相机,我们围绕视图平面法线旋转视图向上矢量。滚动有时被称为扭曲。

接下来的两个动作保持相机的位置不变,而是修改焦点。更改偏航角会使焦点围绕视图向上以相机位置为中心旋转。这就像方位角,只是焦点移动而不是位置。俯仰的变化使焦点围绕投影方向的叉积旋转,并以相机位置为中心向上查看。推入和推出沿投影方向移动相机的位置,靠近或远离焦点。此操作指定为其当前距离与其新距离的比率。大于 1 的值将推入,而小于 1 的值将推出。最后,缩放会改变相机的视角,以便或多或少的场景落入视锥体中。

图 3-12图 3-12。相机围绕焦点移动。请参阅 CameraModel1.cxxCameraModel1.py

图 3-13图 3-13。相机位置周围的相机移动。请参阅 CameraModel2.cxxCameraModel2.py

一旦我们有了相机,我们就可以生成我们的 2D 图像。穿过我们的 3D 空间的一些光线将穿过相机上的镜头。然后这些光线照射到平坦的表面以产生图像。这有效地将我们的 3D 场景投影到 2D 图像中。相机的位置和其他属性决定了哪些光线被捕获和投射。更具体地说,只有与相机位置相交并在其视锥体内的光线才会影响生成的 2D 图像。

我们简要的渲染概述到此结束。光线从光源传播到actor,在那里被反射和散射。其中一些光被相机捕获并产生 2D 图像。现在我们来看看这个过程的一些细节。

3.6 坐标系

计算机图形学中有四种常用的坐标系和两种不同的表示其中点的方式(图 3-14)。虽然这可能看起来有些过分,但每个都有一个目的。我们使用的四个坐标系是:模型世界视图显示

模型坐标系是定义模型的坐标系,通常是局部笛卡尔坐标系。如果我们的一个actor代表一个足球,它将基于足球几何自然的坐标系(例如,圆柱系统)。这个模型有一个固有的坐标系,由生成它的人决定。他们可能使用英寸或米作为单位,并且足球可能以任意轴为主轴建模。

世界坐标系是actor所在的 3D 空间。Actor 的职责之一是将模型的坐标转换为世界坐标。每个模型可能有自己的坐标系,但只有一个世界坐标系。每个actor都必须缩放、旋转并将其模型转换为世界坐标系。(建模者可能还需要将其自然坐标系转换为局部笛卡尔坐标系。这是因为参与者通常假设模型坐标系是局部笛卡尔坐标系。)世界坐标系也是其中的坐标系。指定相机和灯光的位置和方向。

视图坐标系表示相机可见的内容。这由一对 x 和 y 值组成,范围在 (-1,1) 和 az 深度坐标之间。x、y 值指定图像平面中的位置,而 z 坐标表示距相机的距离或范围。相机的属性由一个四乘四的变换矩阵表示(稍后将描述),该矩阵用于将世界坐标转换为视图坐标。这是介绍相机透视效果的地方。

显示坐标系使用与视图坐标系相同的基础,但不是使用负一比一作为范围,而是图像平面上实际的 x、y 像素位置。显示器上的窗口大小等因素决定了 (-1,1) 的视图坐标范围如何映射到像素位置。这也是视口生效的地方。

图 3-14图 3-14。建模、世界、视图和显示坐标系。

您可能想要渲染两个不同的场景,但将它们显示在同一个窗口中。这可以通过将窗口划分为矩形视口来完成。然后,可以告诉每个渲染器应该使用窗口的哪个部分进行渲染。视口在 x 和 y 轴上的范围为 (0,1)。与视图坐标系类似,显示坐标系中的 z 值也表示窗口的深度。这个 z 值的含义将在标题为“Z-Buffer”的部分中进一步描述。

3.7 坐标变换

当我们使用计算机图形创建图像时,我们将在三个维度中定义的对象投影到二维图像平面上。正如我们之前看到的,这个投影自然包括透视。为了包括诸如消失点之类的投影效果,我们使用称为齐次坐标的特殊坐标系。

在 3D 中表示点的常用方法是三元素笛卡尔向量(x,y,z). 齐次坐标由四元素向量表示(xh,yh,zh,wh). 笛卡尔坐标和齐次坐标之间的转换由下式给出:

屏幕截图 2022-11-06 143241使用齐次坐标,我们可以通过将 wh 设置为零来表示无限点。相机使用此功能进行透视变换。通过使用 4×4 变换矩阵应用变换。变换矩阵广泛用于计算机图形学中,因为它们允许我们通过重复矩阵​​乘法来执行对象的平移、缩放和旋转。并非所有这些操作都可以使用 3×3 矩阵执行。

例如,假设我们想要创建一个转换一个点的变换矩阵在笛卡尔空间中由向量(tx,ty,tz). 我们只需要构造由下式给出的平移矩阵

屏幕截图 2022-11-06 143328然后将其与齐次坐标后乘(xh,yh,zh,wh). 为了实现这个例子,我们从笛卡尔坐标构造齐次坐标通过设置屈服(x,y,z,1). 然后确定平移点 ${x’, y’, z’) 我们将当前位置与变换矩阵相乘TT产生平移坐标。代入方程 3-6我们有结果
屏幕截图 2022-11-06 143427

通过方程3-5 转换回笛卡尔坐标,我们得到了预期的解决方案

 

屏幕截图 2022-11-06 143456

相同的过程用于缩放或旋转对象。为了缩放一个对象,我们使用变换矩阵

屏幕截图 2022-11-06 143544

参数在哪里 , 比例因子,z axes。类似地,我们可以围绕角度 使用矩阵

 

屏幕截图 2022-11-06 143555

围绕y我们使用的轴

 

屏幕截图 2022-11-06 143604

围绕x我们使用的轴

 

屏幕截图 2022-11-06 143615

另一个有用的旋转矩阵用于将一个坐标轴 x – y – z x-y-z 转换为另一个坐标轴 x ‘ – y ‘ – z ‘ x’-y’-z’。 为了推导变换矩阵,我们假设单位 x ′ x′。 为了导出变换矩阵,我们假设单位 x ′ x′ 轴在 x 周围形成角度 ( θ x ′ x , θ x ′ y , θ x ′ z ) (θx′x,θx′y,θx′z) − y − z x−y−z 轴(这些称为方向余弦)。 类似地,单位 y ′ y′ 轴使角度 ( θ y ′ x , θ y ′ y , θ y ′ z ) (θy′x,θy′y,θy′z) 并且单位 z ′ z′ 轴使 角度 ( θ z ′ x , θ z ′ y , θ z ′ z ) (θz′x,θz′y,θz′z)。 通过沿变换矩阵的行放置方向余弦来形成结果旋转矩阵,如下所示

屏幕截图 2022-11-06 143627

围绕坐标原点发生旋转。围绕对象的中心(或用户指定的点)旋转通常更方便。假设我们称这个点为对象的中心。旋转○C我们必须首先将对象从○C到原点,应用旋转,然后将对象平移回原点○C.

变换矩阵可以通过矩阵乘法组合,实现平移、旋转和缩放的组合。单个变换矩阵可以同时表示所有类型的变换。该矩阵是重复矩阵乘法的结果。一个警告:乘法的顺序很重要。例如,将平移矩阵乘以旋转矩阵不会产生与旋转矩阵乘以平移矩阵相同的结果。

3.8 actor几何

我们已经看到了光照属性如何控制actor的外观,以及相机如何结合变换矩阵将actor投影到图像平面。剩下要定义的是actor的几何形状,以及我们如何在世界坐标系中定位它。

造型

计算机图形学研究的一个主要课题是建模或表示物理对象的几何形状。已经应用了各种数学技术,包括各种形式的点、线、多边形、曲线和样条的组合,甚至是隐式数学函数。

这个话题超出了本文的范围。这里的重点是有一个底层几何模型,它指定对象的形状是什么以及它在模型坐标系中的位置。

在数据可视化中,建模扮演着不同的角色。可视化算法不是直接创建几何图形来表示对象,而是计算这些形式。通常几何是抽象的(如等高线),与现实世界的几何关系不大。当我们在第 6 章 – 基本算法第 9 章 – 高级算法中描述可视化算法时,我们将看到这些模型是如何计算的。

数据可视化的几何表示往往很简单,尽管计算表示并不简单。这些形式通常是点、线和多边形等基元,或体数据等可视化数据。我们使用简单的形式是因为我们需要高性能和交互式系统。因此,我们利用了计算机硬件(将在“图形硬件”中介绍)或特殊的渲染技术,如体积渲染(参见第 7 章中的“体积渲染”)。

actor位置和方向

每个actor都有一个变换矩阵,用于控制其在世界空间中的位置和缩放。actor的几何形状由模型坐标中的模型定义。我们使用沿坐标轴的方向、位置和比例因子指定actor的位置。此外,我们可以定义actor围绕其旋转的原点。这个特性很有用,因为我们可以围绕它的中心或其他有意义的点旋转actor。

actor的方向由存储在方向向量 (Ox,Roy,Oz) 中的旋转确定。该向量定义了一系列旋转变换矩阵。正如我们在前面关于变换矩阵的部分中看到的,变换的应用顺序不是任意的。我们根据我们认为对用户来说很自然的东西选择了一个固定的顺序。变换的顺序是 O y 绕 y 轴旋转,然后绕 Ox 绕 x 轴旋转,最后 O z 绕 z 轴旋转。此排序是任意的,并且基于标准相机操作。这些操作(按顺序)是相机方位角,然后是仰角,然后是滚动(图 3-15).

所有这些旋转都围绕actor的原点进行。通常将其设置为其边界框的中心,但可以将其设置为任何方便的点。有许多不同的方法可以改变actor的方向。RotateX()、RotateY() 和 RotateZ() 是绕各自轴旋转的常用方法。许多系统还包括一种绕用户定义的轴旋转的方法。在可视化工具包中,RotateXYZ() 方法用于围绕通过原点的任意向量进行旋转。

图 3-15图 3-15。actor坐标系。

3.9 图形硬件

前面我们提到图形硬件的进步对渲染的执行方式产生了很大的影响。现在我们已经介绍了渲染场景的基础知识,我们来看看一些硬件问题。首先,我们讨论已取代矢量显示作为主要输出设备的光栅设备。然后,我们看看我们的程序如何与图形硬件通信。我们还检查了计算机图形、隐藏线/表面移除和z缓冲中使用的不同坐标系。

光栅设备

计算机图形学的成果在当今世界无处不在——数字图像(由计算机图形生成)可以在手机上找到、在计算机显示器上显示、在电视上播放、在电影院放映和在电子广告牌上展示。所有这些以及更多的显示媒体都是光栅设备。光栅设备使用称为像素的二维图像元素阵列来表示图像。例如,单词“hello”可以表示为像素数组。如图所示图 3-16. 这里,“你好”这个词写在一个宽 25 像素、高 10 像素的像素阵列中。每个像素存储一位信息,无论是黑色还是白色。这就是黑白激光打印机的工作原理,对于纸上的每个点,它要么打印一个黑点,要么留下纸张的颜色。由于硬件限制,诸如激光打印机和计算机显示器之类的光栅设备实际上并不能像在图 3-16. 相反,它们往往会略微模糊和重叠。光栅设备的另一个硬件限制是它们的分辨率。这就是导致 300 dpi(每英寸点数)激光打印机产生比九针点阵打印机更详细的输出的原因。300 dpi 激光打印机的分辨率为每英寸 300 像素,而点阵打印机的分辨率约为 50 dpi。

图 3-16图 3-16。单词“hello”的像素数组。

图 3-17图 3-17。黑白抖动。

彩色计算机显示器的分辨率通常约为每英寸 80 像素,使屏幕成为一个像素阵列,其宽度和高度约为一千像素。这会产生超过一百万个像素,每个像素都有一个指示它应该是什么颜色的值。由于彩色显示器中的硬件使用 RGB 系统,因此使用它来描述像素中的颜色是有意义的。不幸的是,拥有超过一百万个像素,每个像素都有红色、绿色和蓝色分量,会占用大量内存。这是区分市场上各种图形硬件的部分原因。一些公司每像素使用 24 位存储,其他公司使用 8 位,一些高级系统每像素使用超过 100 位存储。通常,每个像素的位数越多,颜色就越准确。

解决图形硬件颜色限制的一种方法是使用一种称为抖动的技术。例如,假设您想使用一些不同的灰色阴影,但您的图形硬件只支持黑白。抖动允许您通过混合使用黑色和白色像素来近似灰色阴影。在图 3-17,使用黑白像素混合绘制七个灰色方块。从远处看,这七个正方形看起来像不同深浅的灰色,即使近距离看,很明显它们只是黑白像素的不同混合。同样的技术也适用于其他颜色。例如,如果您的图形硬件支持原色蓝、原绿和白色,但不支持柔和的海绿,您可以通过抖动硬件支持的绿色、蓝色和白色来近似此颜色。

与硬件接口

现在我们已经介绍了显示硬件的基础知识,好消息是您很少需要担心它们。大多数图形编程是使用比单个像素更高级别的图元完成的。图 3-18显示了一个可视化程序的典型安排。层次结构的底部是我们已经讨论过的显示硬件;您的程序可能不会直接与之交互。硬件之上的前三层是您可能需要关注的层。

许多程序利用应用程序库作为系统图形功能的高级接口。本书附带的可视化工具包就是一个很好的例子。它允许您仅使用几个命令来显示复杂的对象或图形。由于不同的硬件平台可能支持不同的库,因此还可以连接到许多不同的图形库。

图形库和图形硬件层都执行类似的功能。它们负责从应用程序库或程序中获取高级命令并执行它们。通过提供更复杂的原语来使用,这使得编程变得更加容易。我们可以绘制多边形、三角形和线条等图元,而不是一次绘制一个像素,而不必担心将哪些像素设置为哪种颜色的细节。图 3-19说明了所有主流图形库都支持的一些高级原语。

此功能分为两个不同的层,因为不同的机器可能具有截然不同的图形硬件。如果您编写一个绘制红色多边形的程序,图形库或图形硬件必须能够执行该命令。在高端系统上,这可能在图形硬件中完成,而在其他系统上,这将由软件中的图形库完成。因此,相同的命令可以用于各种机器,而无需担心底层的图形硬件。

原语的基本构建块图 3-19是一个点(或顶点)。一个顶点有一个位置、法线和颜色,每一个都是一个三元素向量。位置指定顶点所在的位置,其法线指定顶点面向的方向,其颜色指定顶点的红色、绿色和蓝色分量。

多边形是通过连接一系列点或顶点来构建的,如图所示图 3-20. 您可能想知道为什么每个顶点都有一个法线,而不是整个多边形只有一个法线。一个平面多边形只能面向一个方向,而不管其顶点的法线表示什么。原因是有时多边形被用作其他东西的近似值,比如曲线。图 3-21显示了圆柱体的俯视图。如您所见,它并不是真正的圆柱体,而是用灰色绘制的圆柱体的多边形近似。每个顶点由两个多边形共享,并且顶点的正确法线与多边形的法线不同。类似的逻辑解释了为什么每个顶点都有一种颜色,而不是整个多边形只有一种颜色。

图 3-18图 3-18。典型的图形界面层次结构。

图 3-19图 3-19。图形基元。

当您将自己限制在上述图元类型时,许多图形系统都支持一些附加属性。边缘颜色和边缘可见性可用于突出显示构成角色的多边形图元。另一种方法是调整从表面线框的表示。这将分别用它们的边界边缘或点替换诸如多边形之类的表面。虽然从物理角度来看这可能没有多大意义,但它可以在某些插图中提供帮助。在渲染 CAD 模型时使用边缘可见性有助于显示构成模型的不同部分。

光栅化

在本文的这一点上,我们已经描述了如何使用渲染图元来表示图形数据,并且我们已经描述了如何使用光栅显示设备来表示图像。问题仍然存在,我们如何将图形基元转换为光栅图像?这是我们在本节中讨论的主题。尽管关于该主题的详尽论述超出了本文的范围,但我们将尽最大努力提供一个高层次的概述。

图 3-20图 3-20。一个示例多边形。

图 3-21图 3-21。顶点法线和多边形法线。

将几何表示转换为光栅图像的过程称为光栅化扫描转换。在下面的描述中,我们假设图形基元是三角形多边形。这并不像您想象的那样具有限制性,因为任何一般多边形都可以细分为一组三角形。此外,诸如样条的其他表面表示通常由图形系统镶嵌成三角形或多边形。(这里描述的方法实际上适用于凸多边形。)

当今的大多数硬件都基于对象顺序光栅化技术。正如我们在本章前面所看到的,这意味着按顺序处理我们的actor。由于我们的actors由多边形图元表示,我们一次处理一个多边形。因此,尽管我们描述了一个多边形的处理,但请记住,处理了许多多边形,可能还有许多actor。

第一步是使用适当的变换矩阵变换多边形。我们还使用平行或正交投影将多边形投影到图像平面。此过程的一部分涉及裁剪多边形。我们不仅要使用前后剪裁平面来剪裁太近或太远的多边形,而且还必须剪裁跨越图像平面边界的多边形。裁剪穿过视锥体边界的多边形意味着我们必须生成新的多边形边界。

图 3-22
图 3-22。栅格化凸多边形。像素在图像平面中以水平跨度(或扫描线)进行处理。点 $p_i$ 处的数据值 $d_i$ 沿边缘插值,然后使用增量数据值沿扫描线插值。典型的数据值是颜色的 RGB 分量。

将多边形裁剪并投影到图像平面后,我们可以开始扫描线处理(图 3-22)。第一步确定与投影多边形相交的初始扫描线。这是通过对顶点的y值进行排序来找到的。然后我们找到连接左右两侧顶点的两条边。使用边缘的斜率以及数据值,我们计算增量数据值。这些数据通常是RGB颜色分量。其他数据值包括透明度值和z深度值。(如果我们使用z缓冲区,则z值是必要的,将在下一节中描述。​​)多边形内的像素行(即,从左右边缘开始)称为跨度. 数据值从跨度任一侧的边缘内插以计算内部像素值。这个过程逐个跨度地继续,直到整个多边形被填满。请注意,当遇到新顶点时,有必要重新计算增量数据值。

多边形的阴影(即多边形上的颜色插值)取决于actor的插值属性。有三种可能性:flatGouraudPhong shading图 3-7说明了平面插值和 Gouraud 插值之间的区别。平面着色通过将光照方程应用于多边形的一条法线(通常是表面法线)来计算多边形的颜色。Gouraud 着色使用顶点的法线和标准光照方程计算多边形所有顶点的颜色。然后通过应用扫描线插值过程填充多边形的内部和边缘。Phong 着色是三者中最真实的。它通过插入顶点法线来计算多边形上每个位置的法线。然后在照明方程中使用这些来确定生成的像素颜色。平面和 Gouraud 着色都是常用的方法。Phong 着色的复杂性使其无法在硬件中得到广泛支持。

Z缓冲区

在我们之前对渲染过程的描述中,我们跟随光线从我们的眼睛穿过图像平面中的一个像素到达actor并返回到光源。光线追踪的一个很好的副作用是,观察光线会击中他们遇到的第一个actor,而忽略隐藏在其后面的任何actor。当使用上述多边形方法渲染actor时,我们没有这样的方法来计算哪些多边形是隐藏的,哪些不是。我们通常不能指望正确排序的多边形。相反,我们可以使用许多隐藏表面方法进行多边形渲染。

一种方法是从后到前(沿着相机的视图矢量)对我们所有的多边形进行排序,然后按该顺序渲染它们。这称为画家算法或画家排序,有一个主要弱点图 3-23. 无论我们绘制这三个三角形的顺序如何,我们都无法获得所需的结果,因为每个三角形都在另一个三角形的前面和后面。有一些算法可以根据需要对多边形进行排序和分割,以处理这种情况 [Carlson85]。这需要更多的初始处理来执行排序和拆分。如果图像之间的几何图元发生变化或相机视图发生变化,则必须在每次渲染之前执行此处理。

图 3-23图 3-23。画家算法的问题。

另一种隐藏表面算法 z-buffering 处理了这个问题并且不需要排序。Z 缓冲利用视图坐标系中的 z 值(即沿投影方向的深度值)。在绘制新像素之前,会将其 z 值与该像素位置的当前 z 值进行比较。如果新像素位于当前像素的前面,则绘制该像素并更新该像素位置的 z 值。否则,当前像素保留,新像素被忽略。Z 缓冲因其简单性和鲁棒性而在硬件中得到广泛实施。z 缓冲的缺点是它需要大量内存(称为 z 缓冲区)来存储每个像素的 z 值。大多数系统使用深度为 24 或 32 位的 z 缓冲区。对于 1000 x 1000 的显示,仅用于 z 缓冲区就转换为 3 到 4 兆字节。z-buffering 的另一个问题是它的准确性受到其深度的限制。24 位 z 缓冲区在视锥体高度上产生的精度为 16,777,216 分之一。如果物体靠得很近,这个分辨率通常是不够的。如果您确实遇到了具有 z 缓冲精度的情况,请确保前后剪裁平面尽可能靠近可见几何体。

3.10 放在一起

本节概述了图形对象以及如何在 VTK 中使用它们。

图形模型

图 3-24图 3-24。图形对象的说明图。请参阅 Model.cxxModel.py

我们已经讨论了许多在场景渲染中起作用的对象。现在是时候将它们组合成一个用于图形和可视化的综合对象模型了。

可视化工具包中,我们使用七个基本对象来渲染场景。幕后还有很多对象,但这七个是我们最常使用的对象。下面列出了这些对象,并在图 3-24.

  1. vtkRenderWindow — 管理显示设备上的一个窗口;一个或多个渲染器绘制到vtkRenderWindow的一个实例中。

  2. vtkRenderer — 协调涉及灯光、摄像机和actor的渲染过程。

  3. vtkLight — 照亮场景的光源。

  4. vtkCamera — 定义场景的视图位置、焦点和其他查看属性。

  5. vtkActor — 表示场景中渲染的对象,包括其属性和在世界坐标系中的位置。(注意: vtkActor是 vtkProp 的子类。vtkProp通用的 actor 形式,包括注释和 2D 绘图类。有关更多信息,请参见“Assemblies and Other Types of vtkProp”。)

  6. vtkProperty — 定义一个actor的外观属性,包括颜色、透明度和光照属性,如镜面反射和漫反射。还有代表性的属性,如线框和实体表面。

  7. vtkMapper — actor的几何表示。不止一个参与者可以引用同一个映射器。

vtkRenderWindow类将渲染过程联系在一起。它负责管理显示设备上的窗口。对于运行 Windows 的 PC,这将是一个 Microsoft 显示窗口,对于 Linux 和 UNIX 系统,这将是一个 X 窗口,在 Mac (OSX) 上是一个 Quartz 窗口。在 VTK 中,vtkRenderWindow的实例是独立于设备的。这意味着您无需关心正在使用的底层图形硬件或软件,该软件会在创建vtkRenderWindow实例时自动适应您的计算机。(有关详细信息,请参阅“实现设备独立性”。)

除了窗口管理之外,vtkRenderWindow对象还用于管理渲染器并存储显示窗口的图形特定特征,例如大小、位置、窗口标题、窗口深度双缓冲旗帜。窗口的深度表示每个像素分配了多少位。双缓冲是一种将窗口逻辑划分为两个缓冲区的技术。在任何给定时间,用户当前都可以看到一个缓冲区。同时,第二个缓冲区可用于绘制动画中的下一个图像。渲染完成后,可以交换两个缓冲区,以便新图像可见。这种常见的技术允许在用户没有看到图元的实际渲染的情况下显示动画。高端图形系统在硬件中执行双缓冲。一个典型的系统会有一个深度为 72 位的渲染窗口。前 24 位用于存储前缓冲区的红色、绿色和蓝色 (RGB) 像素分量。接下来的 24 位存储后台缓冲区的 RGB 值。最后 24 位用作z – 缓冲区。

vtkRenderer类负责协调其灯光、相机和actor以生成图像。每个实例都维护一个特定场景中的actor、灯光和活动摄像机的列表。至少必须定义一个actor,但如果没有定义灯光和相机,它们将由渲染器自动创建。在这种情况下,actor在图像中居中,默认摄像机视图在z轴下方。vtkRenderer类的实例还提供了指定背景和环境照明颜色的方法。方法也可用于在世界、视图和显示坐标系之间进行转换。

渲染器的一个重要方面是它必须与它要在其中绘制的vtkRenderWindow类的实例相关联,并且它在其中绘制的渲染窗口中的区域必须由矩形视口定义。视口由xy图像坐标轴上的归一化坐标 (0,1) 定义。默认情况下,渲染器绘制到渲染窗口的整个范围(视点坐标 (0,0,1,1))。可以指定较小的视口。并让多个渲染器绘制到同一个渲染窗口中。

vtkLight类的实例照亮场景。可以使用各种用于定向和定位灯光的实例变量。也可以打开/关闭灯以及设置它们的颜色。通常至少有一盏灯“打开”以照亮场景。如果没有定义和打开灯光,渲染器会自动构建灯光。VTK 中的灯光可以是位置的,也可以是无限的。位置灯具有相关的锥角和衰减因子。无限光投射出彼此平行的光线。

相机由vtkCamera类构建。重要参数包括相机位置、焦点、前后剪裁平面的位置、向上查看矢量和视野。如本章前面所述,相机还具有简化操作的特殊方法。

这些包括仰角、方位角、缩放和滚动。与vtkLight类似,如果没有定义vtkCamera的实例,渲染器将自动创建一个实例。

vtkActor类的实例表示场景中的对象。特别是,vtkActor结合了世界坐标系中的对象属性(颜色、阴影类型等)、几何定义和方向。这是通过维护引用vtkPropertyvtkMappervtkTransform实例的实例变量在幕后实现的。通常你不需要显式地创建属性或转换,因为它们是使用vtkActor的方法自动创建和操作的。您确实需要创建一个vtkMapper实例(或其子类之一)。映射器将数据可视化管道与图形设备联系起来。(我们将在下一章中详细介绍管道。)

在 VTK 中,actor 实际上是vtkProp(任意道具)和[vtkProp](https://www.vtk.org/doc/nightly/html/classvtkProp.html#details)3D(可以在 3D 空间中变换的那些)的子类(“道具”一词源自舞台,其中道具是场景中的对象。)还有其他具有特殊行为的道具和actor子类(有关更多信息,请参阅“组件和其他类型的 vtkProp”)。一个例子是vtkFollower。这个类的实例总是面向活动的相机。这在设计必须从场景中的任何相机位置读取的标志或文本时很有用。

vtkProperty类的实例影响actor的渲染外观。创建 Actor 时,会自动使用它们创建一个属性实例。也可以直接创建属性对象,然后将属性对象与一个或多个参与者相关联。通过这种方式,参与者可以共享共同的属性。

最后,vtkMapper(及其子类)定义了对象的几何形状,以及可选的顶点颜色。此外,vtkMapper指的是用于为几何图形着色的颜色表(即vtkLookupTable)。(我们将在第 6 章的“颜色映射”中讨论数据到颜色的映射。)我们将在第 6 章的“映射器设计”中更详细地研究映射过程。现在假设vtkMapper是一个表示几何和其他类型的可视化数据的对象。

还有另一个重要的对象vtkRenderWindowInteractor,它为渲染窗口中的渲染器捕获事件(例如鼠标点击和鼠标移动)。vtkRenderWindowInteractor捕获这些事件,然后触发某些操作,如相机小车、平移和旋转、actor挑选、进入/退出立体模式等。此类的实例使用 SetRenderWindow() 方法与渲染窗口相关联。

实现设备独立

使用 VTK 构建的应用程序的一个理想特性是它们独立于设备。这意味着在具有特定软件/硬件配置的一个操作系统上运行的计算机代码在不同的操作系统和软件/硬件配置上运行不变。这样做的好处是程序员不需要花费精力在不同的计算机系统之间移植应用程序。此外,无需重写现有应用程序即可利用硬件或软件技术的新发展。相反,VTK 通过结合继承和一种称为对象工厂的技术来透明地处理这个问题。

图 3-25a
(a) 设备类的继承。(注意:在 VTK 4.2 中不再支持 Starbase 和 XGL 图形库。)
图 3-25b
(b) 来自 vtkActor::New() 的代码片段
图3-25c
(c) 来自 vtkGraphicsFactory::CreateInstance(vtkclassname) 的代码片段。
图 3-25。使用 (a) 继承和对象工厂 (b) 和 (c) 实现设备独立性。

图 3-25a说明了使用继承来实现设备独立性。某些类如vtkActor分为两部分:一个设备独立的超类和一个设备相关的子类。这里的技巧是用户通过调用设备无关超类中的特殊构造函数 New() 创建一个设备相关子类。例如,我们将使用(在 C++ 中)

vtkActor *anActor = vtkActor::New()

创建vtkActor的设备相关实例。用户看不到设备相关代码,但实际上 anActor 是指向 vtkActor 的设备相关子类的指针图 3-25b是使用VTK的对象工厂机制的构造方法New()的代码片段。反过来,vtkGraphicsFactory(用于实例化图形类)在请求实例化参与者时生成适当的具体子类,如图所示图 3-25c.

使用使用 New() 方法实现的对象工厂允许我们创建设备独立代码,这些代码可以在计算机之间移动并适应不断变化的技术。例如,如果一个新的图形库可用,我们只需要创建一个新的设备相关子类,然后根据环境变量或其他系统信息修改图形工厂以实例化适当的子类。此扩展将被本地化并且只完成一次,并且基于这些对象工厂的所有应用程序都将自动移植而无需更改。

例子

本节介绍一些使用 VTK 图形对象实现的简单应用程序。重点是基础知识:如何创建渲染器、灯光、摄像机和actor。后面的章节将这些基本原则联系在一起,为数据可视化创建应用程序。

以下 C++ 代码使用本节介绍的大部分对象来创建圆锥图像。vtkConeSource生成一个圆锥体的多边形表示,vtkPolyDataMapper几何体(与actor一起)映射到底层图形库。(此示例的源代码可以在Cone.cxx中找到。源代码还包含其他文档。) 2

#include <vtkSmartPointer.h>
#include <vtkConeSource.h>

#include <vtkActor.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkNamedColors.h>

int main(int, char *[])
{
  //Create a cone
  auto coneSource =
    vtkSmartPointer<vtkConeSource>::New();
  coneSource->SetHeight( 3.0);
  coneSource->SetRadius( 1.0);
  coneSource->SetResolution( 10);
  coneSource->Update();

  //Create a mapper and actor
  auto mapper =
    vtkSmartPointer<vtkPolyDataMapper>::New();
  mapper->SetInputConnection(coneSource->GetOutputPort());

  auto colors =
    vtkSmartPointer<vtkNamedColors>::New();

  auto actor =
    vtkSmartPointer<vtkActor>::New();
  actor->SetMapper(mapper);
  actor->GetProperty()->SetDiffuseColor(colors->GetColor3d("bisque").GetData());

  //Create a renderer, render window, and interactor
  auto renderer =
    vtkSmartPointer<vtkRenderer>::New();
  auto renderWindow =
    vtkSmartPointer<vtkRenderWindow>::New();
  renderWindow->AddRenderer(renderer);
  renderWindow->SetSize(640, 480);

  auto renderWindowInteractor =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
  renderWindowInteractor->SetRenderWindow(renderWindow);

  //Add the actors to the scene
  renderer->AddActor(actor);
  renderer->SetBackground(colors->GetColor3d("Salmon").GetData());

  //Render and interact
  renderWindow->Render();
  renderWindowInteractor->Start();

  return EXIT_SUCCESS;
}
图 3-26
图 3-26。程序生成多边形模型的源对象示例。这九幅图像仅代表了 VTK 的部分功能。从左上角按阅读顺序:球体、圆锥体、圆柱体、立方体、平面、文本、随机点云、圆盘(有孔或无孔)和线源。其他多边形源对象可用;检查 vtkPolyDataAlgorithm 的子类。请参阅 SourceObjectsDemo.cxxSourceObjectsDemo.py

关于这个例子的一些评论。包含文件vtk__ .h 包含编译此示例所需的 VTK 中对象的类定义。在此示例中,我们使用构造函数 New() 创建对象,并使用方法 Delete() 销毁对象。在 VTK 中,必须使用 New() 和 Delete() 来确保设备独立性并正确管理引用计数。(有关详细信息,请参阅 VTK 用户指南。)在此示例中,实际上不需要使用 Delete(),因为对象在程序终止时会自动删除。但一般来说,每次调用 New() 时都应该使用 Delete()。(未来的示例将不会在 main() 程序范围内显示 Delete() 方法以节省空间,也不会显示所需的 #include 语句。)

本例中表示圆锥体(一组多边形)的数据是通过将一系列对象连接到管道中来创建的(这是下一章的主题)。首先使用vtkConeSource创建圆锥的多边形表示并作为 SetInput() 方法指定的数据映射器的输入。SetMapper() 方法将映射器的数据与coneActor 相关联。下一行将coneActor 添加到渲染器的actor 列表中。圆锥体以 360 度的循环渲染。由于上例中没有定义摄像机或灯光,为了方便用户,VTK 会自动生成默认的灯光和摄像机。通过 GetActiveCamera() 方法访问相机,并应用 1 度方位角,如图所示。每次对任何对象进行更改时,都会调用 Render() 方法来生成相应的图像。一旦循环完成,所有分配的对象都被销毁并且程序退出。

VTK 中有许多不同类型的源对象,类似于vtkConeSource,如图所示图 3-26. 在下一章中,我们将了解更多关于源和其他类型的过滤器。

事件和观察员。像 VTK 这样的可视化工具包经常用于交互式应用程序中,或者可能需要在操作期间提供状态。此外,与其他包(如 GUI 工具包)集成是一项常见任务。支持此类功能需要一种将用户功能插入软件的机制。在 VTK 中,命令/观察者设计模式[Gamma95]用于此目的。

在 VTK 中实现的这种设计模式的基础是事件的概念。事件表明软件中发生了重要操作。例如,如果用户在渲染窗口中按下鼠标左键,VTK 将调用 LeftButtonPressEvent。观察者是记录他们对特定事件或事件的兴趣的对象。当这些事件之一被调用时,观察者会收到通知,并可以在此时执行任何有效的操作;即执行与观察者相关的命令。命令/观察者设计模式的好处是概念和实现简单,但为用户提供了强大的功能。但是,它确实需要软件实现在运行时调用事件。

在下一个示例中,观察者监视渲染器调用的 StartEvent,就像它开始渲染过程一样。观察者依次执行其相关命令,该命令简单地打印出相机的当前位置。

#include "vtkCommand.h"
// Callback for the interaction
class vtkMyCallback : public vtkCommand
{
public:
  static vtkMyCallback *New()
    { return new vtkMyCallback; }
  virtual void Execute(vtkObject *caller, unsigned long, void*)
    {
      vtkRenderer *ren =
               reinterpret_cast<vtkRenderer*>(caller);
      cout << ren->GetActiveCamera()->GetPosition()[0] << " "
      ren->GetActiveCamera()->GetPosition()[1] << " "
      ren->GetActiveCamera()->GetPosition()[2] << "n";
    }
};

int main( int argc, char *argv[] )
{
  vtkConeSource *cone = vtkConeSource::New();
  cone->SetHeight( 3.0 );
  cone->SetRadius( 1.0 );
  cone->SetResolution( 10 );

  vtkPolyDataMapper *coneMapper = vtkPolyDataMapper::New();
  coneMapper->SetInputConnection( cone->GetOutputPort() ); vtkActor
  *coneActor = vtkActor::New(); coneActor->SetMapper( coneMapper );

  vtkRenderer *ren1= vtkRenderer::New();
  ren1->AddActor( coneActor );
  ren1->SetBackground( 0.1, 0.2, 0.4 );

  vtkRenderWindow *renWin = vtkRenderWindow::New();
  renWin->AddRenderer( ren1 ); renWin->SetSize( 300, 300 );

  vtkMyCallback *mo1 = vtkMyCallback::New();
  ren1->AddObserver(vtkCommand::StartEvent,mo1); mo1->Delete();

  int i;
  for (i = 0; i < 360; ++i)
  {
  //   render the image
    renWin->Render();
  // rotate the active camera by one degree
    ren1->GetActiveCamera()->Azimuth( 1 );
  }

  cone->Delete();
  coneMapper->Delete();
  coneActor->Delete();
  ren1->Delete();
  renWin->Delete();
  return 0;
}

观察者是通过从类vtkCommand派生来创建的。Execute() 方法必须由vtkCommand的任何具体子类实现(即,该方法是纯虚拟的)。生成的子类 vtkMyCommand 使用 AddObserver() 方法被实例化并注册到渲染器实例 ren1。在这种情况下,StartEvent 是观察到的事件。

这个简单的例子并没有展示命令/观察者设计模式的真正威力。在本章后面(“解释代码”)中,我们将看到如何使用此功能将简单的 GUI 集成到 VTK 中。在第 7 章 – 高级计算机图形学中,将介绍三维交互小部件([“3D 小部件和用户交互”]/VTKBook/07Chapter7/#718-3d-widgets-and-user-interaction)。

创建多个渲染器。下一个示例稍微复杂一些,并使用共享单个渲染窗口的多个渲染器。我们使用视口来定义渲染器应该在渲染窗口中绘制的位置。(此 C++ 代码可在 Cone3​​.cxx 中找到。)

图 3-27图 3-27。Cone3​​.cxx 的四帧输出。请参阅 Cone3​​.cxxCone3​​.py

vtkRenderer *ren1= vtkRenderer::New();
ren1->AddActor( coneActor );
ren1->SetBackground( 0.1, 0.2, 0.4 );
ren1->SetViewport(0.0, 0.0, 0.5, 1.0);

vtkRenderer *ren2= vtkRenderer::New();
ren2->AddActor( coneActor );
ren2->SetBackground( 0.2, 0.3, 0.5 );
ren2->SetViewport(0.5, 0.0, 1.0, 1.0);

vtkRenderWindow *renWin = vtkRenderWindow::New();
renWin->AddRenderer( ren1 ); renWin->AddRenderer( ren2 );
renWin->SetSize( 600, 300 );

ren1->GetActiveCamera()->Azimuth(90);

int i;
for (i = 0; i < 360; ++i)
  {
  // render the image renWin->Render();
  // rotate the active camera by one degree
    ren1->GetActiveCamera()->Azimuth( 1 );
    ren2->GetActiveCamera()->Azimuth( 1 );
  }

如您所见,大部分代码与前面的示例相同。第一个区别是我们创建了两个渲染器而不是一个。我们将相同的actor分配给两个渲染器,但将每个渲染器的背景设置为不同的颜色。我们设置两个渲染器的视口,一个在渲染窗口的左半边,另一个在右半边。渲染窗口的大小指定为 600 x 300 像素,这会导致每个渲染器绘制到 300 x 300 像素的视口中。

多个渲染器的一个很好的应用是显示同一世界的不同视图,如本例所示。在这里,我们将第一个渲染器的相机调整为 90 度方位角。然后我们开始一个循环,使两个摄像机围绕锥体旋转。图 3-27显示此动画的四个帧。

属性和转换。前面的示例没有显式创建属性或转换对象,也没有应用影响这些对象的参与者方法。相反,我们接受了默认的实例变量值。此过程是典型的 VTK 应用程序。大多数实例变量已预设为生成可接受的结果,但您始终可以使用方法来覆盖默认值。

图 3-28图 3-28。修改属性和变换矩阵。请参阅 Cone4.cxxCone4.py

这个例子创建了一个具有不同颜色和高光属性的两个圆锥体的图像。此外,我们将其中一个对象转换为与另一个对象相邻。此示例的 C++ 源代码可在 Cone4.cxx 中找到。

vtkActor *coneActor = vtkActor::New();
coneActor->SetMapper(coneMapper);
coneActor->GetProperty()->SetColor(0.2, 0.63, 0.79);
coneActor->GetProperty()->SetDiffuse(0.7);
coneActor->GetProperty()->SetSpecular(0.4);
coneActor->GetProperty()->SetSpecularPower(20);

vtkProperty *property = vtkProperty::New();
property->SetColor(1.0, 0.3882, 0.2784);
property->SetDiffuse(0.7);
property->SetSpecular(0.4);
property->SetSpecularPower(20);

vtkActor *coneActor2 = vtkActor::New();
coneActor2->SetMapper(coneMapper);
coneActor2->GetProperty()->SetColor(0.2, 0.63, 0.79);
coneActor2->SetProperty(property); coneActor2->SetPosition(0, 2, 0);

vtkRenderer *ren1= vtkRenderer::New();
ren1->AddActor( coneActor );
ren1->AddActor( coneActor2 );
ren1->SetBackground( 0.1, 0.2, 0.4 );

我们通过修改actor自动创建的属性对象来设置actor的coneActor属性。这与actor coneActor2 不同,我们直接创建一个属性,然后将其分配给actor。ConeActor2 通过应用 SetPosition() 方法从其默认位置移动。此方法影响作为参与者实例变量的转换矩阵。生成的图像显示在图 3-28.

介绍vtkRenderWindowInteractor前面的示例不是交互式的。也就是说,不修改和重新编译 C++ 代码是不可能直接与数据交互的。一种常见的交互类型是更改相机位置,以便我们可以从不同的有利位置查看我们的场景。在Visualization Toolkit中,我们提供了一套方便的对象来执行此操作: vtkRenderWindowInteractor vtkInteractorStyle及其派生类。

vtkRenderWindowInteractor类的实例在渲染窗口中捕获窗口系统特定的鼠标和键盘事件,然后将这些事件转换为 VTK 事件。例如,X11 或 Windows 应用程序中的鼠标运动(发生在渲染窗口中)将由vtkRenderWindowInteractor转换为 VTK 的 MouseMoveEvent。任何为此事件注册的观察者都会收到通知(参见“事件和观察者”)。通常vtkInteractorStyle的实例与vtkRenderWindowInteractor结合使用定义与特定事件相关的行为。例如,我们可以通过使用不同的鼠标按钮和运动组合来执行相机小车、平移和旋转。下面的代码片段展示了如何实例化和使用这些对象。此示例与我们的第一个示例相同,只是添加了交互器和交互器样式。完整的示例 C++ 代码位于 Cone5.cxx 中。

vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::New();
iren->SetRenderWindow(renWin);

vtkInteractorStyleTrackballCamera *style =
 vtkInteractorStyleTrackballCamera::New();

iren->SetInteractorStyle(style);
iren->Initialize();
iren->Start();

在使用其 New() 方法创建交互器之后,我们必须使用 SetRenderWindow() 方法告诉它要在哪个渲染窗口中捕获事件。为了使用交互器,我们必须使用 Initialize() 和 Start() 方法初始化和启动事件循环,它们与窗口系统的事件循环一起工作以开始捕获事件。一些更有用的事件包括“w”键,它可以在线框中绘制所有actor;“s”键,以表面形式绘制actor;“3”键,用于切换支持此功能的系统的 3D 立体效果;“r”键,重置相机视图;和退出应用程序的“e”键。此外,鼠标按钮还可以围绕相机的焦点旋转、平移和移动。两个高级功能是“u”键,执行用户定义的函数;和“p”键,它选择鼠标指针下的actor。

解释代码。在前面的示例中,我们看到了如何结合vtkRenderWindowInteractor创建一个交互器样式对象使我们能够通过鼠标在渲染窗口中操作相机。尽管这为大量应用程序提供了灵活性和交互性,但在本文中仍有我们希望修改其他参数的示例。这些参数的范围从actor属性(例如颜色)到输入文件的名称。当然,我们总是可以编写或修改 C++ 代码来做到这一点,但在许多情况下,从进行更改到看到结果之间的周转时间太长了。提高系统整体交互性的一种方法是使用解释界面。解释系统允许我们修改对象并立即看到结果,而无需重新编译和重新链接源代码。解释语言还提供了许多工具,例如 GUI(图形用户界面)工具,可简化应用程序的创建。

可视化工具包在其编译过程中内置了自动生成与 [Ousterhout94] 的语言绑定的能力。这个所谓的包装过程会自动在 C++ VTK 库和解释器之间创建一个层,如图所示图 3-29. 对于系统中的大多数对象和方法,C++ 方法和 Python C++ 函数之间存在一对一的映射。为了证明这一点,以下示例重复了前面的 C++ 示例,不同之处在于它是使用 Python 脚本实现的。(脚本可以在 Cone5.py 中找到。)

图 3-29图 3-29。在 VTK 中,C++ 库自动使用解释语言 Python 和 Java 进行包装。

!/usr/bin/env python
import vtk
colors = vtk.vtkNamedColors ()

cone = vtk.vtkConeSource ()
cone.SetHeight ( 3.0 )
cone.SetRadius ( 1.0 )
cone.SetResolution ( 10 )

coneMapper = vtk.vtkPolyDataMapper ()
coneMapper.SetInputConnection (cone.GetOutputPort ())

coneActor = vtk.vtkActor ()
coneActor.SetMapper ( coneMapper )

ren1 = vtk.vtkRenderer ()
ren1.AddActor( coneActor )
ren1.SetBackground (colors.GetColor3d ("MidnightBlue"))

renWin = vtk.vtkRenderWindow ()
renWin.AddRenderer (ren1)
renWin.SetSize (300 , 300)

iren = vtk.vtkRenderWindowInteractor ()
iren.SetRenderWindow (renWin)

style = vtk.vtkInteractorStyleTrackballCamera ()
iren.SetInteractorStyle (style)

iren.Initialize ()
iren.Start ()

图 3-30图 3-30。使用 Tcl 和 Tk 构建解释型应用程序。

该示例首先加载一些定义各种 VTK 类的共享库。接下来从vtkConeSourcevtkPolyDataMapper创建标准可视化管道。渲染类的创建与 C++ 示例完全相同。一个主要的补充是观察者在渲染窗口中观察用户事件(默认情况下是“keypress-u”)。观察者触发一个 Tcl 脚本的调用来引发一个名为.vtkInteract 的 Tk 交互器 GUI 小部件。这个允许直接输入 Tcl 语句的 GUI 显示在图 3-30并且由 Tcl 命令包 require vtkinteraction 定义,该命令包之前在脚本中执行过。(注意:Tk 是一种流行的解释语言 GUI 工具包,作为 Tcl 的一部分分发。)

从这个例子中我们可以看出,Tcl 例子的代码行数少于等效的 C++ 代码。此外,使用解释语言隐藏了 C++ 的许多复杂性。使用这个用户界面 GUI,我们可以创建、修改和删除对象,并修改它们的实例变量。一旦应用了 Render() 方法或渲染窗口中的鼠标事件导致渲染发生,结果更改就会出现。我们鼓励您使用 Tcl(或其他解释器之一)来快速创建图形和可视化示例。当您需要更高性能的应用程序时,最好使用 C++。

变换矩阵

转换矩阵在整个Visualization Toolkit中使用。Actors(vtkProp3D的子类)参见“Assemblies and Other Types of vtkProp”使用它们来定位和定位自己。各种过滤器,包括vtkGlyph3DvtkTransformFilter,使用转换矩阵来实现它们自己的功能。作为用户,您可能永远不会直接使用转换矩阵,但理解它们对于成功使用许多 VTK 类很重要。

应用变换矩阵最重要的方面是理解应用变换的顺序。如果您将一系列复杂的转换分解为平移、缩放和旋转的简单组合,并仔细跟踪应用的顺序,那么您将在很大程度上掌握它们的使用。

转换矩阵的一个很好的演示示例是检查vtkActor如何使用其内部矩阵。vtkActor有一个内部实例变量 Transform,它将许多方法委托给该变量或使用矩阵来实现其方法。例如,RotateX()、RotateY() 和 RotateZ() 方法都委托给 Transform。SetOrientation() 方法使用Transform 来定位actor。vtkActor类以我们认为对大多数用户来说很自然的顺序应用转换

vtkActor类以我们认为对大多数用户来说很自然的顺序应用转换为方便起见,我们创建了抽象转换矩阵的实例变量。起源(ox,oy,oz)指定作为旋转和缩放中心的点。点(px,py,pz)指定对象的最终转换。方向(定义围绕 z axes。比例(sx,sy,sz)定义比例因子。在内部,actor 使用这些实例变量来创建以下转换序列(参见Equation3-6Equation3-9Equation3-13)。

屏幕截图 2022-11-06 144256

表示在 方向。回想一下,我们将变换矩阵与位置向量相乘。这意味着从右到左读取转换。换言之,Equation 3-14 进行如下:

  1. 将actor翻译到它的起源。将围绕这一点进行缩放和旋转。应用缩放和旋转后,初始平移将被相反方向的平移抵消。

  2. 缩放几何。

  3. 围绕y轴、x轴和z轴旋转 actor 。

  4. 撤消步骤 1 的平移并将actor移动到其最终位置。

转换的顺序很重要。在 VTK 中,在大多数情况下,旋转是按自然顺序排列的。我们建议您花一些时间使用该软件来了解这些转换如何处理您自己的数据。

转换中最令人困惑的方面可能是旋转及其对 Orientation 实例变量的影响。通常方向不是由用户直接设置的,大多数用户更喜欢使用 RotateX()、RotateY() 和 RotateZ() 方法指定旋转。这些方法执行关于xyz的旋转轴按用户指定的顺序。新的旋转应用于旋转变换的右侧。如果您需要围绕单个轴旋转您的actor,actor 将完全按照您的预期旋转,并且生成的方向向量将如预期的那样。例如,操作 RotateY(20) 将产生 (0,20,0) 的方向,而 RotateZ(20) 将产生 (0,0,20)。但是,RotateY(20) 后跟 RotateZ(20) 不会产生 (0,20,20),而是产生 (6.71771, 18.8817, 18.8817) 的方向!这是因为公式 3-14的旋转部分是根据旋转顺序 z、x、y 构建的。为了验证这一点,RotateZ(20) 后跟 RotateY(20) 确实会产生 (0,20,20) 的方向。添加第三次旋转可能会更加混乱。

一个好的经验法则是仅使用 SetOrientation() 方法将方向重置为 (0,0,0) 或仅设置一个旋转。当需要多个角度时,RotateX()、RotateY() 和 RotateZ() 方法优于 SetOrientation()。请记住,这些旋转以相反的顺序应用。图 3-31说明了旋转方法的使用。我们使用渲染窗口的 EraseOff() 方法关闭帧之间的擦除,以便我们可以看到旋转的效果。请注意,在第四张图像中,母牛仍然围绕她自己的 y 轴旋转,即使 x 轴旋转先于 y 旋转。

图 3-31
图 3-31。一头母牛绕着她的斧头旋转。在这个模型中,*x* 轴是从左到右;*y* 轴从下到上;并且 *z* 轴从图像中出现。所有四张图像中的相机位置都相同。请参阅 Rotations.cxxRotations.py

我们已经看到,VTK 通过使用比变换矩阵更自然的实例变量来隐藏矩阵变换的一些复杂性。但是有时actor执行的转换的预定义顺序是不够的。vtkActor有一个包含 4 x 4 变换矩阵的实例变量 UserMatrix。该矩阵在actor组成的转换之前应用。随着您对 4 x 4 变换矩阵越来越熟悉,您可能想要构建自己的矩阵。对象vtkTransform创建和操作这些矩阵。与actor不同,vtkTransform的一个实例没有位置、比例、原点等的实例变量。您可以直接控制矩阵的组成。

以下语句创建了一个与参与者创建的相同的 4 x 4 矩阵:

vtkTransform *myTrans = vtkTransform::New ();
myTrans->Translate(position[0],position[1],position[2]);
myTrans->Translate(origin[0],origin[1],origin[2]);
myTrans->RotateZ(orientation[2]);

myTrans->RotateX (orientation[0]);
myTrans->RotateZ(orientation[1];
myTrans->Scale (scale[0],scale[1],scale[2]);
myTrans->Translate (-origin[0],-origin[1],-origin[2]);

将此变换操作序列与公式 3-14中的变换进行比较。

我们的最后一个示例显示了使用 vtkTransform 构建的转换与vtkActor构建的转换的比较。在此示例中,我们将变换我们的奶牛,使其围绕世界坐标原点 (0,0,0) 旋转。她似乎在原点周围走动。我们通过两种方式实现这一点:一种使用vtkTransform和 actor 的 UserMatrix,然后使用 actor 的实例变量。

首先,我们将沿着z轴移动奶牛五英尺,然后围绕原点旋转她。我们总是按照应用程序的相反顺序指定转换:

vtkTransform *walk = vtkTransform::New();
walk->RotateY(0,20,0);
walk->Translate(0,0,5);

vtkActor *cow=vtkActor::New();
cow->SetUserMatrix(walk->GetMatrix());

这些操作产生转换序列:

屏幕截图 2022-11-06 144217

 

现在我们使用牛的实例变量做同样的事情:

vtkActor *cow=vtkActor::New();
cow->SetOrigin(0,0,-5);
cow->RotateY(20);
cow->SetPosition(0,0,5);

当actor构建它的变换时,它将是:

屏幕截图 2022-11-06 144141

取消最右边平移矩阵中的减号并结合位置和原点平移产生我们使用 vtkTranform 构建的等效变换。图 3-32显示以指定变换顺序旋转的奶牛。您的偏好取决于您的品味以及您对矩阵转换的舒适程度。随着您变得更加熟练(并且您的要求更高),您可能更喜欢始终构建您的转换。VTK 为您提供选择。

有一个最终且强大的操作会影响actor的方向。您可以围绕角色原点处的任意向量旋转角色。这是通过actor(和变换)的 RotateWXYZ() 方法完成的。操作的第一个参数指定围绕由接下来的三个参数指定的向量旋转的度数。图 3-33展示了如何围绕穿过她鼻子的矢量旋转奶牛。首先,我们将原点留在 (0,0,0)。这显然不是我们想要的。第二个图显示了当我们将牛的旋转原点更改为她的鼻尖时的旋转。

图 3-32图 3-32。牛在全球原产地“行走”。请参阅 WalkCow.cxxWalkCow.py

图 3-33a

(一)

图 3-33b

(二)图 3-33。母牛绕着一个穿过她鼻子的向量旋转。(a) 原点 (0,0,0)。(b) 原点位于 (6.1,1.3,.02)。参见 WalkCowA.cxxWalkCowA.py。(b)。请参阅 WalkCowB.cxxWalkCowB.py

程序集和其他类型的vtkProp

通常希望将参与者收集到依赖于变换的组的层次结构中。例如,机器人手臂可以由连接在诸如肩关节、上臂、肘部、下臂、腕关节和手等关节处的刚性连杆来表示。在这样的配置中,当肩关节旋转时,预期的行为是整个手臂旋转,因为连杆连接在一起。这是VTK中称为程序集的示例。vtkAssembly只是 VTK 中许多类似 actor 的类之一。作为图 3-34如图所示,这些类被排列成 vtkProps 的层次结构。(在舞台和电影术语中,道具是在舞台上出现或使用的东西。)在 VTK 中通过实例化vtkAssembly然后向其添加部件来形成组件。零件是vtkProp3D的任何实例——包括其他组件。这意味着程序集可以形成层次结构(只要它们不包含自引用循环)。程序集遵循上一节中说明的转换连接规则(请参阅“转换矩阵”)。这是一个如何创建简单程序集层次结构的示例(来自 assembly.tcl)。

vtkSphereSource sphere
vtkPolyDataMapper sphereMapper
   sphereMapper SetInputConnection [sphere GetOutputPort]
vtkActor sphereActor
  sphereActor SetMapper sphereMapper
  sphereActor SetOrigin 2 1 3
  sphereActor RotateY 6
  sphereActor SetPosition 2.25 0 0
  [sphereActor GetProperty] SetColor 1 0 1

vtkCubeSource cube
vtkPolyDataMapper cubeMapper
  cubeMapper SetInputConnection [cube GetOutputPort]
vtkActor cubeActor
  cubeActor SetMapper cubeMapper
  cubeActor SetPosition 0.0 .25 0
  [cubeActor GetProperty] SetColor 0 0 1

vtkConeSource cone
vtkPolyDataMapper coneMapper
  coneMapper SetInputConnection [cone GetOutputPort]
vtkActor coneActor
  coneActor SetMapper coneMapper
  coneActor SetPosition 0 0 .25
  [coneActor GetProperty] SetColor 0 1 0

vtkCylinderSource cylinder
vtkPolyDataMapper cylinderMapper
  cylinderMapper SetInputConnection [cylinder GetOutputPort]
  cylinderMapper SetResolveCoincidentTopologyToPolygonOffset

vtkActor cylinderActor
  cylinderActor SetMapper cylinderMapper
  [cylinderActor GetProperty] SetColor 1 0 0

vtkAssembly assembly
  assembly AddPart cylinderActor
  assembly AddPart sphereActor
  assembly AddPart cubeActor
  assembly AddPart coneActor
  assembly SetOrigin 5 10 15

# allows faces a specified camera and is used for billboards.
  assembly AddPosition 5 0 0
  assembly RotateX 15

ren1 AddActor assembly
ren1 AddActor coneActor
图 3-34
图 3-34。图 3-34 vtkProp 层次结构。可以在 3D 空间中变换的道具是 vtkProp3D 的子类。使用 vtkImageActor 可以有效地绘制图像。覆盖文本和图形使用 vtkActor2D。vtkProp 的分层组被聚集到一个 vtkPropAssembly 中。体绘制使用 vtkVolume。可变形道具的集合创建了一个 vtkAssembly。细节层次渲染使用 vtkLODProp3D 和 vtkLODActor。vtkFollower 允许面向指定的相机并用于广告牌。

请注意,在此示例中,使用 AddPart() 方法将各种参与者添加到程序集中。组件的顶级元素是层次结构中添加到渲染器的唯一道具(使用 AddActor())。还要注意,coneActor 出现了两次:一次作为程序集的一部分,一次作为单独的 Actor,使用 AddActor() 添加到渲染器中。正如您可能想象的那样,这意味着程序集的呈现需要连接转换矩阵以确保每个[vtkProp] 的正确定位(https://www.vtk.org/doc/nightly/html/classvtkProp.html#details) 3D。此外,分层组件在挑选过程中需要特殊处理(即以图形方式选择道具),因为可以在不同的装配层次结构中出现多次。拣货问题在第 8 章的“拣货”中有更详细的讨论。

作为图 3-34表示,还有其他类型的vtkProp。其中大部分将在本书中的许多示例中进行非正式的描述。特别是,在我们描述体绘制时,对vtkVolume进行了广泛的介绍(​​参见第 7 章中的“体渲染”)。

3.11 章节总结

渲染是使用计算机生成图像的过程。计算机图形学是包含渲染技术的研究领域,是数据可视化的基础。

三维渲染技术模拟灯光和相机与物体或actor的交互,以生成图像。一个场景由灯光、摄像机和actor组合而成。对象顺序渲染技术通过按顺序渲染场景中的actor来生成图像。图像顺序技术一次渲染一个像素。基于多边形的图形硬件基于对象顺序技术。光线追踪或光线投射是一种图像排序技术。

照明模型需要指定颜色。我们看到了 RGB(红-绿-蓝)和 HSV(色相-饱和度-值)颜色模型。对于大多数用户来说,HSV 模型是比 RGB 模型更自然的模型。光照模型还包括环境光照、漫反射光照和镜面光照的影响。

计算机图形学中有四个重要的坐标系。模型系统是定义几何的 3D 坐标系。世界系统是全球笛卡尔系统。所有建模数据最终都转换为世界系统。视图坐标系表示相机可见的内容。它是一个从 (-1,1) 缩放的二维系统。显示坐标系使用计算机显示器上的实际像素位置。

齐次坐标是一个 4D 坐标系,我们可以在其中包含透视变换的效果。变换矩阵是在齐次坐标上运行的 4×4 矩阵。变换矩阵可以表示actor的平移、缩放和旋转的效果。这些矩阵可以相乘以给出组合变换。

图形编程通常使用更高级别的图形库和专用硬件系统来实现。这些专用系统提供更好的性能和更容易实现图形应用程序。在这些系统中实现的常用技术包括抖动和z缓冲。抖动是一种通过混合可用颜色组合来模拟颜色的技术。Z-缓冲是一种执行隐藏线和隐藏面去除的技术。

可视化工具包使用基于灯光、相机、actor和渲染器的图形模型。渲染器绘制到渲染窗口中。Actor 属性由属性对象表示,其几何由映射器对象表示。综上所述,这些不同类的实例构成了一个场景。vtkRenderWindowInteractorvtkInteractorStyle类促进了与场景中对象的交互。它们使用触发和响应事件的命令/观察者设计模式。用户可以观察特定事件并编写可以执行任意任务的回调,轻松扩展特定应用程序的工具包。

3.12 书目注释

本章为读者提供了足够的信息来理解计算机图形学中使用的基本问题和术语。有许多很好的教科书更详细地介绍了计算机图形学,推荐给想要更深入了解的读者。计算机图形学的圣经是[FoleyVanDam90]。对于那些希望不那么吓人的书的人来说,[BurgerGillies89][Watt93]也是有用的参考资料。您还可能希望仔细阅读 ACM SIGGRAPH 会议的会议记录。其中包括计算机图形学中一些最重要的工作的论文和对其他论文的引用。[Carlson85]为那些希望了解更多关于人类视觉系统的人提供了一个很好的介绍。

3.13 参考文献

[Bresenham65] JE Bresenham。“数字绘图仪的计算机控制算法”。IBM 系统杂志,4(1):25–30,1965 年 1 月。

[BurgerGillies89] P. Burger 和 D. Gillies。交互式计算图形功能、程序和设备级 方法。Addison-Wesley,马萨诸塞州雷丁,1989 年。

[卡尔森85] NR卡尔森。行为生理学(3d 版)。Allyn and Bacon Inc.,牛顿,马萨诸塞州,1985 年。

[Dartnall83] HJA Dartnall、JK Bowmaker 和 JD Mollon。“人类视觉颜料:七个人眼睛的显微分光光度计结果。” 英国皇家学会会刊,伦敦,1983 年。

[FoleyVanDam90] JD Foley、A. van Dam、SK Feiner 和 JF Hughes。计算机图形学原理与实践(2d 版)。Addison-Wesley,马萨诸塞州雷丁,1990 年。

[Fuchs80] H. Fuchs、ZM Kedem 和 BF Naylor。“通过先验树结构生成可见表面。” 计算机图形学SIGGRAPH ’80),14(3):124–133,1980。

[Gamma95] E. Gamma、R. Helm、R. Johnson、J. Vlissides。可重用的面向对象软件的设计模式元素。艾迪生-韦斯利 1995 年。ISBN0-201-63361-2。

[Ousterhout94] JKOusterhout。Tcl 和 Tk 工具包。Addison-Wesley 出版公司,马萨诸塞州雷丁,1994 年。

[Watt93] A. 瓦特。3D 计算机图形学(2d 版)。Addison-Wesley,马萨诸塞州雷丁,1993 年。

[惠特80] T.惠特。“一种改进的阴影显示照明模型。” ACM 通讯,23(6):343-349, 1980。

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

昵称

取消
昵称表情代码图片

    暂无评论内容