注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

 
 
 

日志

 
 

3D游戏引擎剖析   

2009-05-11 22:21:25|  分类: CG转文 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |


  自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--人工智能。 


  让我们从渲染器来开始游戏引擎设计的探讨吧, 我们将从游戏开发者(本文作者的背景)的角度来探讨这些问题。事实上,在本文的各个段落,我们将常常从游戏开发者的角度探讨, 也让您像我们一样思考问题! 


  最近,当我和一位从事计算机图形方面工作长达数年之久的人会谈时,她向我吐露道, 当她第一次看到实时操纵计算机 3D 图象时, 她不知道这是怎么实现的, 也不知道计算机如何能够存储 3D 图象。 今天这对于在大街上的普通人来说或许是真实的,即使他们时常玩 PC 游戏, 游戏机游戏, 或街机游戏。


  问题来了。 我现在有一个由几十万个顶点/多边形描述的世界。 我以第一人称视角位于我们这个 3D 世界的一边。 在视野中可以看见世界的一些多边形, 而另外一些则不可见, 因为一些物体, 比如一面看得见的墙壁, 遮挡住了它们。 即使是最好的游戏编码人员, 在目前的 3D 显卡上, 在一个视野中也不能处理 300,000个三角形且仍然维持 60fps (一个主要目标)。 显卡不能处理它, 因此我们必须写一些代码,在把它们交给显卡处理之前除去那些看不见的多边形。 这个过程被称为剔除。


  最简单的剔除方式就是把世界分成区域, 每个区域有一个其他可见区域的列表。 那样, 你只需要显示针对任何给定点的可见部分。 如何生成可见视野区域的列表是技巧所在。 再者, 有许多方法可以用来生成可见区域列表, 如 BSP 树, 窥孔等等。 


  一个简单的例子,从游戏到多边形绘制的图形管线过程大致是这样:
    · 游戏决定在游戏中有哪些对象, 它们的模型, 使用的纹理, 他们可能在什么动画幀,以及它们在游戏世界里的位置。 游戏也决定照相机的位置和方向。


  - 高层的概观
   1. 应用程序/ 场景
  ·场景/ 几何数据库遍历
  ·对象的运动,观察相机的运动和瞄准
  ·对象模型的动画运动
  ·3D 世界内容的描述
  ·对象的可见性检查,包括可能的遮挡剔除
  ·细节层次的选择 (LOD)


  ·变换 (旋转,平移, 缩放)
  ·从模型空间到世界空间的变换 (Direct3D)
  ·从世界空间到观察空间变换
  ·观察投影
  ·细节接受/ 拒绝 剔除
  ·背面剔除 (也可以在后面的屏幕空间中做)
  光照
  ·透视分割 - 变换到裁剪空间
  ·裁剪
  ·变换到屏幕空间


  ·背面剔除 ( 或者在光照计算之前的观察空间中完成)
  ·斜率/ 角度计算
  ·扫瞄线变换


  ·着色
  ·纹理
  ·雾
  ·Alpha 透明测试
  ·深度缓冲
  ·抗锯齿 (可选择的)
  ·显示


  除了三角形,曲面片的使用现在正变得更普遍。 因为他们能用数学表达式来描述几何 ( 通常涉及某种曲线的几何形体) ,而不仅仅只是列出大量的多边形以及在游戏世界中的位置, 所以曲面片 ( 高次表面的另一个名称) 非常好。 这样,你实际上就能够动态地根据方程式来建立( 和变形 )多边形网格, 并决定你实际想要从曲面片上看到的多边形数量。 因此,举例来说,你可以描述一个管道, 然后在世界中就可以有这种管道的许多样例。 在一些房间中, 你已经显示了 10,000个多边形,你可以说,"因为我们已经显示了大量的多边形,而且任何更多的多边形将会使幀速率下降, 所以这个管道应该只有 100 个多边形"。 但在另外一个房间中, 视野中只有 5,000个可见的多边形,你可以说,"因为我们还没有达到预算可以显示的多边形数量 , 所以,现在这个管道能有 500 个多边形"。 非常美妙的东西 --但你必须首先知道所有这些,并建立网格,这不是无足轻重的。 通过 AGP 传送同一个对象的曲面方程确实要比传送其大量顶点节省成本。 SOF2 就使用了这个方法的一种变体来建立它的地表系统。


  在变换过程中, 通常是在称为观察空间的坐标空间中, 我们遇到了最重要的运算之一: 光照计算。 它是一种这样的事情, 当它工作时,你不关注它,但当它不工作时, 你就非常关注它了。有很多不同的光照方法,从简单的计算多边形对于灯光的朝向,并根据灯光到多边形的方向和距离加上灯光颜色的百分比值,一直到产生边缘平滑的灯光贴图叠加基本纹理。而且一些 API 实际上提供预先建造的光照方法。举例来说,OpenGL 提供了每多边形,每顶点,和每像素的光照计算。 


  接着是生成照明映射,你用第二个纹理映射(照明映射)与已有的纹理混合来产生照明效果。这样工作得很好, 但这本质上是在渲染之前预先生成的一种罐装效果。如果你使用动态照明 (即,灯光移动, 或者没有程序的干预而打开和关闭),你得必须在每一幀重新生成照明映射,按照动态灯光的运动方式修改这些照明映射。灯光映射能够快速的渲染,但对存储这些灯光纹理所需的内存消耗非常昂贵。你可以使用一些压缩技巧使它们占用较少的的内存空间,或减少其尺寸大小, 甚至使它们是单色的 (这样做就不会有彩色灯光了),等等。 如果你确实在场景中有多个动态灯光, 重新生成照明映射将以昂贵的CPU周期而告终。 


  纹理在使3D场景看起来真实方面异常重要,它们是你应用到场景区域或对象的一些分解成多边形的小图片。多重纹理耗费大量的内存,有不同的技术来帮助管理它们的尺寸大小。纹理压缩是在保持图片信息的情况下,让纹理数据更小的一种方法。纹理压缩占用较少的游戏CD空间,更重要的是,占用较少内存和3D 显卡存储空间。另外,在你第一次要求显卡显示纹理的时候,压缩的(较小的) 版本经过 AGP 接口从 PC 主存送到3D 显卡, 会更快一些。纹理压缩是件好事情。 在下面我们将会更多的讨论纹理压缩。 


  游戏引擎用来减少纹理内存和带宽需求的另外一个技术就是 MIP 映射。 MIP 映射技术通过预先处理纹理,产生它的多个拷贝纹理,每个相继的拷贝是上一个拷贝的一半大小。为什么要这样做?要回答这个问题,你需要了解 3D 显卡是如何显示纹理的。最坏情况,你选择一个纹理,贴到一个多边形上,然后输出到屏幕。我们说这是一对一的关系,最初纹理映射图的一个纹素 (纹理元素) 对应到纹理映射对象多边形的一个像素。如果你显示的多边形被缩小一半,纹理的纹素就每间隔一个被显示。这样通常没有什么问题 -- 但在某些情况下会导致一些视觉上的怪异现象。让我们看看砖块墙壁。 假设最初的纹理是一面砖墙,有许多砖块,砖块之间的泥浆宽度只有一个像素。如果你把多边形缩小一半, 纹素只是每间隔一个被应用,这时候,所有的泥浆会突然消失,因为它们被缩掉了。你只会看到一些奇怪的图像。 


  单一纹理映射给整个3D 真实感图形带来很大的不同, 但使用多重纹理甚至可以达到一些更加令人难忘的效果。过去这一直需要多遍渲染(绘制),严重影响了像素填充率。 但许多具有多流水线的3D 加速卡,如ATI's Radeon 和 nVidia's GeForce 2及更高级的显卡,多重纹理可以在一遍渲染(绘制)过程中完成。 产生多重纹理效果时, 你先用一个纹理绘制多边形,然后再用另外一个纹理透明地绘制在多边形上面。这让你可以使纹理看上去在移动,或脉动, 甚至产生阴影效果 (我们在照明一节中描述过)。绘制第一个纹理映射,然后在上面绘制带透明的全黑纹理,引起一种是所有的织法黑色的但是有一个透明分层堆积过它的顶端 , 这就是 -- 即时阴影。 该技术被称为照明映射 ( 有时也称为 暗映射),直至新的Doom ,一直是Id引擎里关卡照明的传统方法。 


  纹理高速缓存的管理游戏引擎的速度至关重要。 和任何高速缓存一样,缓存命中很好,而不命中将很糟糕。如果遇到纹理在图形显示卡内存被频繁地换入换出的情况,这就是纹理高速缓存抖动。发生这种情况时,通常API将会废弃每个纹理,结果是所有的纹理在下一幀将被重新加载,这非常耗时和浪费。对游戏玩家来说,当API重新加载纹理高速缓存时,会导致幀速率迟钝。


  让我们想一想,在今天实际上是如何使用3D 显卡内存的以及在将来又会如何使用。 如今绝大多数3D显卡处理32位像素颜色,8位红色, 8位蓝色,8 位绿色,和 8 位透明度。这些组合的红,蓝和绿256个色度,可以组成 16。7 百万种颜色-- 那是你我可以在一个监视器上看见的所有颜色。 


  我们现在开始讲雾,它是某种视觉上的效果。如今绝大多数的引擎都能处理雾, 因为雾非常方便地让远处的世界淡出视野,所以当模型和场景地理越过观察体后平面进入视觉范围内时,你就不会看见它们突然从远处跳出来了。 也有一种称为体雾的技术。这种雾不是随物体离照相机的距离而定,它实际上是一个你能看见的真实对象,并且可以穿越它,从另外一侧出去 -- 当你在穿越对象的时候,视觉上雾的可见程度随着变化。想象一下穿过云团 -- 这是体雾的一个完美例子。体雾的一些好的实现例子是Quake III一些关卡中的红色雾,或新的Rogue Squadron II 之 Lucas Arts的 GameCube 版本。其中有一些是我曾经见过的最好的云--大约与你能看见的一样真实。


 
  每当厂商提供新的显卡时,我们可以得到硬件支持的更新更复杂的混合模式,从而制作出更多更眩目的效果。GF3+4和最近的Radeon显卡提供的像素操作,已经到了极限。


  用模板产生阴影效果,事情就变得复杂而昂贵了。这里不讨论太多细节(可以写成一篇单独的文章了),其思想是,从光源视角绘制模型视图,然后用这个把多边形纹理形状产生或投射到受影响的物体表面。 


  现在我们开始讨论深度测试, 深度测试丢弃隐藏的像素,过度绘制开始起作用。过度绘制非常简单 – 在一幀中,你数次绘制一个像素位置。它以3D场景中Z(深度)方向上存在的元素数量为基础,也被称为深度复杂度。如果你常常太多的过度绘制, -- 举例来说, 符咒的眩目视觉特效,就象Heretic II,能让你的幀速率变得很糟糕。当屏幕上的一些人们彼此施放符咒时,Heretic II设计的一些最初效果造成的情形是,他们在一幀中对屏幕上每个相同的像素画了40次! 不用说,这必须调整,尤其是软件渲染器,除了将游戏降低到象是滑雪表演外,它根本不能处理这样的负荷。深度测试是一种用来决定在相同的像素位置上哪些对象在其它对象前面的技术,这样我们就能够避免绘制那些隐藏的对象。 


  让我们快速的看一下抗锯齿。当渲染单个多边形时,3D 显卡仔细检查已经渲染的,并对新的多边形的边缘进行柔化,这样你就不会得到明显可见的锯齿形的像素边缘。两种技术方法之一通常被用来处理。 第一种方法是单个多边形层次,需要你从视野后面到前面渲染多边形,这样每个多边形都能和它后面的进行适当的混合。如果不按序进行渲染,最后你会看见各种奇怪的效果。在第二种方法中,使用比实际显示更大的分辩率来渲染整幅幀画面,然后在你缩小图像时,尖锐的锯齿形边缘就混合消失了。这第二种方法的结果不错,但因为显卡需要渲染比实际结果幀更多的像素,所以需要大量的内存资源和很高的内存带宽。


  在结束讨论渲染技术之前,我们快速的说一下顶点和像素着色,最近它们正引起很多关注。顶点着色是一种直接使用显卡硬件特征的方式,不使用API。举例来说,如果显卡支持硬件 T & L ,你可以用DirectX或OpenGL编程,并希望你的顶点通过 T & L 单元 (因为这完全由驱动程序处理,所以没有办法确信),或者你直接利用显卡硬件使用顶点着色。它们允许你根据显卡自身特征进行特别编码,你自己特殊的编码使用T & L 引擎,以及为了发挥你的最大优势,显卡必须提供的其他别的特征。 事实上,现在nVidia 和ATI 在他们大量的显卡上都提供了这个特征。 


  最终,渲染器是游戏程序员最受评判的地方。在这个行业,视觉上的华丽非常重要,因此它为知道你正在做的买单。对于渲染器程序员,最坏的因素之一就是3D 显卡工业界变化的速度。一天,你正在尝试使透明图像正确地工作;第二天 nVidia 正在做顶点着色编程的展示。而且发展非常快,大致上,四年以前为那个时代的 3D 显卡写的代码现在已经过时了,需要全部重写。 甚至John Carmack 这样描述过,他知道四年以前为充分发挥那个时期显卡的性能所写的不错的代码,如今很平凡 -- 因此他产生了为每个新的id项目完全重写渲染器的欲望。Epic 的Tim Sweeney赞同 -- 这里是去年他给我的评论: 


  那么什么是API? 它是应用程序编程接口,将不一致的后端用一致的前端呈现出来。举例来说,很大程度上每种3D显示卡的3D实现方式都有所差别。然而,他们全部都呈现一个一致的前端给最终使用者或者程序员,所以他们知道他们为X 3D显示卡写的代码将会在Y 3D显示卡上面有相同的结果。好吧,不管怎样理论上是那样。 大约在三年以前这可能是相当真实的陈述,但自那以后,在nVidia 公司的引领下,3D显卡行业的事情发生了变化。 


 
  3dfx 创造了T- 缓冲。 nVidia 努力寻求硬件变换和光照计算。Matrox努力获取凹凸贴图。等等。 我以前说过的一句话,"过去几年以来,3D显示卡领域的事情发生了变化。"委婉地说明了这一切。 


  你的角色模型在屏幕上看起来怎么样,怎样容易创建它们,纹理,以及动画对于现代游戏试图完成的`消除不可信`因素来说至关重要。角色模型系统逐渐变得复杂起来, 包括较高的多边形数量模型, 和让模型在屏幕上移动的更好方式。


  骨骼动画的另一个优点是能够根据影响顶点的一些骨架来分别“估价” 每个顶点。例如,双臂的骨架运动,肩,脖子而且甚至躯干都能在肩中影响网格。当你移动躯干的时候,网格就活像一个角色一样移动。总的效果是3D角色能够实现的动画更加流畅和可信,且需要更少的内存。每个人都赢了。 


  先前描述的效果可以通过具有层次的骨骼系统来完成。这是什么意思呢?意思是每块骨架实际上的位置相对于它的父亲,而不是每个骨架直接位于空间中的地方。这意谓着如果你移动父亲骨架,那么它所有的子孙骨架也跟着移动,在代码上不需要任何额外的努力。这是让你能够在任何骨架层次改变动画,而且通过骨骼其余部分向下传递的东西。 


  反向运动学 (IK) 是被许多人们丢弃的一个专业术语,对它的真实含义没有多少概念。IK 是如今游戏里面一个相对比较新的系统。使用 IK ,程序员能够移动一只手,或一条腿, 模型的其余关节自动重新定位,因此模型被正确定向。而且有模型的关节新位置的其馀者他们自己,因此模型正确的被定向。比如,你将会说,"好,手 , 去拾起桌子上的那个杯子"并指出杯子在世界中的位置。手就会移动到那里,且它后面的身体会调节其自身以便双臂移动,身体适当弯曲,等等。


  最后,我们应当快速讨论一下与缩放模型几何复杂度相关的细节级别(LOD)系统(与讨论MIP映射时使用的LOD相对照)。假定如今绝大多数PC游戏支持的处理器速度的巨大范围,以及你可能渲染的任何给定可视场景的动态性质(在屏幕上有一个角色还是12个?), 你通常需要一些系统来处理这样的情况,比如,当系统接近极限试图同时在屏幕上绘制出12个角色,每个角色有3,000个多边形,并维持现实的幀速率。 LOD 被设计来协助这样的情景中。最基本的情况,它是在任何给定时间动态地改变你在屏幕上绘制的角色的多边形数量的能力。面对现实吧,当一个角色走远,也许只有十个屏幕像素高度,你真的不需要3000个多边形来渲染这个角色 -- 或许300个就够了,而且你很难分辨出差别。 


  常常在建立一个含有任何3D成分的游戏时,你最终要试图建立一个将会在里面产生游戏动作的3D环境。 不知怎么的游戏开发者提供了一个建立这种环境的方,它容易修改,有效率,有较低的多边形数量,对于游戏既容易渲染又容易运用物理学。很简单,对吗?当做这个的时候我用左手在做什么?当做这的时候 , 我对我的左手做什么? 是的。不错。 


  回想一下我们在第一部分讨论的BSP (二叉空间分割) 树,你也可能听说过潜在可视集合(PVS)这个术语正被四处谈论。两者都有相同的目标,不去探究涉及到的繁杂的数学,它是一种把世界分解为你能从世界任何给定位置看见的墙壁的最小子集的方式。在实现时,它们仅仅返回你能看见的那些,而不是那些隐藏在可能被遮挡的墙壁后面的。你能想象出这给软件渲染器带来的好处,渲染的每个像素(可能是这样的情形)极为重要。它们也按从后到前的顺序返回那些墙壁,在渲染时这是很方便的,因为你能够在渲染次序中确定一个对象的实际位置。 


  既然我们已经在内存中得到了世界的结构,我们必须防止我们的角色从里面掉落出去,并处理地板,斜坡,墙壁,门,以及移动平台。加之,我们必须正确地处理地心引力,速度变化,惯性,和放置在世界里面的其它对象的碰撞。这被看作是游戏物理学。而且在我们进一步深入讨论之前,我想现在就在这里消除一个神话。任何时候你在世界中看见物理,或者任何人在一个复杂的游戏环境中宣称“真实的物理”,很好,它是BS。超过80%的建造一个有效率游戏物理系统的精力花在简化用来处理世界中对象的真实方程式上面。甚至那时,你时常忽略什么是‘真实的’,并创造一些‘有趣的’东西,毕竟,这是目标所在。


  如今绝大多数的游戏引擎建造有某种效果产生器,这允许我们表现出有洞察力的游戏者期盼的所有可爱的吸引眼球的东西。然而,效果系统幕后所进行的东西能够急剧影响幀速率,所以这是我们需要特别关心的地方。如今我们有很棒的3D显示卡,我们能够传送大量的三角形给它们,而且他们仍然要求更多的三角形。并不总是那样。 在Heretic II,使用它的可爱的软件渲染模式,由于他们漂亮的符咒效果,Raven遇到了相当严重的过度绘制问题。回想当你在屏幕上绘制相同的像素超过一次时,过度绘制就发生了。当你有许多效果正在进行,按其性质你有许多三角形,多个三角形可能相互堆叠在彼此上面。结果是你有许多重复绘制的像素。加上Alpha,这意味着在重新绘制之前你必须读取旧像素并和新的像素混合,这会消耗更多的CPU时间。 


  我们没有过多的谈到游戏中的音乐生成。传统的有两种方法,一种是简单的音乐 .wav 文件(或同等物)。它被预先制作做好,准备运行,和最小忙乱。然而,这些在内存和回放时间方面很昂贵。第二种方式用预设的样本编码MIDI音轨。这时常比较节省内存,但缺点是必须同时把一些声音混合在一起,因而会把声音通道用光。

www.fatman.com) 就是一家这样的公司。音乐可能比其他别的东西更加容易外包,这是他们存在的方式。


  去年底我访谈了Jim Dose--Ritual的前任开发者,现在是Id Software的一个开发者,Doom3脚本系统(和其他一些事情)的设计者。尽管这次访谈有些久了,但仍然是很有洞察力。


  脚本:照相机可以沿着一条设定的路径前进
  游戏时间:照相机有必须要遵循的定义的行为


  快速,准确的世界导航( 也叫做路径-发现) 近来已经成为游戏开发者的圣杯。 让它看起来非常信服是一件非常困难的事情。你需要有局部世界的地理知识—墙壁的位置,台阶,悬崖和建筑物等的边缘。你也需要世界中的对象的知识—比如家具,汽车,尤其是其他人的位置。真正最后的因素是问题所在,一会儿我们将回到这一点上。

http://www.garagegames.com/index.php?sec=mg&mod=v12&page=features 。 然后就是Serious Sam 引擎。这也是需要许可证的,的确值得看一看。如果你对它有兴趣的话,可以联系God Games---他们应当可以给你指明正确的方向。

http://sourceforge.net/projects/crystal ,并在你的游戏中随意使用。这不是一个专业的引擎,但看看所有的部分如何结合在一起时常是一个好的学习经历。

http://www.inside3d.com/qip/home.shtml

http://www.discreet.com/products/gmax/gmaxconsumer/index.html

  评论这张
 
阅读(560)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017