1.3 什么是游戏引擎

本文是Game Engine Architecture的一部分
免责声明

“游戏引擎”这个词最早出现在90年代中期,与当时疯狂流行的id Software公司生产的第一人称射击(FPS)游戏Doom有关。Doom的架构设计将核心软件部分(如三维渲染引擎,碰撞检测系统,或音效系统)与艺术资源,游戏世界,游戏规则非常恰当地分离开来。这种分离设计的价值由于开发人员许可他人更改和重新创作游戏而变得原来越明显。人们创作新艺术作品,改变世界布局,武器,字体,车辆和游戏规则只需要最低限度地修改“引擎”软件。这标志这“mod社区”的诞生 —— 一群个体玩家或小型工作室,通过使用原开发人员提供的免费工具包,修改现有游戏并发布新的版本。到90年代末,一些游戏如Quake III Arena和Unreal,在设计时就已经把重用和“可mod”记在脑子里。引擎通过使如id公司的Quake C这种脚本语言获得了高度的可定制性,引擎的使用许可也成为了引擎开发人员的第二收入来源。今天,游戏开发者可以获得某个游戏引擎的授权,重用其中的大部分关键组件并以此构建游戏。虽然这种做法仍然需要投入大量的资金以定制和修改原引擎,但这比自己重新开发所有引擎核心组件节省了许多。

 
游戏和它的引擎之间的界限是很模糊的。一些引擎对此有一个合理的区分,但其它的则几乎完全没有尝试去区分它们两个。在某个游戏中,负责渲染的代码也许“知道”具体该如何绘制一个兽人。在另一个游戏中,渲染引擎可能会提供通用的材质和着色功能,而“兽人之类的东西”可能完全是由数据来定义。没有任何一个工作室可以做到引擎和游戏的完美分离,这是可以被理解的,因为在开发的过程中,这两个组件的定义经常会发生改变。
 
可以说,数据驱动的架构决定了一个软件是到底仅仅是游戏还是引擎。当一个游戏包含有通过硬编码实现的逻辑和游戏规则,或则采取特殊的代码来应对特定类型的游戏对象,那么重用该软件来制作出不同的游戏就是极度困难的。也许我们可以将“游戏引擎”这个词保留给那些具备可拓展性的,在不修改主要部分的情况下能作为开发起来类型游戏的基础的软件。
 
 
显然这不是黑白分明的。我们可以用一个下降的区域来表示每个引擎的可重用性。图1.1试图在这个区域中标出一些知名游戏/引擎所属的位置。
 
有人会认为游戏引擎可类比为像苹果的QuickTime或微软的Windows Media Player这样的媒体播放器 —— 我们可以在这种通用的软件中“播放”任何可想象的游戏内容。然而这个理想尚未实现(也永远不可能被实现)。大多数游戏引擎被精心设计和调整为在特定的硬件平台上运行特定种类的游戏。甚至最通用的多平台游戏引擎也仅仅适合构建某一种类型的游戏,例如第一人称射击或赛车游戏。毫不夸张的说,引擎和中间件越通用,它们相对于特定场景的应用来说就越低效。
 
这种现象之所以会发生,是因为在设计任何有效的软件模块之时,总要进行某种取舍。这些权衡基于软件将被如何使用以及软件的目标硬件是什么。例如,一个被设计用来处理紧凑的室内环境的渲染引擎可能并不能很好的处理巨大的户外场景。室内引擎可能会使用BSP树或门户系统来确保被墙壁或物体遮挡的部分不会被绘制。而户外引擎,在另一方面,可能会使用不那么精确的遮挡机制,或根本就不采用任何遮挡机制。但它会大量使用分层细节(LOD)技术来确保远处的物体使用低分辨率模型,而近处的物体使用高分辨率模型。
 
更快的计算机硬件和显卡的出现,以及更高效的数据结构与算法的产生,导致不同类型的游戏引擎之间的差异开始慢慢减小。例如,现在我们可以使用一款第一人称射击类的游戏引擎来构建一款即时战略游戏。然而,不同类型游戏之间的权衡以及最优化依旧存在。一个游戏总是可以通过根据需求和硬件平台微调引擎来做到令人影响深刻。

1.2 什么是游戏

本文是Game Engine Architecture的一部分
免责声明

我们大部分人对于“什么是游戏”这个问题可能会有一种很好的直觉。通用术语“游戏”包含棋盘类游戏像国际象棋和大富翁,卡片游戏如扑克牌和二十一点,赌博游戏如轮盘赌和老虎机,军事战争游戏,电脑游戏,孩子们所玩的各式各样的游戏,等等。在学术界我们有时会说到“博弈论”,也就是多名代理人在一个给定的设计好的游戏规则框架中,各自使用策略以使自己的收益最大化。在电脑和游戏机平台的语义下,“游戏”这个词通常令人脑海中浮现出一个虚拟的三维世界,玩家控制着其中的一个机器人,动物或者载具作为主要角色。(也许对于我们这些老家伙来说,它让人联想到二维的经典游戏如“乒乓”,“吃豆人”和“大金刚”。)在Raph Koster所著的优秀的书籍《A Thory of Fun for Game Design》中,Raph Koster将“游戏”定义为一种交互体验,向玩家提供一系列越来越具有挑战性的模式,玩家最终会学习并完全掌握这种模式[26]。Koster断言,学习直到最终掌握游戏模式的过程是人们所说的“乐趣”的核心,就像一个笑话会在我们“听懂”它时变得有趣。

基于本书的目的,我们将着重讨论游戏的一个子集,即在二维或三维的虚拟环境下,由少数玩家(1到16左右)参与进行的游戏。我们所学到的许多知识也可以应用到互联网Flash游戏,如俄罗斯方块的纯益智游戏,或大型多人在线游戏(MMOG)上。但是,我们将把重点放在第一人称射击游戏,第三人称动作/平台游戏,竞速游戏,格斗游戏这些游戏的引擎上。

 

1.2.1 作为软实时模拟系统的视频游戏
大多数的二或三维游戏就是科学家口中所说的基于代理人的交互式软实时计算机仿真系统的绝佳例子。让我们休息一下,以便更好地理解这个短语是什么意思。
在大多数视频游戏中,一些真实世界的子集 —— 或者是一个虚构的世界 —— 将被数学建模,以使计算机能操纵它们。该模型是一个对现实世界(或是一个虚构的现实世界)的近似和简化,因为显然包含原子甚至是夸克水平的细节是不切实际的。因此,数学模型是对现实的或想象中的游戏世界的模拟。近似和简化是游戏开发人员所使用的两个最具威力的工具。当使用技巧得当,一个极度简化的模型甚至可以和现实近似得难以区分 —— 并显得更加有趣。
基于代理人的模拟是指令一系列我们称之为“代理人”的离散实体之间产生互动。这符合大多数3D电脑游戏的描述,在这些游戏中,车辆,人物角色,火球,能量点(kid:是指吃豆人中的豆子吗?)都可以看做是代理人。考虑到大多数游戏都是基于代理人的,现今大部分游戏都是使用面向对象,或者至少是基于对象的编程语言实现的,我们对此不应感到奇怪。
所有的互动视频游戏都是暂时态仿真,这意味着虚拟游戏世界是动态的 —— 随着故事情节的展开和事件的发展,游戏世界的状态将不断改变。视频游戏还必须对来自人类玩家的不可预知的输入做出响应 —— 因此是交互式暂时态仿真。最后,大部分游戏实时地响应玩家的输入展开它们的故事,使其成为了一个实时的交互式仿真系统。一个值得注意的例外是回合制游戏,如电脑国际象棋或非实时策略游戏。但即便是这种类型的游戏通常也会向用户提供某种形式的实时图形界面。所以,基于本书的目的,我们假设所有的电子游戏都含有至少一部分的有关实时性的限制。
死线是所有实时系统的核心概念。一个明显的例子就是,在视频游戏中,屏幕被要求每秒至少有24次画面刷新,以提供运动的错觉。(大多数游戏的渲染帧率达到了30或60帧每秒,因为这是NTSC制式显示器的屏幕刷新率。)当然,视频游戏中还存在许多各式各样的死线。一个物理模拟可能需要每秒更新120次以保证稳定。游戏角色的人工智能系统也许至少每秒进行一次“思考”,以防止行动笨拙。音频库也许需要每1/60秒被调用一次,以保证声卡缓冲区得到填充。
“软”实时系统意味着错过死线并不会导致灾难性的后果。因此所有电子游戏都属于软实时系统 —— 如果屏幕卡死了,玩家并不会死!与之相对的是硬实时系统,错过了死线可能意味着严重的伤害甚至是人类操作者的死亡。直升机上的航电系统和核电站的控制系统就是硬实时系统的例子。
数学模型可以是解析式的或数值式的。例如解析式(闭合解)数学模型下的刚体由于受到恒定重力而自由落体,通常写成如下形式:
一个解析式的模型可以由已知量计算出任何独立变量。例如在上面的式子中,仅仅给出了了初始位置y0,初始速度v0和重力常量g。只要能找到,这种数学模型非常的方便。然而,许多数学问题并没有闭合解。而在视频游戏中,由于用户的输入是不可预期的,我们不可能用解析式对整个游戏建模。
一个同样的描述刚体受重力影响下坠的数值式模型可能是这样的:
即,刚体在未来某个时间(t+△t)的高度可以通过初始高度以及第一和第二次迭代计算出的高度计算得出。数值式模拟的典型实现是通过反复计算以确定每一个时间片系统的运行状态。游戏以同样的方式工作。一个主要的“游戏循环”运行多次,而在每一次的迭代过程中,各个游戏系统如人工智能,游戏逻辑,物理模拟等等都有机会计算或更新它们在下一个时间片的状态。结果最终由显卡渲染,或由声卡发出声音,或是像游戏手柄的力反馈一样由其它设备输出。

1.1 一个典型的游戏团队结构

本文是Game Engine Architecture的一部分
免责声明

在我们深入研究游戏引擎结构之前,让我们先来简单地看一下一个典型的游戏开发团队的构成。游戏工作室通常包含五类工作人员,它们分别是工程师,艺术家,游戏设计师,制作人和其他管理及支持人员(营销,法律,技术支持,行政等)。每个种类下面又有更细的分工。下面我们将简要地浏览他们。

 

1.1.1 工程师
工程师们设计并实现那些使游戏及工具运作的软件。工程师通常被分为两个分支:运行时程序员(从事引擎和游戏本身的开发)和工具程序员(他们研发的辅助工具使得开发团队的其他成员能够有效地工作)。在运行时/工具两条分支上的工程师都有着自己的专长。一些工程师在他们的职业生涯中仅专注于引擎的一个系统,如渲染,人工智能,音频,或物理和碰撞。一些工程师将注意力集中在关于游戏性方面的编程和脚本编写上,而另一些则在系统层面上工作,并没有过多地参与到游戏方面。有些工程师是通才 —— 他们能做许多事情,在开发的过程中到处转悠,解决任何可能出现的问题。
高级工程师往往被要求承担技术团队领袖的角色。这些工程师通常仍然设计和编写代码,但它们也同时帮忙管理团队的时间进度,在项目总体方向相关的地方做决策,有时还会从人力资源的角度直接参与人员管理。
一些公司拥有一个或几个技术主管(TD),他们的职责是从较高的层面监督一个或者多个项目,确保团队能意识到潜在的技术挑战,了解新兴产业发展和新技术等等。在一个游戏工作室中和技术相关的最高职位是首席技术官(CTO),如果有的话。CTO不仅是整个工作室的技术总管,更在整个公司的行政系统中承担着关键性角色。

 

1.1.2 艺术家
当我们提到游戏行业时,我们说“内容就是王道”。艺术家创作出游戏中的所有视觉和音频内容。艺术家各式各样:
  • 概念设计师创作概念图和原画,为团队提供了“最终的游戏看起来会像怎样”的构想。他们早在游戏开发的概念设计阶段就加入工作,但通常在整个项目的生命周期中继续提供视觉向导。这是很寻常的,因为你经常能看见一个正在运营的游戏的截图和它的概念图惊人地相似。
  • 3D建模师构建虚拟世界中的所有三维几何体。这一工作通常又被分为两种:前景建模师和背景建模师。前者创建物体,人物,车辆,武器和其它填充整个游戏世界的物体。后者建造游戏世界中的静态几何体(地形,建筑物,桥梁等等)。
  • 纹理设计师创建被称之为纹理的二维图片。这些二维图片将被应用在三维物体的表面,以增加更多的细节和真实感。
  • 灯光师将各种动态的和静态的光源布置在游戏世界中。灯光师还通过改变灯光的颜色,强度和方向来最大化每个场景的艺术性及所要表达的情感。
  • 动画师为游戏中的角色和物体赋予动作。如同他们在CG电影制作时的工作,动画师就像是游戏制作过程中的演员。但是,动画师必须有一套独特的技能,那就是将自己创作的动画与底层的游戏引擎无缝地组合在一起。
  • 动作捕捉演员经常被用来提供一组粗糙的运动数据,动画师将这些数据清理并调整后集成到游戏当中。
  • 音效设计师与工程师密切合作,以创作和混合游戏中的音效和音乐。
  • 配音演员为游戏中的角色提供声音。
  • 许多游戏还拥有一个或多个作曲家,为游戏制作原创音乐。

如同工程师,高级设计师经常会被叫去做团队领导。一些游戏团队有着一个或多个艺术主管 —— 他们通常是非常高级的设计师,负责管理整个游戏的观感以及确保所有团队成员的工作一致性。

 

1.1.3 游戏设计师
游戏设计师的工作是设计玩家与游戏的互动体验部分,我们通常称之为游戏性。不同程度设计师与不同级别的细节打交道。一些(通常是高级)游戏设计师工作在宏观层面,决定故事的主线,章节或关卡的顺序,以及玩家的高层目标。其他设计师工作在个体层面上,或者是虚拟世界中的局部区域。他们放置游戏世界中的几何体,确定何时何地会出现敌人,放置武器和救生包,设计迷宫等等。还有其他一些设计师在一个高技术水平的层面上工作,他们与游戏工程师合作紧密,或者自己编写代码(通常是某种高级脚本语言)。有些游戏设计师本身就是前工程师,因为希望能跟积极地参与决定游戏如何进行而选择成为设计师。
有些游戏团队会雇佣一个或多个文案。游戏文案的工作范围包括从与高级设计师合作编写主线故事,到编写某个角色的对话内容。与其他种类工作人员相似,一些高级设计师发挥着管理者的角色。许多游戏团队拥有一个游戏主管,它的工作室负责监督游戏设计的方方面面,帮助制定管理计划,以及确保设计师个人的设计是与整个产品相一致的。高级设计师有时会演变成制作人。

 

1.1.4 制作人
不同的游戏工作室对制作人有着不同的定义。在有些游戏公司中,制作人的工作是管理进度计划和人力资源。在另一些公司里,制作人的地位相当于一个高级设计师。还有其他的一些公司希望他们的制作人能充当开发团队与公司业务单元之间的联系人。一些较小的工作室则根本没有制作人。例如,在Naughty Dog,几乎公司里的每个人,包括两位共同总裁,都起到了直接参与构建游戏的作用;团队的管理和工作职责在工作室的高级成员之间共享。

 

1.1.5 其他工作人员
直接参与游戏构建的团队通常是由另一个关键的支持团队提供支持。这包括了工作室的执行管理团队,营销部门(或者是一个负责与外部营销集团联络的团队),行政人员和技术支持部门(该部门的工作通常是为整个团队购买,安装和配置软硬件,以及提供技术支持)。

 

1.1.6 出版商和工作室
游戏品牌的营销,制造和分发通常是由一个出版商,而不是游戏工作室本身进行。出版商通常是一家大公司,例如Electronic Arts,THQ,Vivendi,Sony,Nintendo等。许多游戏工作室并不隶属于一个特定的出版商。他们将每个游戏卖给出价最高的出版商。其他工作室仅仅为一个出版商工作,或者是通过长期合同,或者是作为出版公司的全资子公司。例如,THQ的游戏工作室是独立管理,但由THQ所拥有并最终控制的。Electronic Arts通过直接管理它的游戏工作室,把这个关系更近了一步。一流的游戏开发人员和工作室往往直接为游戏制造商所拥有(Sony, Nintendo,和Microsoft)。例如,Naughty Dog就是Sony旗下的一流开发团队。这些工作室仅仅为其母公司所生产的游戏机硬件开发游戏。

1 介绍

本文是Game Engine Architecture的一部分
免责声明

当我在1979年获得我的第一个游戏机 —— 一个由美泰(Mattel’s)生产的电子游戏系统时,“游戏引擎”这个词还不存在。在当时的成年人看来,这些视屏游戏机和街机无非就是玩具。而使这些游戏运转的软件都是为游戏和硬件高度定制化的。今天,游戏业已成为了一个在规模和流行程度上都能与好莱坞相匹敌的数十亿美元的主流行业。而驱动这些无处不在的三维游戏的软件 —— 游戏引擎,像Quake和Doom引擎,Epic Games的虚幻3(Unreal 3)引擎,和Valve’s 的Source引擎等 —— 已经成为了功能齐全的、可重用的软件开发工具包,它们可被授权用于开发几乎任何能想象到的游戏。
尽管游戏引擎的具体实现和架构各有不同,但当我们考察这些开源以及非开源的游戏引擎时,会发现大体上有模式可循。几乎所有的游戏引擎都包含有一系列相似的核心组件,包括渲染引擎,碰撞和物理引擎,动画系统,音效系统,游戏世界对象模型,人工智能系统,等等。在每一个这些组件中,数量相对较少的一些不完全标准设计方案也开始出现。
有相当多的书对游戏引擎的某个子系统,如三维图形,描述得极尽详尽。你可以通过另外的书籍拼凑出游戏技术其他领域的一些有价值的建议和技巧。然而我一直都未能找到一本书,能为读者提供一副组成现代游戏引擎的所有组件的合理全景图。这本书的目标,就是带领和指导读者参观和实践这巨大而复杂的游戏引擎架构景观。
在这本书中,你将学会:
  • 现实中工业级的游戏引擎产品是如何构建的;
  • 现实中的游戏开发团队是如何组织和工作的;
  • 哪个主要的子系统和设计模式在几乎所有游戏引擎中一次又一次地出现;
  • 每个主要子系统的典型需求;
  • 哪些子系统是通用的,哪些子系统是为某种特定类型的游戏而特别设计的。

我们还将亲自观察一些流行的游戏引擎内部是如何运作的,比如Quake和Unreal。与此同时还有一些知名的中间件,例如Havok物理库,OGRE渲染引擎,Red Game Tools的Granny 3D动画与几何体管理工具包。

在我们开始之前,我们将回顾一些在游戏引擎开发上下文中,大型软件工程所使用的技术和工具,包括
  • 逻辑上的与物理上的软件架构的不同;
  • 配置管理,版本控制,构建系统;
  • 一些使用微软Visual Studio开发环境编写C和C++的技巧。

在本书中,我假设您已经对C++有了一个坚实的理解(C++是大多数现代游戏开发商的选择),并了解软件工程的基本原则。我还假设你已经接触过线性代数,三维向量,矩阵数学,和三角函数(虽然我们会在第四章回顾它们的核心概念)。理想情况下你应该接触过一些实时渲染和事件驱动编程的基本概念。但不要害怕,我将会简短地回顾这些主题。如果你觉得在我们开始之前你需要更进一步地磨练你的技能,我还会给你指出正确的方向。

本文是Game Engine Architecture的一部分
免责声明

欢迎来到游戏引擎架构。本书的目的在于对组成典型商业游戏引擎的主要部分进行一个完整的讨论。游戏编程是一个巨大的话题,我们有许多方面需要覆盖。不过,我相信你会发现,我们对每个科目的讨论深度,足以使你对理论和实践产生坚固的理解。也就是说,这本书实际上是一个可能会持续终身的迷人旅程的开始。对于游戏技术的各个方面,市面上有大量的可用信息。本书可以充当着基础,也可以作为更进一步学习的切入点。

我们这本书将会着重于游戏引擎技术和体系结构。这意味着本书不但会覆盖组成一个商业游戏引擎的的各个子系统的理论基础,还会涉及实现这些子系统所用到的典型数据结构,算法,以及软件接口。游戏引擎与游戏之间的界限是相当模糊的。我们将把重点放在引擎本身,包括一系列的底层基础系统,如渲染引擎,碰撞系统,物理模拟,角色动画,以及一个将要被深入讨论的,我称之为“游戏基础层”的层。这一层包括了游戏对象模型,世界编辑器,事件系统,和脚本系统。我们还会谈论到游戏编程的某些方面,包括玩家机制,摄像机,和人工智能。然而,由于需要,这些讨论的范围会被限制在如何实现与游戏引擎之间的接口上。

本书旨在用作于大学二年级或三年级的中级游戏编程系列课程的课本。当然,本书的读者也包括业余软件工程师,爱好者,自学成才的游戏程序员,和目前投身于游戏产业的人。初级工程师可以通过本书巩固他们对游戏数学,引擎架构和游戏技术的理解。而那些投身于游戏某个专业领域的高级工程师也可以从本书所描绘的更大的图景中受益。为了可以更有效地使用本书,你应该要了解面向对象的编程思想,以及至少拥有一些使用C++编程的经验。尽管大量令人兴奋的新语言正在开始掌握游戏行业,工业级的3D游戏引擎仍然主要由C和C++写成。除此之外,任何认真的程序员都需要知道C++。我们将在第三章回顾一些面向对象的基本原则,当你在阅读这本书时,你无疑会捡起一些新的C++技巧。但是,为了获得一个坚实的C++基础,你最好阅读以下[39],[31][32]这几本书。如果你的C++有点儿荒废了,我建议你在阅读本书之前先读读前面基本C++基础书或一些相似的书籍来刷新下自己的知识。如果在此之前你完全没有任何C++经验,那么在深入本书之前你至少需要读读[39]的前几章,或者完成一些C++在线教程。

最好的学习计算机编程的方式就是自己动手写写代码。当你阅读本书时,我强烈鼓励你去挑选一些自己感兴趣的方面,并为自己想出一些项目来做。例如,如果你发现动画这一章节非常有趣,你可以开始安装Ogre3D并探索它的皮肤动画演示程序。然后你可以尝试使用Ogre来实现一些书中描述的动画混合技术。接下来你可能会决定去实现一个简单的使用手柄控制的动画人物,使其在一个平面上奔跑。一旦你拥有了一些简单的作品,扩展它!然后继续向另一个游戏技术领域前进,长此以往。项目是什么并不重要,重要的是你正在实践游戏编程的艺术。游戏技术是一个活着的,会呼吸的东西,它永远不会被一本书完全捕获。因此,额外的资源,刊物,更新,示例代码,和项目的想法将会不时地在本书的网站上发布出来。本书网站: http://gameenginebook.com

(kid:本章的剩下一部分是感谢,我懒得翻译,现在直接将原文贴出来)

Acknowledgments

No book is created in a vacuum, and this one is certainly no exception. This book would not have been possible without the help of my family, friends, and colleagues in the game industry, and I’d like to extend warm thanks to everyone who helped me to bring this project to fruition.

Of course, the ones most impacted by a project like this one are invariably the author’s family. So I’d like to start by off ering a special thank-you to my wife Trina, who has been a pillar of strength during this diffi cult time, taking care of our two boys Evan (age 5) and Quinn (age 3) day aft er day (and night aft er night!) while I holed myself up to get yet another chapter under my belt, forgoing her own plans to accommodate my schedule, doing my chores as well as her own (more oft en than I’d like to admit), and always giving me kind words of encouragement when I needed them the most. I’d also like to thank my eldest son Evan for being patient as he endured the absence of his favorite video game playing partner, and his younger brother Quinn for always welcoming me home aft er a long day’s work with huge hugs and endless smiles.

I would also like to extend special thanks to my editors, Matt Whiting and Jeff Lander. Their insightful, targeted, and timely feedback was always right on the money, and their vast experience in the game industry has helped to give me confi dence that the information presented in these pages is as accurate and up-to-date as humanly possible. Matt and Jeff were both a pleasure to work with, and I am honored to have had the opportunity to collaborate with such consummate professionals on this project. I’d like to thank Jeff in particular for putt ing me in touch with Alice Peters and helping me to get this project off the ground in the fi rst place.

A number of my colleagues at Naughty Dog also contributed to this book, either by providing feedback or by helping me with the structure and topic content of one of the chapters. I’d like to thank Marshall Robin and Carlos Gonzalez-Ochoa for their guidance and tutelage as I wrote the rendering chapter, and Pål-Kristian Engstad for his excellent and insightful feedback on the text and content of that chapter. I’d also like to thank Christian Gyrling for his feedback on various sections of the book, including the chapter on animation (which is one of his many specialties). My thanks also go to the entire Naughty Dog engineering team for creating all of the incredible game engine systems that I highlight in this book. Special thanks
go to Keith Schaeff er of Electronic Arts for providing me with much of the raw content regarding the impact of physics on a game, found in Section 12.1. I’d also like to thank Paul Keet of Electronic Arts and Steve Ranck, the lead engineer on theHydro Thunder project at Midway San Diego, for their mentorship and guidance over the years. While they did not contribute to the book directly, their infl uences are echoed on virtually every page in one way or another.

This book arose out of the notes I developed for a course called ITP-485: Programming Game Engines , which I have been teaching under the auspices of the Information Technology Program at the University of Southern California for approximately three years now. I would like to thank Dr. Anthony Borquez, the director of the ITP department at the time, for hiring me to develop the ITP-485 course curriculum in the fi rst place. I’d also like to extend warm thanks to Ashish Soni, the current ITP director, for his continued support and encouragement as ITP-485 continues to evolve.

My extended family and friends also deserve thanks, in part for their unwavering encouragement, and in part for entertaining my wife and our two boys on so many occasions while I was working. I’d like to thank my sister- and brother-in-law, Tracy Lee and Doug Provins, my cousin-in-law Ma tt Glenn, and all of our incredible friends, including: Kim and Drew Clark, Sherilyn and Jim Kritzer, Anne and Michael Scherer, and Kim and Mike Warner. My
father Kenneth Gregory wrote a book on investing in the stock market when I was a teenager, and in doing so he inspired me to write a book. For this and so much more, I am eternally grateful to him. I’d also like to thank my mother Erica Gregory, in part for her insistence that I embark on this project, and in part for spending countless hours with me when I was a child, beating the art of writing into my cranium—I owe my writing skills (not to mention my work ethic… and my rather twisted sense of humor…) entirely to her!

Last but certainly not least, I’d like to thank Alice Peters and Kevin Jackson-Mead, as well as the entire A K Peters sta ff , for their Herculean efforts in publishing this book. Alice and Kevin have both been a pleasure to work with, and I truly appreciate both their willingness to bend over backwards to get this book out the door under very tight time constraints, and their infi nite patience with me as a new author.

Jason Gregory
April 2009

GEA References

[1] Tomas Akenine-Moller, Eric Haines, and Naty Hoff man. Real-Time Rendering
(3rd Edition). Wellesley, MA: A K Peters, 2008.
[2] Andrei Alexandrescu. Modern C++ Design: Generic Programming and Design
Patt erns Applied. Resding, MA: Addison-Wesley, 2001.
[3] Grenville Armitage, Mark Claypool and Philip Branch. Networking and Online
Games: Understanding and Engineering Multiplayer Internet Games. New York,
NY: John Wiley and Sons, 2006.
[4] James Arvo (editor). Graphics Gems II. San Diego, CA: Academic Press,
1991.
[5] Grady Booch, Robert A. Maksimchuk, Michael W. Engel, Bobbi J. Young,
Jim Conallen, and Kelli A. Houston. Object-Oriented Analysis and Design with
Applications, third edition. Reading, MA: Addison-Wesley, 2007.
[6] Mark DeLoura (editor). Game Programming Gems. Hingham, MA: Charles
River Media, 2000.
[7] Mark DeLoura (editor). Game Programming Gems 2. Hingham, MA: Charles
River Media, 2001.
[8] Philip Dutré, Kavita Bala and Philippe Bekaert. Advanced Global Illumination
(Second Edition). Wellesley, MA: A K Peters, 2006.
[9] David H. Eberly. 3D Game Engine Design: A Practical Approach to Real-Time
Computer Graphics. San Francisco, CA: Morgan Kaufmann, 2001.
[10] David H. Eberly. 3D Game Engine Architecture: Engineering Real-Time
Applications with Wild Magic. San Francisco, CA: Morgan Kaufmann, 2005.
[11] David H. Eberly. Game Physics. San Francisco, CA: Morgan Kaufmann,
2003.
[12] Christer Ericson. Real-Time Collision Detection. San Francisco, CA: Morgan
Kaufmann, 2005.
[13] Randima Fernando (editor). GPU Gems: Programming Techniques, Tips and
Tricks for Real-Time Graphics. Reading, MA: Addison-Wesley, 2004.
[14] James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes.
Computer Graphics: Principles and Practice in C, second edition. Reading, MA:
Addison-Wesley, 1995.
[15] Grant R. Fowles and George L. Cassiday. Analytical Mechanics (7th Edition).
Pacifi c Grove, CA: Brooks Cole, 2005.
[16] John David Funge. AI for Games and Animation: A Cognitive Modeling Approach.
Wellesley, MA: A K Peters, 1999.
[17] Erich Gamma, Richard Helm, Ralph Johnson, and John M. Vlissiddes.
Design Patt erns: Elements of Reusable Object-Oriented Soft ware. Reading, MA:
Addison-Wesley, 1994.
[18] Andrew S. Glassner (editor). Graphics Gems I. San Francisco, CA: Morgan
Kaufmann, 1990.
[19] Paul S. Heckbert (editor). Graphics Gems IV. San Diego, CA: Academic Press,
1994.
[20] Maurice Herlihy, Nir Shavit. The Art of Multiprocessor Programming. San
Francisco, CA: Morgan Kaufmann, 2008.
[21] Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes.
Lua 5.1 Reference Manual. Lua.org, 2006.
[22] Roberto Ierusalimschy. Programming in Lua, 2nd Edition. Lua.org, 2006.
[23] Isaac Victor Kerlow. The Art of 3-D Computer Animation and Imaging (Second
Edition). New York, NY: John Wiley and Sons, 2000.
[24] David Kirk (editor). Graphics Gems III. San Francisco, CA: Morgan Kaufmann,
1994.
[25] Danny Kodicek. Mathematics and Physics for Game Programmers. Hingham,
MA: Charles River Media, 2005.
[26] Raph Koster. A Theory of Fun for Game Design. Phoenix, AZ: Paraglyph,
2004.
[27] John Lakos. Large-Scale C++ Soft ware Design. Reading, MA: Addison-Wesley,
1995.
[28] Eric Lengyel. Mathematics for 3D Game Programming and Computer Graphics,
2nd Edition. Hingham, MA: Charles River Media, 2003.
[29] Tuoc V. Luong, James S. H. Lok, David J. Taylor and Kevin Driscoll.
Internationalization: Developing Soft ware for Global Markets. New York, NY:
John Wiley & Sons, 1995.
[30] Steve Maguire. Writing Solid Code: Microsoft ’s Techniques for Developing Bug-
Free C Programs. Bellevue, WA: Microsoft Press, 1993.
[31] Scott Meyers. Eff ective C++: 55 Specifi c Ways to Improve Your Programs and
Designs (3rd Edition). Reading, MA: Addison-Wesley, 2005.
[32] Scott Meyers. More Eff ective C++: 35 New Ways to Improve Your Programs and
Designs. Reading, MA: Addison-Wesley, 1996.
[33] Scott Meyers. Eff ective STL: 50 Specifi c Ways to Improve Your Use of the Standard
Template Library. Reading, MA: Addison-Wesley, 2001.
[34] Ian Millington. Game Physics Engine Development. San Francisco, CA: Morgan
Kaufmann, 2007.
[35] Hubert Nguyen (editor). GPU Gems 3. Reading, MA: Addison-Wesley, 2007.
[36] Alan W. Paeth (editor). Graphics Gems V. San Francisco, CA: Morgan
Kaufmann, 1995.
[37] C. Michael Pilato, Ben Collins-Sussman, and Brian W. Fitzpatrick. Version
Control with Subversion, second edition. Sebastopol , CA: O’Reilly Media,
2008. (Commonly known as “The Subversion Book.” Available online at
htt p://svnbook.red-bean.com.)
[38] Matt Pharr (editor). GPU Gems 2: Programming Techniques for High-Performance
Graphics and General-Purpose Computation. Reading, MA: Addison-Wesley,
2005.
[39] Bjarne Stroustrup. The C++ Programming Language, special edition (3rd
edition). Reading, MA: Addison-Wesley, 2000.
[40] Dante Treglia (editor). Game Programming Gems 3. Hingham, MA: Charles
River Media, 2002.
[41] Gino van den Bergen. Collision Detection in Interactive 3D Environments. San
Francisco, CA: Morgan Kaufmann, 2003.
[42] Alan Watt . 3D Computer Graphics (3rd Edition). Reading, MA: Addison
Wesley, 1999.
[43] James Whitehead II, Bryan McLemore and Matt hew Orlando. World of
Warcraft Programming: A Guide and Reference for Creating WoW Addons. New
York, NY: John Wiley & Sons, 2008.
[44] Richard Williams. The Animator’s Survival Kit. London, England: Faber &
Faber, 2002.

由于entity和camera性质对立导致的封装性问题

昨天写了一天java终于把工作做完,从图书馆回到宿舍累的倒头就睡,从晚上八点睡到第二天早上10点。一想到再也不用写java真是神清气爽,爬起来继续写Camera类。Camera可以挂载在场景节点上,因此需要通过父节点的位置信息来更新自己的状态。毫无问题!我记得我的Movable类是拥有一个成员叫做mParentNode的。可是当我翻过去看的时候,却惊讶的发现这个成员已经被我自己注释掉了!

怎么回事呢?在我原来的设计中一个Movable只能属于一个SceneNode,但后来当我写到Entity的时候,我又希望一个Entity可以被多个SceneNode重用。因为SceneNode仅仅起到了一个计算位置的作用,所以只要在加入渲染列表的时候将自己的位置传给自己拥有的MovableObject就可以了。

Entity通过传入的节点来更新自身的位置信息,同时为了重用,Entity要将自身Renderable的部分复制一份。由于Entity同时派生自Movable和Renderable,因此这里会发生截断,不过这个截断正是我所希望发生的。

这样,一个Entity被创建出来以后,就可以同时被不同的SceneNode所有,因此在Entity上保存一个父节点指针是没意义的。然而悲剧的是,同样派生自Movable的Camera却需要一个这样的父节点指针,而且Camera并不会在同一时刻被挂到不同的场景节点上。同样可以预见到的另一个悲剧是Light,它和Camera有类似的性质。

遇到这种情况第一时间的反应当然是去看看Ogre是怎么解决这个问题的啦。嘿嘿,Ogre的方式真是简单粗暴:Ogre中,一个Entity不能被多个SceneNode重用。嘛~和我最初的想法蛮一致的嘛。那么如果玩家有一门速射机炮,每秒钟发射60发炮弹,你说该怎么办?Ogre说,嗯,这种情况下你应该调用第一个炮弹Entity的Clone()函数,来复制出剩下的59个。噢噢!真是简单的方法,我怎么没有想到?然而为什么我总感觉有些不对?擦!每个Entity都需要有一个独一无二的名字,因此我需要手工为每个复制出来的炮弹命名?!Ogre说,你个蠢货,Ogre不是会为没有人工指定名字的Entity自动生成一个独一无二的名字吗?我:T_T

于是我决定坚持我自己的方法,这回不听Ogre的!为什么呢,想想看,为了一颗小小的炮弹,居然每秒要生成60个字符串,真是蛋疼……好吧,其实是我实在不想写那个自动生成名字的类。那么我要怎么做呢?完全恢复Movable类里面的mParentNode是不可以的,因为将来可能会有人尝试使用Entity来获取自身的“父节点”。所以我决定部分恢复。

首先,将mParentNode变成protected成员(在saber core中我很少使用protected,极有可能这个会是第一个。即使是在继承关系中,子类也是通过inline的get/set函数来访问父类成员)。在Movable中,仅声明mParentNode的set方法。当Movable被Attach到某个SceneNode上面时,这个SceneNode会调用set方法把自己设为该Movable的父节点。然后在需要使用父节点的子类中,比如Camera或者是Light,我才声明get方法。由于Entity并没有get方法,所以就无法获取Entity的“父节点”了~

说实话这样做让我感觉很不舒服,不过先就这样吧……

camera和viewport 2

让我们来重温一下构成projection matrix的几个属性:FOV(file of view),aspect ratio(宽高比),z near(近平面), z far(远平面)。我们发现这几个属性在绝大部分情况下,是从出生到结束都不会发生变化的(当然有时还是会变的,比如窗口分辨率改变,比如模拟镜头变焦效果)。

反观构成view matrix的几个属性:camera position,look at position, up direction。显然这几个属性会由于摄像机的不断运动而实时改变。

基于以上事实,在一开始我想令Viewport类拥有一个叫做GetProjMatrix()的方法,令Camera类拥有一个叫做GetViewMatrix()的方法,并将FOV,zNear和zFar属性放入Viewport中。这样,Viewport就可以为projection matrix建立缓存,并当自身成员发生变化的时候通知缓存更新。与此相对的是,Camera的view matrix不需要建立缓存。

这种设计最大的优点就是非常简单且耦合度低,然而最大的问题是,camera也需要FOV,zNear和zFar。因为这三个属性实际上构成了camera的视域体(view volume)或者说是视平截头体(view frustum),而SceneManager在遍历场景并决定要将哪些物体送入渲染管线时,需要通过这个范围来进行判定,在视域体之外的物体明显是不需要被渲染的。因此我们应该将FOV,zNear和zFar归入Camera类。

这样会导致一个问题,那就是当camera的属性发生变更了以后,需要通知viewport更新缓存。最初的想法是,将Viewport的某个Notify()函数注册进Camera中。但我觉得这样实在是麻烦,还不如直接在Camera中存一个Viewport的指针。同时按照原来的从属关系,Viewport中本身就存有Camera的指针,因此他们的关系现在变成了这样:

UML使用这种不带箭头的线表示两个类之间的“你中有我我中有你”的蛋疼耦合关系。为了稍稍降低一下复杂度,我规定只有Viewport拥有SetCamera()方法。为了再简化一下问题,我规定Camera和Viewport是一一对应的,也就是说一个Camera仅属于一个Viewport。

当Camera中某个影响projection matrix的属性(比如FOV)发生改变时,将会调用Viewport的Notify()函数,它看起来会是这样:

void scCamera::SetFOV(float fov)
{
    this.mFov = fov;
    if (this.mViewport)
        this.mViewport->NotifyUpdate();
}

在Viewport中我们可以使用懒汉的方式更新缓存,也就是在调用GetProjMatrix()的时候判断是否需要更新,它看起来会是这样:

void scViewport::NotifyUpdate()
{
    this.mNeedUpdate = true;
}

const XMFLOAT4x4& scViewport::GetProjMatrix()
{
    if (mNeedUpdate)
    {
        // update projection matrix
    }
    return mProjMat;
}

GEA翻译免责声明

鉴于Jason Gregory所著的《Game Engine Architecture》是一本好书,国内又没有中文翻译版本,本人志愿在空余时间承担翻译工作,并将译文贴在博客上。

a. 本人基于兴趣以及对英语学习的“热情”翻译此书,任何人不得将此翻译版本用作商业用途。
b. 本人无法保证翻译质量,但会尽力使其信达雅。
c. 本人无法保证翻译速度,但会尽力每天坚持。
d. 如果你手头上有英文原版,并对本人的某些翻译有异议,欢迎将建议发至 963262214@qq.com。
e. 欢迎转载,但希望能注明出处 kidsang.com

kid

鸣谢以下热心的志愿者,他们也为GEA的翻译做出了宝贵的贡献:
Genie – 3.1
Mary
Logic — 4, 4.1
邱嘉伟 — 3.2

camera和viewport

乍一看我们只需要一个camera就够了。虽然说我们可以有很多不同的camera挂载在不同的场景节点上,我们也可以通过切换不同的camera很方便地切换视角,但同一时刻只有一个camera是在工作得。既然如此,我为何不能让一个camera同时“挂载”在多个场景节点上,然后通过切换父节点来改变视角呢?

当然是不行的啦。虽然大部分时候都是只有一个camera在工作,但还是有多情况是需要多个camera同时工作的,比如画中画,比如镜面反射(“分屏”这种这么奇葩的应用我就不说了)。似乎camera是一种很神奇的物种,我们可以用SceneManager创建出很多个camera,却无法命令他们同时工作,因此camera实际上和SceneManager关系不大。真正和camera存在密切联系的是viewport,一个camera是工作在一个viewport之上的。

在我的上一篇SceneNode, Movable和Renderable中有提到Ogre渲染场景的流程,实际上在renderScene()函数之上还有两层调用,这次为了更加明晰我把部分参数也写出来了。

Viewport::update()
    --> Camera::renderScene(Viewport*)
        --> SceneManager::renderScene(Camera*, Viewport*)

在每一层的函数调用中,调用者都会把自己当做参数传入下一层。可见场景的渲染同时需要camera和viewport的协作。

在这里还想提一下viewport和render target的关系。render target可以被理解为画布,而viewport则是作画的区域。显然作画区域再大也不能大得过画布。并且,当viewport尺寸小于render target的时候,内容会被压缩而不是被裁剪。我还没有尝试过大于的情况,我不过猜测那样会导致裁剪。

上图是在同一个窗口同时存在两个viewport的情况。(ps:大家会不会觉得右边的那个物体的角度和左边的那个不一样?哈哈,其实是视觉错觉,我专门打格子测量过,不行你也可以试试看)

无论是camera还是viewport,最终都会被转化为矩阵表现出来。三个基本矩阵中,WorldMatrix表示了物体在场景中的位置,剩下的两个矩阵ViewMatrix和ProjectionMatrix则与camera和viewport息息相关。下面分别列出了Dx11中创建这两个矩阵的API调用。

// view matrix
XMMatrixLookAtRH(XMVECTOR eyePos, XMVECTOR lookatPos, XMVECTOR upDirection)
// projection matrix
XMMatrixPerspectiveFovRH(float FOV, float aspectRatio, float nearZ, float farZ)

函数签名中的RH代表的含义是,所创建的矩阵符合右手坐标系。

XMMatrixLookAtRH()中三个参数的含义分别为摄像机位置,摄像机看向的目标以及摄像机向上的方向向量。第一个其实就是摄像机的父节点的坐标,第二和第三个可以从父节点的Orientation中得到。Orientation是由四元数表示的,一个四元数对应着唯一一个等价旋转矩阵(相反一个旋转矩阵对应着两个等价的四元数)。由于旋转矩阵的三列(或者是三行?)分别代表着节点当前朝向的xyz轴向量,因此camera的upDirection实际上就是y轴,lookPosision可以由z轴以及相机当前位置计算出来。至于四元数如何转换成旋转矩阵,我还是直接贴Ogre的源码吧。

//-----------------------------------------------------------------------
// 注:两个函数是Quaternion的成员函数,那些不知道从哪里冒出来的变量其实都是成员变量
    void Quaternion::ToRotationMatrix (Matrix3& kRot) const
    {
        Real fTx  = x+x;
        Real fTy  = y+y;
        Real fTz  = z+z;
        Real fTwx = fTx*w;
        Real fTwy = fTy*w;
        Real fTwz = fTz*w;
        Real fTxx = fTx*x;
        Real fTxy = fTy*x;
        Real fTxz = fTz*x;
        Real fTyy = fTy*y;
        Real fTyz = fTz*y;
        Real fTzz = fTz*z;

        kRot[0][0] = 1.0f-(fTyy+fTzz);
        kRot[0][1] = fTxy-fTwz;
        kRot[0][2] = fTxz+fTwy;
        kRot[1][0] = fTxy+fTwz;
        kRot[1][1] = 1.0f-(fTxx+fTzz);
        kRot[1][2] = fTyz-fTwx;
        kRot[2][0] = fTxz-fTwy;
        kRot[2][1] = fTyz+fTwx;
        kRot[2][2] = 1.0f-(fTxx+fTyy);
    }

 //-----------------------------------------------------------------------
    void Quaternion::ToAxes (Vector3* akAxis) const
    {
        Matrix3 kRot;

        ToRotationMatrix(kRot);

        for (size_t iCol = 0; iCol < 3; iCol++)
        {
            akAxis[iCol].x = kRot[0][iCol];
            akAxis[iCol].y = kRot[1][iCol];
            akAxis[iCol].z = kRot[2][iCol];
        }
    }

XMMatrixPerspectiveFovRH()函数的第一个参数FOV描述了相机的镜头,越大表示长焦,越小表示广角。第二个参数aspectRatio表示viewport的宽高比,一般而言使用窗口的宽除以窗口的高计算得出。最后两个参数描述了相机的近平面和远平面。相机的投影的景深是由FOV,zNear和zFar共同作用的结果。下面这幅图描述了这三者的关系。

——————————– 华丽分割线 ——————————–

本来今天准备把类的设计也写完的,但是今天被别人叫去写了一天java,真的是恶心死了。明天还要被人拉去写java啊。。。