游戏引擎基础(一)(渲染和构造3D世界)
本系列转自:http://www.cppblog.com/orlando/archive/2007/12/03/37734.html
谢谢作者有这么好的文章与大家共享!代表所有有幸阅读到次书的读者谢谢先!
第 1 部分 : 游戏引擎介绍, 渲染和构造 3D 世界
介绍
自 Doom 游戏时代以来我们已经走了很远。 DOOM 不只是一款伟大的游戏,它同时也开创了一种新的游戏编程模式 : 游戏 " 引擎 " 。 这种模块化,可伸缩和扩展的设计观念可以让游戏玩家和程序设计者深入到游戏核心,用新的模型,场景和声音创造新的游戏, 或向已有的游戏素材中添加新的东西。大量的新游戏根据已经存在的游戏引擎开发出来,而大多数都以 ID 公司的 Quake 引擎为基础, 这些游戏包括 Counter Strike , Team Fortress , Tac Ops , Strike Force , 以及 Quake Soccer 。 Tac Ops 和 Strike Force 都使用了 Unreal Tournament 引擎。事实上, " 游戏引擎 " 已经成为游戏玩家之间交流的标准用语,但是究竟引擎止于何处,而游戏又从哪里开始呢 ? 像素的渲染,声音的播放,怪物的思考以及游戏事件的触发,游戏中所有这一切的幕后又是什么呢? 如果你曾经思考过这些问题, 而且想要知道更多驱动游戏进行的东西,那么这篇文章正好可以告诉你这些。 本文分多个部分深入剖析了游戏引擎的内核, 特别是 Quake 引擎,因为我最近工作的公司 Raven Software 已经在 Quake 引擎的基础上开发出了多款游戏,其中包括著名的 Soldier of Fortune 。
开始
让我们首先来看看一个游戏引擎和游戏本身之间的主要区别。 许多人们会混淆游戏引擎和整个游戏 。这有点像把一个汽车发动机和整个汽车混淆起来一样 。 你能够从汽车里面取出发动机, 建造另外一个外壳,再使用发动机一次。 游戏也像那。 游戏引擎被定义为所有的非游戏特有的技术。 游戏部份是被称为 ' 资产 ' 的所有内容 ( 模型,动画,声音,人工智能和物理学 ) 和为了使游戏运行或者控制如何运行而特别需要的程序代码, 比如说 AI-- 人工智能。
对于曾经看过 Quake 游戏结构的人来说, 游戏引擎就是 Quake 。 exe ,而游戏部分则是 QAGame 。 dll 和 CGame 。 dll 。 如果你不知道这是什么意思, 也没有什么关系;在有人向我解释它以前, 我也不知道是什么意思。 但是你将会完全明白它的意思。 这篇游戏引擎指导分为十一个部份。 是的, 从数量上来说,总共是十一个部份 ! 每个部分大概 3000 字左右。现在就从第一部分开始我们的探索吧,深入我们所玩游戏的内核,在这里我们将了解一些基本的东西, 为后面的章节作铺垫。。。
渲染器
让我们从渲染器来开始游戏引擎设计的探讨吧, 我们将从游戏开发者 ( 本文作者的背景 ) 的角度来探讨这些问题。事实上,在本文的各个段落,我们将常常从游戏开发者的角度探讨, 也让您像我们一样思考问题 !
什么是渲染器,为什么它又这么重要呢?好吧,如果没有它,你将什么也看不到。它让游戏场景可视化,让玩家 / 观众可以看见场景,从而让玩家能够根据屏幕上所看到的东西作出适当的决断。 尽管我们下面的探讨可能让新手感到有些恐惧,先别去理会它。 渲染器做些什么?为什么它是必须的?我们将会解释这些重要问题。
当构造一个游戏引擎的时候, 你通常想做的第一件事情就是建造渲染器。 因为如果看不见任何东西 – 那么你又如何知道你的程序代码在工作呢 ? 超过 50% 的 CPU 处理时间花费在渲染器上面; 通常也是在这个部分,游戏开发者将会受到最苛刻的评判。 如果我们在这个部分表现很差,事情将会变得非常糟糕, 我们的程序技术,我们的游戏和我们的公司将在 10 天之内变成业界的笑话。 它也是我们最依赖于外部厂商和力量的地方,在这里他们将处理最大限度的潜在操作目标。 如此说来, 建造一个渲染器确实不象听起来那么吸引人(事实如此), 但如果没有一个好的渲染器, 游戏或许永远不会跻身于排行榜前 10 名。
如今,在屏幕上生成像素,涉及到 3D 加速卡, API ,三维空间数学, 对 3D 硬件如何工作的理解等等。对於主机(游戏机)游戏来说,也需要相同类型的知识,但是至少对于主机, 你不必去尝试击中一个移动中的目标。 因为一台主机的硬件配置是固定的 " 时间快照 " , 和 PC (个人计算机)不同, 在一台主机的生命期中,它的硬件配置不会改变。
在一般意义上,渲染器的工作就是要创造出游戏的视觉闪光点,实际上达到这个目标需要大量的技巧。 3D 图形本质上是用最少的努力创造出最大效果的一门艺术, 因为额外的 3D 处理在处理器时间和和內存带宽方面都是极为昂贵的。 它也是一种预算, 要弄清楚你想在什么地方花费处理器时间,而你宁愿在什么地方节省一些从而达到最好的整体效果。 接下来我们将会介绍一些这方面的工具,以及怎样更好的用它们让游戏引擎工作。
建造 3D 世界
最近,当我和一位从事计算机图形方面工作长达数年之久的人会谈时,她向我吐露道, 当她第一次看到实时操纵计算机 3D 图象时, 她不知道这是怎么实现的, 也不知道计算机如何能够存储 3D 图象。 今天这对于在大街上的普通人来说或许是真实的,即使他们时常玩 PC 游戏, 游戏机游戏, 或街机游戏。
下面我们将从游戏设计者的角度讨论创造 3D 世界的一些细节,你也应该看一看 Dave Salvator 所写的 “3D 管线导论 “ ,以便对 3D 图象生成的主要过程有一个整体的了解。
3D 物体(对象)被储存成 3D 世界中的一系列点 ( 被称为顶点 ) , 彼此之间有相互关系,所以计算机知道如何在世界中的这些点之间画线或者是填充表面。 一个立方体由 8 个点组成,每个角一个点。立方体有 6 个表面, 分别代表它的每一个面。 这就是 3D 对象储存的基础。 对于一些比较复杂的 3D 物体, 比如说一个 Quake 的关卡,将有数以千计 ( 有时数以十万计 ) 的顶点, 和数以千计的多边形表面。
参见上图的线框表示(注:原文在这里有一幅图)。 本质上与上面的立方体例子类似, 它仅仅是由许许多多的小多边形组成的一些复杂场景。模型和世界如何储存是渲染器的一部份功能, 而不属于应用程序 / 游戏部份。 游戏逻辑不需要知道对象在內存中如何表示, 也不需要知道渲染器将怎样把他们显示出来。 游戏只是需要知道渲染器将使用正确的视野去表示对象, 并将在正确的动画幀中把正确的模型显示出来。
在一个好的引擎中,渲染器应该是可以完全被一个新的渲染器替换掉, 并且不需要去改动游戏的一行代码。许多跨平台引擎, 而且许多自行开发的游戏机引擎就是这样的,如 Unreal 引擎, -- 举例来说, 这个游戏 GameCube 版本的渲染器就可以被你任意的替换掉。
让我们再看看内部的表示方法 — 除了使用坐标系统,还有其他方法可以在计算机內存里表示空间的点。在数学上,你可以使用一个方程式来描述直线或曲线, 并得到多边形, 而几乎所有的 3D 显示卡都使用多边形来作为它们的最终渲染图元。 一个图元就是你在任何显示卡上面所能使用的最低级的绘制(渲染)单位,几乎所有的硬件都是使用三个顶点的多边形 ( 三角形 ) 。 新一代的 nVidia 和 ATI 显卡可以允许你以数学方式渲染 ( 被称为高次表面 ) , 但因为这不是所有图形卡的标准, 你还不能靠它作为渲染策略。
从计算的角度来看,这通常有些昂贵,但它时常是新的实验技术的基础,例如,地表的渲染,或者对物件锐利的边缘进行柔化。 我们将会在下面的曲面片小节中更进一步介绍这些高次表面。
剔除概观
问题来了。 我现在有一个由几十万个顶点 / 多边形描述的世界。 我以第一人称视角位于我们这个 3D 世界的一边。 在视野中可以看见世界的一些多边形, 而另外一些则不可见, 因为一些物体, 比如一面看得见的墙壁, 遮挡住了它们。 即使是最好的游戏编码人员, 在目前的 3D 显卡上, 在一个视野中也不能处理 300 , 000 个三角形且仍然维持 60fps ( 一个主要目标 ) 。 显卡不能处理它, 因此我们必须写一些代码,在把它们交给显卡处理之前除去那些看不见的多边形。 这个过程被称为剔除。
有许多不同的剔除方法。 在深入了解这些之前,让我们探讨一下为什么图形显示卡不能处理超高数量的多边形。 我是说,最新的图形卡每秒钟不能处理几百万个多边形吗?它不应该能够处理吗 ? 首先,你必须理解市场销售宣称的多边形生成率和真实世界的多边形生成率。 行销上宣称的多边形生成率是图形显示卡理论上能够达到的多边形生成率。
如果全部多边形都在屏幕上, 相同的纹理,相同的尺寸大小, 正在往显示卡上传送多边形的应用程序除了传送多边形以外什么也不做, 这时显卡能处理多少多边形数量, 就是图形芯片厂商呈现给你的数字。
然而,在真实的游戏情形中,应用程序时常在后台做着许多其他的事情 -- 多边形的 3D 变换, 光照计算, 拷贝较多的纹理到显卡內存, 等等。 不仅纹理要送到显示卡, 而且还有每个多边形的细节。一些比较新的显卡允许你实际上在显卡內存本身里面储存模型 / 世界几何细节, 但这可能是昂贵的,将会耗光纹理正常可以使用的空间,所以你最好能确定每一幀都在使用这些模型的顶点, 否则你只是在浪费显示卡上的存储空间。 我们就说到这里了。 重要的是,在实际使用显卡时,并不必然就能达到你在显卡包装盒上所看到的那些指标,如果你有一个比较慢速的 CPU , 或没有足够的內存时,这种差异就尤为真实。
基本的剔除方法
最简单的剔除方式就是把世界分成区域, 每个区域有一个其他可见区域的列表。 那样, 你只需要显示针对任何给定点的可见部分。 如何生成可见视野区域的列表是技巧所在。 再者, 有许多方法可以用来生成可见区域列表, 如 BSP 树, 窥孔等等。
可以肯定,当谈论 DOOM 或 QUAKE 时,你已经听到过使用 BSP 这个术语了。 它表示二叉空间分割。
BSP 是一种将世界分成小区域的的方法,通过组织世界的多边形,容易确定哪些区域是可见的而哪些是不可见的 – 从而方便了那些不想做太多绘制工作的基于软件的渲染器。它同时也以一种非常有效的方式让你知道你位于世界中的什么地方。
在基于窥孔的引擎 ( 最早由 3D Realms 已经取消的 Prey 项目引入游戏世界 ) 里,每个区域 ( 或房间 ) 都建造有自己的模型, 通过每个区域的门 ( 或窥孔 ) 能够看见另外的区段。 渲染器把每个区域作为独立的场景单独绘制。 这就是它的大致原理。 足以说这是任何一个渲染器的必需部份,而且非常重要。
尽管一些这样的技术归类在 " 遮挡剔除 " 之下,但是他们全部都有同样的目的 : 尽早消除不必要的工作。 对於一个 FPS 游戏 ( 第一人称射击游戏 ) 来说,视野中时常有许多三角形,而且游戏玩家承担视野的控制,丢弃或者剔除不可见的三角形就是绝对必要的了。 对空间模拟来说也是这样的, 你可以看见很远很远的地方 – 剔除超过视觉范围外面的东西就非常重要。 对于视野受到限制的游戏来说 – 比如 RTS ( 即时战略类游戏 )-- 通常比较容易实现。 通常渲染器的这个部份还是由软件来完成, 而不是由显卡完成, 由显卡来做这部分工作只是一个时间问题。
基本的图形管线流程
一个简单的例子,从游戏到多边形绘制的图形管线过程大致是这样 :
· 游戏决定在游戏中有哪些对象, 它们的模型, 使用的纹理, 他们可能在什么动画幀,以及它们在游戏世界里的位置。 游戏也决定照相机的位置和方向。
· 游戏把这些信息传递给渲染器。以模型为例 ,渲染器首先要查看模型的大小 ,照相机的位置, 然後决定模型在屏幕上是否全部可见, 或者在观察者 ( 照相机视野 ) 的左边,在观察者的后面,或距离很远而不可见。它甚至会使用一些世界测定方式来计算出模型是否是可见的。 ( 参见下面这条 )
· 世界可视化系统决定照相机在世界中的位置,并根据照相机视野决定世界的哪些区域 / 多边形是可见的。有许多方法可以完成这个任务, 包括把世界分割成许多区域的暴力方法,每个区域直接为 " 我能从区域 D 看见区域 AB & C" , 到更精致的 BSP( 二叉空间分割 ) 世界。 所有通过这些剔除测试的多边形被传递给多边形渲染器进行绘制。
· 对於每一个被传递给渲染器的多边形, 渲染器依照局部数学 ( 也就是模型动画 ) 和世界数学 ( 相对于照相机的位置 ?) 对多边形进行变换,并检查决定多边形是不是背对相机 ( 也就是远离照相机 ) 。背面的多边形被丢弃。 非背面的多边形由渲染器根据发现的附近灯光照亮。然后渲染器要看多边形使用的纹理,并且确定 API/ 图形卡正在使用那种纹理作为它的渲染基础。 在这里,多边形被送到渲染 API ,然后再送给显卡。
很明显这有些过分简单化了,但你大概理解了。 下面的图表摘自 Dave Salvator's 3D 管线一文,可以给你多一些具体细节 :
3D 管线
- 高层的概观
1. 应用程序 / 场景
· 场景 / 几何数据库遍历
· 对象的运动,观察相机的运动和瞄准
· 对象模型的动画运动
·3D 世界内容的描述
· 对象的可见性检查,包括可能的遮挡剔除
· 细节层次的选择 (LOD)
2. 几何
· 变换 ( 旋转,平移, 缩放 )
· 从模型空间到世界空间的变换 (Direct3D)
· 从世界空间到观察空间变换
· 观察投影
· 细节接受 / 拒绝 剔除
· 背面剔除 ( 也可以在后面的屏幕空间中做 )
光照
· 透视分割 - 变换到裁剪空间
· 裁剪
· 变换到屏幕空间
3. 三角形生成
· 背面剔除 ( 或者在光照计算之前的观察空间中完成 )
· 斜率 / 角度计算
· 扫瞄线变换
4. 渲染 / 光栅化
· 着色
· 纹理
· 雾
·Alpha 透明测试
· 深度缓冲
· 抗锯齿 ( 可选择的 )
· 显示
通常你会把所有的多边形放到一些列表内, 然後根据纹理对这个列表排序 ( 这样你只需要对显卡传送一次纹理, 而不是每个多边形都传送一次 ) , 等等。在过去,会把多边形按照它们到相机的距离进行排序,首先绘制那些距离相机最远的多边形, 但现在由于 Z 缓冲的出现,这种方法就不是那么重要了。 当然那些透明的多边形要除外,它们要在所有的非半透明多边形绘制之后才能够绘制 ,这样一来,所有在它们后面的多边形就能正确地在场景中显现出来。 当然,象那样,实际上你必须得从后到前地绘制那些多边形。 但时常在任何给定的 FPS 游戏场景中, 通常没有太多透明的多边形。 它可能看起来像有,但实际上与那些不透明的多边形相比,其比率是相当低的。
一旦应用程序将场景传递到 API , API 就能利用硬件加速的变换和光照处理 (T&L) , 这在如今的 3D 显卡中是很平常的事情。 这里不讨论涉及到的矩阵数学 ( 参见 Dave 的 3D 管线导论 ) ,几何变换允许 3D 显卡按照你的尝试,根据相机在任何时间的位置和方向,在世界的正确角度和位置绘制多边形。
对于每个点或顶点都有大量的计算, 包括裁剪运算,决定任何给定的多边形实际上是否可见,在屏幕上完全不可见或部分可见。 光照运算,计算纹理色彩明亮程度,这取决于世界的灯光从什么角度如何投射到顶点上。 过去,处理器处理这些计算,但现在,当代图形硬件就能为你做这些事情, 这意谓着你的处理器可以去做其他的事情了。很明显这是件好事情 (tm) ,由于不能指望市面上所有的 3D 显卡板上都有 T & L , 所以无论如何你自己将必须写所有的这些例程 ( 再一次从游戏开发者角度来说 ) 。 你将在本文各处的不同段落看到 " 好事情 ( tm)" 这个词汇。 我认为,这些特征为使游戏看起来更好作出了非常有用的贡献。 毫不令人吃惊,你也将会看见它的对立面;你猜到了,那就是 “ 坏事情 (tm)” 。 我正在尝试争取这些词汇的版权, 你要使用他们就得支付一笔小小的费用哟。
曲面片(高次表面)
除了三角形,曲面片的使用现在正变得更普遍。 因为他们能用数学表达式来描述几何 ( 通常涉及某种曲线的几何形体 ) ,而不仅仅只是列出大量的多边形以及在游戏世界中的位置, 所以曲面片 ( 高次表面的另一个名称 ) 非常好。 这样,你实际上就能够动态地根据方程式来建立 ( 和变形 ) 多边形网格, 并决定你实际想要从曲面片上看到的多边形数量。 因此,举例来说,你可以描述一个管道, 然后在世界中就可以有这种管道的许多样例。 在一些房间中, 你已经显示了 10 , 000 个多边形,你可以说, " 因为我们已经显示了大量的多边形,而且任何更多的多边形将会使幀速率下降, 所以这个管道应该只有 100 个多边形 " 。 但在另外一个房间中, 视野中只有 5 , 000 个可见的多边形,你可以说, " 因为我们还没有达到预算可以显示的多边形数量 , 所以,现在这个管道能有 500 个多边形 " 。 非常美妙的东西 -- 但你必须首先知道所有这些,并建立网格,这不是无足轻重的。 通过 AGP 传送同一个对象的曲面方程确实要比传送其大量顶点节省成本。 SOF2 就使用了这个方法的一种变体来建立它的地表系统。
事实上现在的 ATI 显卡具有 TruForm , 它能带一个以三角形为基础的模型,并将该模型转换为基于高次表面的模型,使其平滑 — 接着再用十倍三角形数量把模型转换回基于大量三角形的模型 ( 被称为 retesselation) 。然后模型送往管线做进一步的处理。 实际上 ATI 仅仅在 T & L 引擎之前增加了一个阶段来处理这个过程。这里的缺点是,要控制哪些模型需要被平滑处理而哪些又不需要。你常常想要一些边缘比较尖锐, 比如鼻子,但它却被不恰当的平滑过了。 这仍然是一种很好的技术,而且我能预见它在将来会被更多的应用。
这就是第一部份 -- 我们将会在第二部分继续介绍光照和纹理,下面的章节会更加深入。