OpenGL 工作原理

OpenGL 工作原理

OpenGL 图形接口的设计 The Design of the OpenGL Graphics Interface

Mark Segal
Kurt Akeley
Silicon Graphics Computer Systems
2011 N. Shoreline Blvd., Mountain View, CA 94039

Abstract

OpenGL 是一种新兴的图形标准,它提供高级渲染功能,同时 保持简单的编程模型。因为 OpenGL 是仅渲染的,它可以被合 并到任何窗口系统中(并且已经被合并到 X 窗口系统和即将发 布的 Windows 版本中)或者可以在没有窗口系统的情况下使 用。OpenGL 实现可以有效地适应几乎任何级别的图形硬件, 从基本的帧缓冲区到最复杂的图形子系统。因此,在交互式 3 D 和 2D 图形应用程序中使用它是一个不错的选择。

我们描述了这些和其他考虑因素如何控制 OpenGL 中图形运算 符的选择和表示。复杂的操作已被避开,取而代之的是对 3D 和 2D 图形的基本操作进行简单、直接的控制。然而,更高级 别的图形函数可以从 OpenGL 的低级运算符构建,因为这些运 算符在设计时就考虑到了这种分层。

CR 类别和主题描述符:I.3.3[计算机图形学]:图片图像生成;I.3.7【计算机图形学】:三维图形与写实

1 Introduction

计算机图形(尤其是 3D 图形,尤其是交互式 3D 图形)正在寻 找越来越多的应用程序,从用于个人计算机的简单图形程序到用于工作站和超级计算机的复杂建模和可视化软件。随着对计算机图形学的兴趣不断增长,人们希望能够编写在具有 一系列图形功能的各种平台上运行的应用程序。图形标准消 除了为运行应用程序的每个平台编写不同的图形驱动程序的 需要,从而简化了这项任务。

为了可行,用于交互式 3D 应用程序的图形标准必须满足几个 标准。它必须能够在具有不同图形功能的平台上实现,而不会影响底层硬件的图形性能,也不会牺牲对硬件操作的控制 。它必须提供一个自然接口,允许程序员简洁地描述渲染操作。最后,界面必须足够灵活适应扩展,以便当新的图形操作变得重要或在新的图形子系 统中可用时,可以在不破坏原始界面的情况下提供这些操作 。

OpenGL 通过为 3D 图形渲染的基本操作提供简单、直接的接口来满足这些标准。它支持点、线段、多边形和图像等基本图形基元,以及仿射和投影变换和光照计算等基本渲染操作 。它还支持高级渲染功能,例如纹理映射和抗锯齿。

还有其他几个提供 API 的系统(Application Programmer's Interface) 用于影响图形渲染。在 2D 图形的情 况下,PostScript 页面描述语言 [5] 已被广泛接受,使得电子交换相对容易,并且在一定程度上可以操作包含文本和 2D 图 形的静态文档。除了提供图形渲染操作符之外,PostScript 还是一种基于堆栈的编程语言。

X 窗口系统 [9] 已成为 UNIX 工作站的标准。程序员使用 X 在图形显示上获得一个窗口,可以在其中绘制文本或 2D 图形;X 还提供了一种从键盘和鼠标等设备获取用户输入的方法。大 多数工作站制造商采用 X 意味着单个程序可以通过简单地重新编译程序来生成 2D 图形或在各种工作站上获取用户输入。这 种集成甚至可以跨网络工作:程序可以在一个工作站上运行 ,但在另一个工作站上显示并从另一个工作站上获取用户输入,即使网络两端的工作站是由不同的公司制造的。

对于 3D 图形,有几个系统正在使用中。一个相对知名的系统 是 PHIGS(程序员的分层交互式图形系统)。PHIGS 基于 GK S[6](图形内核系统),是一个 ANSI(美国国家标准协会) 标准。PHIGS(及其后代 PHIGS+[11])提供了一种操作和绘制 3D 对象的方法,方法是将对象描述和属性封装到显示列表中,然后在显示或操作对象时引用该列表。显示列表的一个优点是即使要显示多次,复杂对象也只需要描述一次。如果要显示的对象必须通过低带宽通道(例如网络)传输,这一点尤其重要。显示列表的一个缺点是,如果由于用户交互而不断修改对象,则可能需要相当大的努力来重新指定对象。P HIGS 和 PHIGS+(以及 GKS)的另一个困难是缺乏对高级渲染功能(如纹理映射)的支持。PEX[10] 扩展了 X 以包含操纵和绘制 3D 对象的能力。

(PEXlib[7] 是一个使用 PEX 协议的 API。)最初基于 PHIGS,PEX 允许立即模式渲染,这意味着对象可以按描述的方式显示,而不必先完成显示列表。PEX 目前缺乏高级渲染功能(尽管提供此类功能的兼容版本尚未设计),并且仅适用于 X 的用户。然而,从广义上讲,描述图形对象以使用 PEX(或者更确切地说,PEXlib)进行渲染的方法类似于 OpenGL 提供的那些。

与 OpenGL 和 PEXlib 一样,Renderman[16] 是一种 API,它提供了一种渲染几何对象的方法。然而,与这些界面不同的是 ,Renderman 提供了一种编程语言(称为着色语言)来描述这些对象在绘制时如何出现。这种可编程性允许生成看起来非常逼真的图像,但在大多数图形加速器上实现是不切实际的,这使得 Renderman 成为交互式 3D 图形的糟糕选择。

最后,作为描述高级图形对象的方法的结果,有一些 API 提供对 3D 渲染的访问。其中最主要的是 HOOPS[17] 和 IRISInvent or[15]。这些接口提供的对象通常比使用 PEXlib 或 OpenGL 等 API 描述的简单几何图形更复杂;它们可能不仅包括几何图形 ,还包括有关它们如何绘制以及它们如何对用户输入作出反应的信息。

HOOPS 和 Inventor 将程序员从单个绘图操作的繁琐描述中解 放出来,但对复杂对象的简单访问通常意味着失去对渲染的精细控制(或至少使这种控制变得困难)。在任何情况下,OpenGL 都可以为构建此类更高级别的 API 提供良好的基础。

2 OpenGL

在本节中,我们将简要介绍 OpenGL。更全面的描述,请读者参考 [8] 或 [13]。

OpenGL 将图元绘制到帧缓冲区中,并受多种可选模式的影响 。每个图元都是一个点、线段、多边形、像素矩形或位图。 每种模式都可以独立改变;一个模式的设置不会影响其他的设置 (尽管许多模式可能会相互作用以确定最终在帧缓冲区中结束的内容)。通过以函数或过程调用的形式发出命令来设置模式、指定图元和其他 OpenGL 操作。

图 1 显示了 OpenGL 的示意图。命令在左侧输入 OpenGL。大多数命令可以累积在显示列表中以供以后处理。否则,命令将通过处理管道有效发送。

第一阶段提供了一种通过评估输入值的多项式函数来逼近曲线和曲面几何形状的有效方法。下一阶段对顶点描述的几何图元进行操作:点、线段和多边形。在这个阶段,顶点被变换和点亮,图元被裁剪到一个视锥体中,为下一个阶段光栅化做准备。光栅器使用点、线段或多边形的二维描述生成一系列帧缓冲区地址和值。这样产生的每个片段都被馈送到下一个阶段,在它们最终改变帧缓冲区之前对各个片段执行操作。这些操作包括基于传入和先前存储的深度值(以实现深度缓冲)对帧缓冲区进行条件更新,将传入的片段颜色与存储的颜色混合,以及对片段值进行屏蔽和其他逻辑操作。

最后,像素矩形和位图绕过管道的顶点处理部分,通过光栅化直接将一个片段块发送到各个片段操作,最终导致一个像素块被写入帧缓冲区。值也可以从帧缓冲区读回或从帧缓冲区的一部分复制到另一部分。这些传输可能包括某种类型的解码或编码。

3 设计注意事项

设计任何 API 都需要在许多一般因素之间进行权衡,例如完成常见操作的简单性与通用性,或者带有少量参数的许多命令与带有许多参数的少量命令。在本节中,我们将描述影响 OpenGL 开发的 3DAPI 设计所特有的考虑因素。

3.1 性能 Performance

交互式 3D 图形的一个基本考虑因素是性能。渲染一个复杂度不高的 3D 场景需要进行大量计算,并且在交互式应用程序中 ,通常每秒必须重绘几次场景。因此,用于交互式 3D 应用程序的 API 必须提供对其使用的图形硬件功能的有效访问。但是不同的图形子系统提供不同的能力,所以必须找到一个通用的接口

该界面还必须提供一种打开和关闭各种渲染功能的方法。这 是必需的,因为某些硬件可能不支持某些功能,因此无法为这些功能提供可接受的性能,还因为即使有硬件支持,启用某些功能或功能组合可能会显着降低性能。例如,在生成场景的最终图像时,慢速渲染可能是可以接受的,但在处理场景中的对象或调整视点时,通常需要交互速率。在这种情况 下,性能下降的特征可能对最终图像是可取的,但在场景操作期间是不可取的。

3.2 正交性 Orthogonality

由于希望能够打开和关闭功能,因此这样做应该对其他功能几乎没有副作用。例如,如果希望使用单一颜色绘制每个多边形,而不是在其面上插入颜色,则这样做不应影响如何应用光照或纹理。类似地,启用或禁用任何单个功能不应导致渲染结果未定义的不一致状态。这些类型的特征独立性是必要的,它允许程序员轻松地操作特征,而不必为特定的非法或不希望的特征组合生成测试,这可能需要改变貌似不相关的特征的状态。功能独立性的另一个好处是可以以设计界面时未预见到 的有用方式组合功能。

3.3 完备性 Completeness

在具有图形子系统的系统上运行的 3D 图形 API 应该提供一些 方法来访问子系统的所有重要功能。如果某些功能可用但未提供,则程序员被迫使用不同的 API 来获得缺少的功能。由于两个 API 之间的交互,这可能会使应用程序复杂化。

另一方面,如果 API 的实现在一个硬件平台上提供了某些特性 ,那么一般来说,这些特性应该存在于提供 API 的任何平台上 。如果这个规则被打破,那么在一个确定可以在不同硬件平台上运行的程序中使用 API 是很困难的,而不记得确切地在哪 些机器上支持哪些功能。在没有适当加速的平台上,某些功能可能表现不佳(因为它们可能必须在软件中实现),但至少最终会出现预期的图像。

3.4 互操作性 Interoperability

许多计算环境由通过网络连接在一起的许多计算机(通常由不同的公司制造)组成。在这样的环境中,能够在一台机器上发出图形命令并让它们在另一台机器上执行是很有用的( 这种能力是 X 成功的因素之一)。这种能力(称为互操作性) 要求 API 命令的执行模型是客户端‑服务器(client-server):客户端发出命令 ,服务器执行它们。(互操作性还要求客户端和服务器共享相同的 API 命令如何编码以通过网络传输的概念;客户端‑服务器模型只是一个先决条件。)当然,客户端和服务器可能是同一台机器。

由于 API 命令可以通过网络发出,因此要求客户端和服务器之间的紧密耦合是不切实际的。由于网络延迟,客户端可能必须等待一段时间才能响应提交给服务器的请求(往返),而不需要确认的简单服务器请求可以缓冲到一个大组中,以便有效传输到服务端并由服务端执行。

3.5 可扩展性 Extensibility

正如在介绍中所讨论的,3D 图形 API 至少在原则上应该是可扩展的,以包含将来可能会流行的新图形硬件功能或算法。 尽管在 API 首次使用很久之后才能衡量此目标的实现情况,但可以采取一些措施来帮助实现它。API 的正交性是有助于实现这一目标的一个要素。另一个是考虑如果有意省略的功能被添加到 API 中,API 会受到怎样的影响。

3.6 接受性 Acceptance

看起来设计一个干净、一致的 3D 图形 API 本身就足够了。但除非程序员决定在各种应用程序中使用 API,否则设计 API 将毫无用处。因此,值得考虑设计决策对程序员接受 API 的影响 。

4 设计特点

在本节中,我们将重点介绍 OpenGL 设计的一般特征,并使用特定示例对每个特征进行说明和说明。

4.1 基于 IRISGL

OpenGL 基于 SiliconGraphics 的 IRISGL。虽然设计一个全新 的 API 是可能的,但使用 IRISGL 的经验让我们深入了解了程序员在 3D 图形 API 中想要和不想要什么。此外,尽可能使 Open GL 与 IRISGL 相似,这使得 OpenGL 更容易被接受;有许多成 功的 IRISGL 应用程序,IRISGL 的程序员将很容易切换到 OpenGL。

4.2 Low-Level

OpenGL 的一个基本目标是提供设备独立性,同时仍允许完全访问硬件功能。因此,API 提供了对仍提供设备独立性的最低级别的图形操作的访问。因此,OpenGL 不提供描述或建模复杂几何对象的方法。描述这种情况的另一种方式是说 OpenGL 提供了描述如何渲染复杂几何对象的机制,而不是描述复杂对象本身的机制

OpenGL 实用程序库

低级 API 的一个好处是对应用程序必须如何表示或描述高级对象没有要求(因为 API 中没有此类对象的概念)。遵守这一原则意味着基本的 OpenGLAPI 不支持一些传统上与图形 API 相关联的几何对象。例如,OpenGL 实现不需要渲染凹多边形。 这种遗漏的一个原因是凹多边形渲染算法必然比渲染凸多边形的算法复杂,并且不同 的凹多边形算法可能适用于不同的领域。特别是,如果要多次绘制凹多边形,首先将其分解为凸多边形(或三角形)一 次,然后再绘制凸多边形会更有效。省略的另一个原因是, 要渲染一般的凹多边形,必须首先知道它的所有顶点。图形子系统通常不提供具有(几乎)任意数量顶点的凹多边形所需的存储。另一方面,凸多边形可以在指定时简化为三角形 ,因此需要存储的顶点不超过三个。

OpenGL 中低级和高级的区别的另一个例子是 OpenGL 求值器和 NURBS 之间的区别。评估器接口为在 OpenGL 之上构建通用多项式曲线和曲面包提供了基础。在 OpenGL 中提供求值器而不是更复杂的 NURBS 接口的一个优点是,表示曲线和曲面 的应用程序(而不是 NURBS)或利用特殊表面属性的应用程序仍然可以访问有效的多项式求值器(可以在图形硬件中实现),而不会产生转换为 NURBS 表示的成本。

然而,凹多边形和 NURBS 是常见且有用的运算符,并且它们为 IRISGL 的用户所熟悉(至少以某种形式)。因此,作为 OpenGL 实用程序库的一部分提供了一个通用的凹多边形分解, 它随每个 OpenGL 实现一起提供。实用程序库还提供了一个基于 OpenGL 的多项式求值器的接口,用于描述和显示 NURBS 曲线和曲面(带有域空间修剪),以及一种渲染球体、圆锥体和圆柱体的方法。实用程序库既可用作渲染有用几何对象 的方法,也可用作构建使用 OpenGL 进行渲染的其他库的模型 。

在客户端‑服务器环境中,实用程序库引发了一个问题:实用程序库命令在客户端转换为 OpenGL 命令;如果服务器计算机比客户端更强大,则客户端转换可能在服务器上更有效地执行。这种困境不仅出现在 OpenGL 中,而且出现在客户端和服务器可能是不同计算机的任何库中。在 OpenGL 中,基本功能反映了高级图形子系统有效执行的功能,因为无论服务器计算机相对于客户端的功能如何,都假定服务器的图形子系统可以有效地执行它提供的功能。例如,如果将来图形子系统通常提供完整的修剪 NURBS 支持,那么此类功能应该可能从实用程序库迁移到 OpenGL 本身。这样的更改不会对 OpenGL API 的其余部分造成任何中断;另一个块将简单地添加到图 1 的左侧。

4.3 细粒度控制器 Fine-Grained Control

为了最大限度地减少对使用 API 的应用程序必须如何存储和呈现其数据的要求,API 必须提供一种方法来指定几何对象的各个组件及其上的操作。需要这种细粒度的控制,以便可以按任何顺序指定这些组件和操作,从而使渲染操作的控制足够灵活,以适应不同应用程序的要求。

顶点和相关数据

在 OpenGL 中,大多数几何对象是通过包含一系列坐标集来绘制的,这些坐标集指定顶点和可选的法线、纹理坐标和 glBegin glEnd 命令对之间的颜色。例如,要指定一个顶点位于(0,0,0),(0,1,0)and(1,0,1) ,可以这样写

glBegin(GL_POLYGON);
  glVertex3i( 0, 0, c0);
  glVertex3i( 0, 1, 0);
  glVertex3i( 1, 0, 1); 
glEnd();

每个顶点可以用两个、三个或四个坐标指定(四个坐标表示 一个均匀的三维位置)。此外,当前法线、当前纹理坐标和 当前颜色可用于处理每个顶点。OpenGL 在光照计算中使用法线;当前法线是一个三维向量,可以通过发送三个指定它的坐标来设置。颜色可能包含红色、绿色、蓝色和 alpha 值(当 OpenGL 已初始化为 RGBA 模式时)或单个颜色索引值(当初 始化指定颜色索引模式时)。一、二、三或四个纹理坐标确定纹理图像如何映射到图元上。

每个指定顶点坐标、法线、颜色或纹理坐标的命令都有几种风格,以适应不同应用程序的数据格式和坐标数量。数据也可以作为参数列表或作为指向包含数据的存储块的指针传递给这些命令。变体通过助记符后缀来区分。

使用过程调用来指定一起定义图元的每个单独的数据组意味着应用程序可以以它选择的任何格式和顺序存储数据;数据不需要以便于呈现给图形 API 的形式存储,因为 OpenGL 使用数据规范过程的适当组合来适应几乎任何数据类型和格式。 该方案的另一个优点是,通过以适当的顺序简单地组合调用 ,可以获得不同的效果。图 2 显示了通过指定由三角形的所有顶点继承的单一颜色获得的统一颜色三角形的示例;通过在每个顶点之前重新指定颜色来获得平滑的阴影三角形。并非所有可能的数据格式都受支持(例如,可能不会为顶点坐标提供字节值),只是因为从 IRISGL 的经验中发现并非所有格式都被使用。然而,将来添加缺失的格式将是一项微不足道的工作。

在如此精细的粒度上使用过程调用的一个缺点是,如果过程调用代价高昂,可能会导致性能下降。在这种情况下,为一 次发送的数据块指定格式的接口可能具有性能优势。然而, 指定数据块的困难在于,它要么限制应用程序以一种受支持的格式存储其数据,要么要求应用程序将其数据复制到以其中一种格式结构化的块中,从而导致效率低下。

(允许由单个数据类型的任意组合产生的任何格式是不切实 际的,因为组合太多。)

在 OpenGL 中,单个过程调用提供的最大灵活性被认为比使用 这些调用所导致的任何低效率更为重要。这一决定的部分原 因是考虑到现代编译器和计算机硬件已经改进到过程调用通 常相对便宜,尤其是与处理调用中包含的几何数据所需的工作相比 。这是 OpenGL 与 PEX 显着不同的一个领域。使用 PEX,图元 的顶点(和相关数据)通常在单个数组中同时呈现。如果事 实证明细粒度的过程调用过于昂贵,那么可能有必要在 Open GLAPI 中添加一些流行的块格式,或者提供一种机制来定义 这些格式。

4 .4 Modal

作为细粒度控制的结果,OpenGL 维护了相当多的状态或模式 ,这些状态或模式决定了如何渲染图元。这种状态的出现代替了必须为每个图元提供大量信息,这些信息将描述该图元将受到的所有操作的设置。用每个图元呈现如此多的信息是乏味的,并且会导致过多的数据从客户端传输到服务器。因 此,除了定义它所需的信息外,基本上没有信息与图元一起呈现。相反,相当大比例的 OpenGL 命令专门用于控制渲染操作的设置。

模态 API 的一个困难出现在单独的处理器(或进程)在不同的图元上并行操作的实现中。在这种情况下,必须将模式更改广播到所有处理器,以便每个处理器在处理其下一个图元之前接收新参数。因此,模式更改被串行处理,停止原始处理 ,直到所有处理器都接收到更改,并相应地降低性能。在这种系统中减少模式变化影响的一种方法是插入一个在并行处理器之间分配工作的处理器。该处理器可以缓冲一串模式更改,仅在另一个图元最终到达时才一次传输所有更改 [1]。

处理状态更改的另一种方法依赖于定义命名状态设置组,然 后只需提供适当的名称即可调用这些设置(这是 X 和 PEX 采用 的方法)。使用这种方法,命名状态设置的单个命令会更改服务器的设置。出于多种原因,这种方法被 OpenGL 拒绝了。 在内存有限的图形子系统上跟踪多个状态向量(每个可能包 含大量信息)可能是不切实际的。命名状态设置也与强调细粒度控制相冲突;在某些情况下,例如在改变单个模式的状态时,直接传递改变比先设置然后命名所需的状态向量更方 便和高效。最后,命名状态设置方法仍可用于 OpenGL 通过在显示列表中封装状态更改命令(见下文)。

矩阵堆栈

OpenGL 中使用了三种变换矩阵:模型‑视图矩阵,应用于顶 点坐标;纹理矩阵,应用于纹理坐标;以及投影矩阵,它描述了视锥体,并在它们被模型‑视图矩阵转换后应用于顶点坐标。这些矩阵中的每一个都是 4x4 。这些矩阵中的任何一个都可以加载或乘以(be loaded with or multiplied by)一般变换;提供命令来指定旋转、平移和缩放的特殊情况(因为这些情况只需要指定几个参数,而不是一般变换所需的 16 个参数)。一个单独的命令控制一个模式,该模式指示哪个矩阵当前受到这些操作中的任何一个的影响。此外,每种矩阵类型实际上都由一堆可以压入或弹出的矩阵组成。堆栈顶部的矩阵是应用 于坐标并受矩阵操作命令影响的矩阵。

由这三个矩阵堆栈表示的保留状态简化了指定在分层图形数据结构中发现的转换。其他图形 API 也使用矩阵堆栈,但通常 仅作为更通用属性结构的一部分。但是 OpenGL 的独特之处在于提供了三种可以用相同的命令操作的矩阵。例如,纹理矩阵可用于有效地旋转或缩放应用于图元的纹理图像,当与透视图变换结合使用时,甚至可用于获得投影纹理效果,例如聚光灯模拟和使用阴影贴图的阴影效果 [14]。

状态查询和属性栈

几乎任何 OpenGL 参数的值都可以通过适当的 get 命令获得。 还有一堆参数值可以被压入和弹出。出于堆叠目的,所有参数分为 21 个功能组;这些组的任何组合都可以在一次操作中被推送到属性堆栈(弹出操作仅自动恢复上次推送的那些值 )。需要 get 命令和参数堆栈,以便各种库可以有效地使用 OpenGL,而不会相互干扰。

4.5 Framebuffer

大多数 OpenGL 要求图形硬件包含帧缓冲区。这是一个合理的要求,因为几乎所有交互式图形应用程序(以及许多非交互式应用程序)都在具有帧缓冲区的系统上运行。OpenGL 中的一些操作只能通过使用帧缓冲区公开它们的实现来实现(使用 alpha 混合的透明度和使用深度缓冲的隐藏表面去除是两个示例)。尽管 OpenGL 可用于为驱动诸如笔式绘图仪和矢量显示器之类的设备提供信息,但这种使用是次要的。

多通道算法 Multipass Algorithms

使帧缓冲区显式化的一个有用效果是它可以使用多通道算法 ,其中相同的图元被渲染多次。多通道算法的一个示例使用累积缓冲区 [3]:一个场景被渲染多次,每次都有一个稍微不同的视图,结果在帧缓冲区中取平均值。根据每个通道视图的更改方式,此算法可用于实现全窗口抗锯齿、景深效果、运动模糊或这些的组合。多通道算法在 OpenGL 中很容易实现,因为在通道之间只需操作少量参数,并且更改这些参数的值既高效又不会对其他必须保持不变的参数产生副作 用。

不变性 Invariance

对多通道算法的考虑提出了帧缓冲区中绘制的内容如何受或不受更改参数值影响的问题。例如,如果改变视点影响了将颜色分配给图元的方式,则累积缓冲区算法(accumulation buffer algorithm)将不起作用。举 一个更合理的例子,如果某些 OpenGL 功能在硬件中不可用, 那么当该功能打开时,OpenGL 实现必须从硬件切换到软件。 由于硬件和软件实现的细微差别,这样的开关可能会显着影响最终到达帧缓冲区的内容。

OpenGL 规范不是像素精确的;它没有指出在给定特定输入的情况下必须将特定像素设置为的确切值。原因是这样的规范 ,除了困难之外,限制性太强。

不同的实现 OpenGL 在不同的硬件上运行,具有不同的浮点数、光栅化算 法和帧缓冲区配置。尽管如此,实现各种多通道算法并期望得到合理的结果应该是可能的。

出于这个原因,OpenGL 规范给出了某些不变性规则,这些规则规定了在什么情况下,在给定某些输入的情况下,一个特定的实现可能会得到相同的结果(不同系统上的实现永远不需要在给定相同输入的情况下产生相同的结果)。这些规则通常表明,更改控制操作的参数不会因任何其他操作而影响结果,但在打开或关闭操作时不需要这种不变性。这使得实现可以在调用模式时从硬件切换到软件而不破坏不变性。另 一方面,即使在切换某种模式时,程序员可能仍然希望保持不变。

为了适应这种情况,不变性规则所涵盖的任何操作都允许对其控制参数进行设置,从而使操作即使在打开时也像关闭一样。例如,可以打开或关闭一个比较,但是当打开时,执行的比较可以设置为始终(或永远)通过。

4.6 不可编程

OpenGL 不提供编程语言。它的功能可以通过打开或关闭操作或为操作指定参数来控制,但渲染算法本质上是固定的。这 个决定的一个原因是,出于性能原因,图形硬件通常被设计为以特定顺序应用某些操作。用任意算法替换这些操作通常是不可行的。可编程性将与保持 API 接近硬件相冲突,从而与 最大化性能的目标相冲突。

图形管道和每个片段的操作

OpenGL 中的命令执行模型是具有固定拓扑的流水线模型(尽管阶段可能会被切入或切出)。该管道旨在模仿图形子系统的组织。例如,流水线的最后阶段包括在最终将片段放入帧缓冲区之前对片段进行一系列测试和修改。为了在短时间内绘制复杂的场景,许多片段必须在到达帧缓冲区的途中经过这些最后阶段,几乎没有时间来处理每个片段。如此高的填充率需要专用硬件,这些硬件只能在对外部数据的访问最少的情况下执行固定操作。

尽管片段操作是有限的,但是通过适当地组合操作可以获得许多有趣和有用的效果。OpenGL 提供的每片段操作包括

  • alpha 混合:根据 alpha 值将片段的颜色与帧缓冲区中对应像素的颜色混合;
  • 深度测试:将与片段关联的深度值与帧缓 冲区中已经存在的相应值进行比较,并根据比较结果丢弃或保留片段;
  • 模板测试:将参考值与存储在帧缓冲区中的相应值 进行比较,并根据比较结果更新值或丢弃片段。

Alpha 混合可用于实现透明度或在抗锯齿时将片段的颜色与背景的颜色混合;深度测试可以影响深度缓冲(从而去除隐藏的表面);模板测试可用于多种效果 [12],包括突出干扰区域和简单的 CSG(构造实体几何)操作。这些(和其他)操 作可以组合以实现,例如,去除隐藏表面的透明干涉区域, 或任何数量的其他效果。

OpenGL 图形流水线还在图元之间引入了一种正交性。每个顶点,无论它属于一个点、线段还是多边形图元,都以相同的方式处理:它的坐标被转换并且光照(如果启用)为它分配 一个颜色。由这些顶点定义的图元随后被光栅化并转换为片段,位图或图像矩形图元也是如此。所有片段,无论其来源如何,都一视同仁。操作之间的这种同质性从管道中删除了不需要的特殊情况(对于每个原始类型)。它还使不同图元在同一场景中的组合变得自然,而无需为每种图元类型设置特殊模式。

4.7 几何和图像

OpenGL 支持处理 3D(和 2D)几何和 2D 图像。与几何一起使用的 API 还应提供对写入、读取和复制图像的支持,因为几何和图像经常结合在一起,例如当 3D 场景覆盖在背景图像上时 。许多应用于由几何图元产生的片段的每片段操作同样适用 于对应于图像中像素的片段,从而可以轻松地将图像与几何 混合。例如,可以使用 alpha 混合将三角形与图像混合。OpenGL 支持多种图像格式和对图像组件(例如查找表)的操作 ,以提供图像处理的灵活性。

纹理映射

纹理映射通过有效地将图像应用于几何图形,在几何图形和 图像之间提供了重要的联系。OpenGL 通过为指定纹理图像和为帧缓冲区指定的图像提供相同的格式来明确这种耦合。除了有助于为场景添加真实感(图 3a)外,纹理映射还可用于实现许多其他有用的效果 [4]。图 3b 和 3c 显示了两个示例,其中索引纹理图像的纹理坐标是从顶点坐标生成的。OpenGL 的正交性使得通过纹理映射实现这种效果只需启用适当的模式并加载适当的纹理图像,而不会影响场景的底层规范。

4.8 立即模式和显示列表

OpenGL 命令解释的基本模型是立即模式,在这种模式下,服务器收到命令后立即执行;例如,顶点处理甚至可能在它所属的图元的规范完成之前就开始了。即时模式执行非常适用于图元和模式不断变化的交互式应用程序。在 OpenGL 中,立即模式提供的细粒度控制被尽可能地采用:即使是单独的照明参数(例如材质的漫反射颜色)和纹理图像也可以使用具有即时效果的单独命令进行设置。

虽然立即模式提供了灵活性,但如果必须重新指定不变的参数或对象,它的使用可能会效率低下。为了适应这种情况,OpenGL 提供了显示列表。显示列表封装了一系列 OpenGL 命令 (除了少数几个 OpenGL 命令可以放在显示列表中),并存储在服务器上。显示列表在指定时由应用程序赋予一个数字名称;应用程序只需命名显示列表即可使服务器有效地执行列表中包含的所有命令。这种机制为应用程序提供了一种直接 、有效的方法,即使这些相同的命令必须多次执行,也只需一次将一组命令传输到服务器。

显示列表优化

将命令累积到一个组中以重复执行提供了优化的可能性。例如,考虑指定纹理图像。纹理图像通常很大,每当重新指定图像时,都需要从客户端到服务器(或从服务器到其图形子 系统)的大量数据传输,因此可能很慢。出于这个原因,一 些图形子系统配备了足够的存储空间来同时保存多个纹理图像。如果纹理图像定义被放置在显示列表中,那么服务器 能能够在指定图像时仅加载一次该图像。当调用(或重新调用)显示列表时,服务器简单地向图形子系统指示它应该使用已经存在于其内存中的纹理图像,从而避免重新指定整个图像的开销。

像这样的示例表明需要优化显示列表才能获得最佳性能。在纹理图像加载的情况下,服务器应识别显示列表包含纹理图像信息并适当地使用该信息。这种期望给 OpenGL 实现者带来了负担,以确保尽可能有效地处理特殊的显示列表案例。它还增加了应用程序编写人员的负担,让他们知道在可以提高性能的情况下使用显示列表。另 一种可能性是为在立即模式下表现不佳的功能引入特殊命令 。但是这种专门化会使 API 变得混乱,并模糊即时模式和显示列表之间的明显区别。

显示列表层次结构

显示列表可以在 OpenGL 中重新定义,但不能编辑。缺少编辑简化了服务器上的显示列表内存管理,消除了此类管理可能带来的损失。然而,一个显示列表可能会调用其他列表。因此,可以通过以下方式获得类似于显示列表编辑的效果:(1) 构建调用多个从属列表的列表;(2) 重新定义下级列表。这种重新定义在细粒度上是可能的:从属显示列表可能包含任何内容(甚至什么都没有),包括仅单个顶点或颜色命令。

没有自动保存或恢复与显示列表执行相关的模式。(如果需要,可以通过在显示列表中封装适当的命令来显式地执行这种保存和恢复。)这允许在执行显示列表时获得尽可能高的性能,因为几乎没有与执行相关的开销。它还简化了控制显示列表层次结构的模式行为:只有明确设置的模式才会受到影响。显示列表中缺少自动模式行为也有一个缺点:很难并行执行显示列表,因为在一个显示列表中设置的模式必须在 执行以下显示列表之前生效。在 OpenGL 中,显示列表通常不用于定义整个场景或场景的复杂部分,而是用于封装经常重复的模式设置命令组(例如描述纹理图像)或描述简单几何的命令(例如,近似圆环的多边形)。

4.9 深度缓冲区

唯一直接提供的隐藏面去除方法是深度(或 Z)缓缓冲区。这个假设与包含帧缓冲区的图形硬件一致。其他隐藏表面去除方法可以与 OpenGL 一起使用(例如,BSP 树 [2] 与画家算法相结合),但假设硬件从不支持此类方法,因此 OpenGL 不需要明确支持。

4.10 局部着色

OpenGL 提供的唯一着色方法是本地的。也就是说,不直接支持用于确定表面颜色的方法,例如需要从场景的其他部分获取信息的光线追踪或辐射度。原因是这样的方法需要全局场景数据库的知识,但到目前为止,专门的图形硬件被构建为本地化操作的管道,并且不提供存储和遍历表示复杂场景所需的大量数据的设施。仅当着色可以预先计算并且在将图形 对象传输到 OpenGL 之前与图形对象关联的结果时,才可以将 全局着色方法与 OpenGL 一起使用。

4.11 仅渲染

OpenGL 仅提供对渲染操作的访问。没有从键盘和鼠标等设备获取用户输入的设施,因为预计任何运行 OpenGL 的系统(特别是窗口系统)都必须已经提供这些设施。此外,OpenGL 命 GLX Client GLX 令对帧缓冲区的影响最终由分配帧缓冲区资源的窗口系统( Xlib Direct 如果有的话)控制。窗口系统确定 OpenGL 可以访问帧缓冲区 OpenGL 的哪些部分,并向 OpenGL 传达这些部分的结构。这些考虑使 Renderer OpenGL 窗口系统独立。

**X 中的集成 **

X 提供了一个过程接口和一个网络协议,用于创建和操作帧缓冲区窗口以及将某些 2D 对象绘制到这些窗口中。OpenGL 通过使其成为称为 GLX 的正式 X 扩展而集成到 X 中。GLX 由大约十几个调用(带有相应的网络编码)组成,它们在 X 中提供了 OpenGL 的紧凑、通用嵌入。与其他 X 扩展(两个示例是 DisplayPostScript 和 PEX)一样,OpenGL 渲染命令有一个特定的网络协议封装在 X 字节流中。

OpenGL 需要帧缓冲区的一个区域,可以将图元渲染到该区域中。在 X 中,这样的区域称为可绘制区域。窗口,一种可绘制对象,与描述窗口的帧缓冲区配置的视觉相关联。在 GLX 中,视觉被扩展为包含有关 OpenGL 缓冲区的信息,这些信息在未修饰的 X 中不存在(深度、模板、累积、前、后等)。

X 还提供了第二种可绘制对象,即 pixmap,它是一个离屏帧 GLX 提供了对应于 X 像素图的 GLX 像素图,但具有一些视觉效果所指示的附加缓冲区。GLX 像素图为 OpenGL 应用程序提供 了一种将屏幕外渲染到软件缓冲区的方法。

为了使用支持 OpenGL 的可绘制对象(drawable),程序员创建一个针对该可绘制对象的 OpenGL 上下文。创建上下文时,OpenGL 渲染器的副本会使用有关可绘制对象的视觉信息进行初始化。这个 OpenGL 渲染器在概念上(如果不是实际上)是 X 服务器的一部分,因此,一旦创建,X 客户端可以连接到 OpenGL 上下文并发出 OpenGL 命令(图 4)。可以创建多个 OpenGL 上下文,以不同或共享的可绘图对象为目标。任何支持 OpenGL 的可绘制对象也可以用于标准 X 绘图(X 未使用的可绘制对象缓冲区将被 X 忽略)。

在图形子系统是其一部分的计算机上运行的 GLX 客户端可以避免通过 X 服务器传递 OpenGL 令牌。由于消除了令牌编码、解码和分派的开销,因此这种直接渲染可以提高图形性能。GLX 支持但不要求直接渲染。直接渲染是可行的,因为不需要在 X 命令和 OpenGL 命令之间保持顺序,除非命令显式同步。因为 OpenGL 只包含渲染操作,所以它非常适合现有的窗口系统(集成到 Windows 类似于 X 中描述的),而无需复制窗口系统中已经存在的操作(如窗口控制或鼠标事件生成)。它还可以利用窗口系统功能,例如离屏渲染,除其他用途外,还可以将 OpenGL 命令的结果发送到打印机。窗口系统提供的渲染操作甚至可以穿插在 OpenGL 中。

4.12 API 不是协议

PEX 主要被指定为网络协议;PEXlib 是通过 API 呈现该协议。OpenGL,另一方面,主要被指定为一个 API;当 OpenGL 嵌入到需要协议的系 统(如 X)中时,API 以指定的网络协议编码。这种偏好的一个原因是应用程序程序员使用 API 而不是协议。另一个是不同的平台可能接受不同的协议(X 对 X 扩展使用的协议施加了某 些约束,而其他窗口系统可能会施加不同的约束)。这意味着 API 在平台上是不变的,即使协议不能,因此可以使用相同的源代码(至少对于 OpenGL 部分),而不用考虑任何特定的协议。此外,当客户端和服务器是同一台计算机时,OpenGL 命令可以直接传输到图形子系统,而无需转换为通用编码。

不同系统之间的互操作性不会因偏爱 API 规范而不是协议规范而受到损害。在一个制造商的实现下运行的 OpenGL 客户端连接到另一个制造商的 OpenGL 服务器的测试提供了极好的结果 。

5 示例:三种文本

为了说明 OpenGL 在执行不同类型的渲染任务方面的灵活性, 我们概述了用于显示文本的特定任务的三种方法。这三种方法是:使用位图,使用线段生成轮廓文本,以及使用纹理生成抗锯齿文本。

第一种方法将字体定义为一系列显示列表,每个显示列表都包含一个位图:

for i = start + ’a’ to start + ’z’ {
glBeginList(i);
glBitmap( ... );
glEndList();
}

glBitmap 指定指向位图编码的指针和指示位图相对于先前和 后续位图如何定位的偏移量。在 GLX 中,这样定义多个显示列表的效果也可以通过调用 glXUseXFont 来实现。glXUseXFont 生成许多显示列表,每个显示列表都包含来自指定 X 字体的单个字符的位图(和关联的偏移量)。在任何一种情况下 ,其原点是位置在 3D 中的投影的字符串“位图文本”由

glRasterPos3i(x, y, z);
glListBase(start);
glCallLists("Bitmapped Text",14,GL_BYTE);

参见图 5a。glListBase 设置显示列表基数,以便后续 glCallLi sts 引用刚刚定义的字符。glCallLists 调用数组中指定的一系 列显示列表;将数组中的每个值添加到显示列表库中,以获 得要使用的显示列表的编号。在这种情况下,数组是表示字 符串的字节数组。glCallLists 的第二个参数表示字符串的长 度;第三个参数表示字符串是一个 8 位字节数组(16 位和 32 位整数可用于访问超过 256 个字符的字体)。第二种方法与第 一种方法类似,但使用线段来勾勒每个字符。每个显示列表 包含一系列线段:

glTranslate (o x, oy, 0); 
glBegin( GL\_LI NES);
  glVertex( .. .) ;
    ... 
glEnd();
glTranslate (dx-ox, dy-oy, 0);

初始 glTranslate 更新转换矩阵以相对于字符原点定位字符。 最后的 glTranslate 更新该字符的来源,为下一个字符做准备 。与前面的示例一样,使用此方法显示字符串,但由于线段具有 3D 位置,因此文本可以在 3D 中定向和定位(图 5b)。 更一般地说,显示列表可以同时包含多边形和线段,并且可以对它们进行抗锯齿处理。

最后,可以通过创建包含字符数组的纹理图像来采用不同的方法。因此,一定范围的纹理坐标对应于纹理图像中的每个字符。通过在其顶点绘制一个具有适当纹理坐标的矩形,可 以以任何大小和任何 3D 方向绘制每个字符:

glTranslate(ox, oy, 0);
glBegin(GL_QUADS)
  glTexCoord( ... );
  glVertex( ... );
   ...
glEnd();
glTranslate(dx-ox, dy-oy, 0);

如果每个字符的每组命令都包含在一个显示列表中,并且用 于描述纹理图像本身的命令(以及列表库的设置)包含在另 一个名为 TEX 的显示列表中,那么字符串“Texturemappedtex t!!”可以通过以下方式显示:

glCallList(TEX);
glCallLists("Texture mapped text!!",21,GL_BYTE);

这种方法的一个优点是,通过简单地使用适当的纹理过滤, 得到的字符是抗锯齿的(图 5c)。

6 Conclusion

OpenGL 是一种用于交互式应用程序的 3D 图形 API。它旨在提 供对硬件图形功能的最大访问权限,无论此类功能处于什么级别可用。这种效率源于提供对基本操作的直接控制的灵活界面。OpenGL 并不强制使用特定的方法来描述 3D 对象以及它们应该如何显示,而是提供了可以渲染这些对象(无论如何描述)的基本方法。因为 OpenGL 对 3D 渲染施加了最小结构 ,所以它为构建用于处理结构化几何对象的库提供了一个极好的基础,无论特定结构可能是什么。

高性能、特性正交性 、互操作性、在各种系统上的可实现性和可扩展性的目标推动了 OpenGLAPI 的设计。我们已经展示了这些和其他考虑因素对 OpenGL 中呈现操作的影响。结果是一个简单的 API,几 乎没有特殊情况,应该易于在各种应用程序中使用。

OpenGL 的未来工作可能集中在通过优化改进实现,并扩展 A PI 以处理图形硬件提供的新技术和功能。可能包含的候选对象是图像处理运算符、新的纹理映射功能以及其他基本几何图元,例如球体和圆柱体。我们相信,在设计 OpenGLAPI 时所采取的谨慎措施将使这些以及其他扩展变得简单,并将导致 OpenGL 在未来许多年中仍然是一个有用的 3D 图形 API。

References

[1] Kurt Akeley. RealityEngine graphics. In SIGGRAPH 93 Conference Proceedings, pages 109–116, August 1993.

[2] H. Fuchs, Z. M. Kedem, and B. F. Naylor. On visible surface generation by a priori tree structures. Computer Graphics (SIGGRAPH ’80 Proceedings), 14(3):124–133, July 1980.

[3] Paul Haeberli and Kurt Akeley. The accumulation buffer: Hardware support for high-quality rendering. Computer Graphics (SIGGRAPH ’90 Proceedings), 24(2):309–318, July 1990.

[4] Paul Haeberli and Mark Segal. Texture mapping as a fundamental drawing primitive. In Proceedings of the Fourth Eurographics Workshop on Rendering, pages 259–266, June 1993.

[5] Adobe Systems Incorporated. PostScript Language Reference Manual. Addison-Wesley, Reading, Mass., 1986.

[6] International Standards Organization. International standard information processing systems — computer graphics — graphical kernel system for three dimensions (GKS-3D) functional description. Technical Report ISO Document Number 9905:1988(E), American National Standards Institute, New York, 1988.

[7] Jeff Stevenson. PEXlib specification and C language binding, version 5.1P. The X Resource, Special Issue B, September 1992.

[8] Jackie Neider, Mason Woo, and Tom Davis. OpenGL Programming Guide. Addison-Wesley, Reading, Ma., 1993.

[9] Adrian Nye. X Window System User’s Guide, volume 3 of The Definitive Guides to the X Window System. O’Reilly and Associates, Sebastapol, Ca., 1987.

[10] Paula Womack, ed. PEX protocol specification and encoding, version 5.1P. The X Resource, Special Issue A, May 1992.

[11] PHIGS+ Committee, Andries van Dam, chair. PHIGS+ functional description, revision 3.0. Computer Graphics, 22(3):125–218, July 1988.

[12] Jarek Rossignac, Abe Megahed, and Bengt-Olaf Schneider. Interactive inspection of solids: Cross-sections and interferences. Computer Graphics (SIGGRAPH ’92 Proceedings), 26(2):353–360, July 1992.

[13] Mark Segal and Kurt Akeley. The OpenGL graphics system: A specification. Technical report, Silicon Graphics Computer Systems, Mountain View, Ca., 1992.

[14] Mark Segal, Carl Korobkin, Rolf van Widenfelt, Jim Foran, and Paul Haeberli. Fast shadows and lighting effects using texture mapping. Computer Graphics (SIGGRAPH ’92 Proceedings), 26(2):249–252, July 1992.

[15] Paul S. Strauss and Rikk Carey. An object-oriented 3D graphics toolkit. Computer Graphics (SIGGRAPH ’92 Proceedings), 26(2):341–349, July 1992.

[16] Steve Upstill. The RenderMan Companion. Addison-Wesley, Reading, Mass., 1990. [17] Garry Wiegand and Bob Covey. HOOPS Reference Manual, Version 3.0. Ithaca Software, 1991.

本文由mdnice多平台发布

你可能感兴趣的:(OpenGL 工作原理)