游戏引擎剖析一(Jake Simpson)

 

<!-- /* Font Definitions */ @font-face {font-family:Wingdings; panose-1:5 0 0 0 0 0 0 0 0 0; mso-font-charset:2; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:0 268435456 0 0 -2147483648 0;} @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h2 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:2; font-size:16.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:36.0pt; mso-footer-margin:36.0pt; mso-paper-source:0;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:192235129; mso-list-type:hybrid; mso-list-template-ids:-1464176108 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;} @list l0:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:42.0pt; mso-level-number-position:left; margin-left:42.0pt; text-indent:-21.0pt; font-family:Wingdings;} @list l1 {mso-list-id:1172601251; mso-list-type:hybrid; mso-list-template-ids:1380069246 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;} @list l1:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:42.0pt; mso-level-number-position:left; margin-left:42.0pt; text-indent:-21.0pt; font-family:Wingdings;} @list l2 {mso-list-id:1178159947; mso-list-type:hybrid; mso-list-template-ids:1988664660 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;} @list l2:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:42.0pt; mso-level-number-position:left; margin-left:42.0pt; text-indent:-21.0pt; font-family:Wingdings;} @list l3 {mso-list-id:1864319846; mso-list-type:hybrid; mso-list-template-ids:-1256798918 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;} @list l3:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:42.0pt; mso-level-number-position:left; margin-left:42.0pt; text-indent:-21.0pt; font-family:Wingdings;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->

Doom 已经流行了很长时间。对于 Doom 而言,它不仅是一个伟大的游戏,而且它提出并推广了一个新的游戏编程模型——游戏引擎。这一模块化、可扩展的设计概念允许游戏者和编程人员通过调用游戏的核心代码来创建具有新的模型、场景和声音的新游戏。 CounterStrike Team Fortress TacOps Strike Force ,以及及其震撼的 Quake Soccer 是通过已有游戏引擎创建的众多游戏中的佼佼者。这些游戏大部分都采用了 iD Quake 引擎作为它们的基础,而 TacOps Strike Force 均采用了 Unreal Tournament 的引擎。尽管游戏引擎已经成为了游戏者们闲聊时的常见话题,但游戏引擎从哪里开始,到哪里结束,它又是怎样显示场景、播放声音、让这些怪物可以思考,并触发游戏中的事件呢?

第一部分 Start from the Start

我们首先来说明游戏引擎与游戏本身之间的区别。很多人混淆了游戏引擎和游戏这两个概念,这就好像混淆了汽车发动机和汽车。我们可以把发动机从汽车中取出,然后给它建造新的外壳,然后重新使用它。游戏也是如此。游戏引擎可以定义为所有不限于游戏本身的技术。游戏部分则包括为保证游戏运行而必须的部件(如模型、动画、声音、 AI 以及物理效果等)。例如在 Quake 游戏中,游戏引擎程序是 Quake.exe ,而游戏本身的程序是 QAGame.dll CGame.dll

渲染器

我们首先从渲染器开始对游戏引擎设计的讨论。渲染器主要负责场景的显示,用户基于显示的内容作出适当的选择。下面我们介绍渲染器的主要特点以及它为什么这么必要。渲染器是游戏引擎构造中的首要工作,因为如果无法看到任何内容,我们无法知道我们的代码是否正常运行。渲染器大约要花费 50% CPU 时间,而且它也是评价游戏开发者最重要的一个指标。如果渲染器设计错误,那么所有的编程技术,整个游戏都变得毫无意义,开发公司也会成为业界笑柄。

在屏幕上显示像素涉及到 3D 加速卡、 API 、三维数学的知识,并需要了解 3D 硬件的工作原理( and a dash of magic dust )。控制台同样需要这些知识,但控制台并不牵扯到具体的游戏细节。控制台的硬件配置是一个冻结的实时快照( snapshot in time ),而且它与 PC 不同,在其整个生命周期中不能做任何改变。

从一般意义上来说,渲染器的工作是创建游戏独立的视觉效果,要完成这项工作需要大量的技巧。由于附加的 3D 处理往往需要耗费大量的 CPU 时间与内存, 3D 图形学主要负责图形的创建而很少牵扯到图形的处理。这也是一个预算问题,首先要搞清楚你想在哪一环节占用 CPU 时间,以及为了达到最佳的整体效果可以忽视哪部分细节。

创建 3D 世界

3D 世界的 3D 对象是以相互关联的点(顶点)的形式进行存放的。计算机根据这些顶点画直线或填充表面。因此,一个盒子有 8 个顶点,对应于它的八个角,并有 6 个面,对应于它的六个侧面。这是 3D 对象存放的基础。对于复杂的 3D 对象(如 Quake 游戏中的 3D 对象),它们将具有成千上万个顶点(有时具有上百万个),以及上千个多边形表面。上图所示为一个恐龙对象的线框模型。从本质上来说,它和盒子的存放形式是相同的,只是它是由很多很多个小多边形拼凑成一个复杂的场景。

模型与场景的存放是渲染器的功能之一。而对于游戏程序逻辑而言,它并不需要知道对象在内存中如何存放,以及渲染器将如何显示这些模型与场景。游戏逻辑只需要知道渲染器将采用适当的视图表示这些对象,并用适当的动画框架显示适当的模型。

一个好的游戏引擎可以完全替换它的渲染器,而且不需要修改一行代码。很多跨平台的引擎,如 Unreal 以及很多( homegrown console engine )可以做到这一点,例如 GameCube 版本的渲染器模型就可以被替换。

在计算机内存中的空间点的表示除了采用坐标系的方法,也可以采用数学方法进行表示,如利用方程来表示直线或曲线,并生成多边形。很多 3D 卡均采用多边形作为其最终的渲染图元。所谓图元是指在加速卡上可以使用的最低层次的渲染单元。现在大多数的加速卡的图元均采用三角形。较新版本的 nVidia ATI 的加速卡已经提供了以数学方式渲染的功能(称为高次曲面)。但由于这种方式并不是所有图形加速卡的通用标准,因此我们还不能将其作为一种渲染策略。从数据处理的角度来看,这种方式的代价有些昂贵,但它常常是一些新的实验性技术的基础,例如地形渲染或将硬边缘软化。

替换概述

对于一个由上百万个顶点 / 多边形描述的场景,如果第一人称的视角是在 3D 世界的一边,那么将有很多个多边形是不可见的。因为总有一些对象,如墙壁,会遮挡它们。另一方面,即使最好的游戏程序员也不能同时处理 300,000 个三角形并保持 60fps (一个基本的指标),因为当前的图形卡还不具备这种能力。因此我们需要在将数据交给图形卡之前,通过编码删除一些不可见的多边形,这一过程称作替换。

替换的方式有很多种。在进入替换的讨论之前,我们先讨论一下为什么图形加速卡不能处理超高数目的多边形。我的意思是指,最新的图形加速卡不是具有每秒处理上百万个多边形的能力吗?它不是能处理任何对象吗?首先,你必须要知道市场多边形处理率( marketing polygon rates )和实际多边形处理率( real world polygon rates )。市场多边形处理率是加速卡理论上可以达到的数据,其基于的条件是,同样的纹理、同样的大小,而且应用程序只进行将多边形交由图形加速卡处理的工作。然而,在真实的游戏环境中,应用程序往往需要在后台做很多工作——如进行多边形的三维变换,、光照计算、将纹理信息移入显存等。不只要把纹理信息移入缓存,而且每个多边形的细节信息也要放入缓存。现在一些比较新的显卡允许将模型 / 场景的几何信息存放在显存内,但这样会侵占纹理所用的空间,因此代价比较高。除非你在每一帧都要使用这些模型,否则会浪费显存资源。问题的关键是我们读取的信息并不是将要显示的信息,这对于硬件配置不太高的机器尤其明显。

基本的替换方法

最简单的替换方法是把整个场景 (world) 分为几块,每块都有一个可以被显示子块的列表。通过这种方式可以只显示从给定视点可能被看到的部分。如何创建可能被看到的块列表是一件比较复杂的工作,我们可以通过多种方式来实现,如采用 BSP 树、 Portals 等等。

了解 Doom Quake 的人肯定听说 BSP BSP 是指二叉空间分割。这是将游戏场景分割成多个空间,对场景中的多边形进行组织以决定哪些是可见哪些是不可见的。这种方式对于基于软件设计的渲染器避来说,可以避免许多不必要的操作。而且通过这种方式,我们还可以非常高效地获取游戏者在场景中的位置信息。

基于 Portal 的引擎(最先由已经消失的游戏 Prey from 3D Realms 引入游戏领域)将场景中的每个区域构建成一个独立的模型,每个模型具有一个可以看到其他模型的门( or portals )。渲染器将每个模型作为一个独立的场景渲染。至少理论是这样。这是所有的渲染器都要求的部分,而且往往非常重要。其中一些技术属于遮挡剔除的范畴,但所有这些技术都具有同样的目的:尽早剔除不必要的工作。

FPS first-person shooter game )游戏中视图往往有多个三角形,而且玩家完全控制视角,因此忽略或剔除不会被显示的三角形是非常必要的。这种方法在空间仿真中同样适用。在空间仿真中可以看得很远,剔除掉视野外的内容是非常必要的。对于可控视角的游戏,如 RTS real-time strategy ),替换通常实现起来稍微简单一些。渲染器的这部分功能往往在软件中实现,并不交由图形加速卡处理,但最终仍将由图形加速卡完成这些操作。

基本的图形管线流程

下面是游戏中图形管线渲染多边形的流程:

1 )游戏首先确定游戏中有什么对象,什么模型,要使用什么纹理,它们将在哪些动画帧上出现,以及它们在游戏场景中的定位。游戏程序还需要确定照相机的位置和指向。

2 )游戏程序将上述信息传递给渲染器。对于模型,渲染器首先看模型的大小,以及摄像机的位置,然后判断这个模型是否在屏幕上,或者在观察者(从摄像机的视角)的左侧,在观察者的后方,或者目前在不可见的距离。游戏引擎甚至可能会采用一些( some form of world determination )来计算出模型是否可见。

3 )场景显示系统确定摄像机的位置,以及在摄像机的视角下哪些部分或多边形是可见的。它的实现可以采用多种方法,如比较有效的入口技术, BSP 树等。经过替换后的多边形交由多边形渲染器进行处理。

4 )对于传送到渲染器的每个多边形,渲染器根据模型的局部坐标系和世界坐标系进行坐标转换,然后判断多边形是否背向( back-faced )(朝向摄像机的反面)。背向摄像机的多边形将被忽略。不背向摄像机的多边形将根据渲染器发现的附近的所有光照进行光照计算。接下来渲染器将确定多边形采用什么纹理并保证 API 或图形卡基于多边形选择的纹理进行渲染。最后,处理后多边形的多边形将传送给相应的渲染 API 并最终放入图形加速卡内。

很显然上述流程是非常简单的,只是为了让你了解基本思想。下述表格摘自 Dave Salavator 3D pipeline ,可以更具体地说明图形管线的整个流程:

3D 管线

1. 应用程序 / 场景

l         场景 / 几何数据库遍历

l         对象的移动,摄像机的定位及移动

l         对象模型的动画移动

l         3D 场景内容描述

l         包含遮挡剔除的对象可见性检查

l         选择细节层次( LOD

2. 几何

l         几何变换(旋转、平移、缩放)

l         从模型空间到世界空间的变换( Direct3D

l         从世界空间到视图空间的变换

l         观察投影

l         细节剔除

l         背面剔除(可以稍后在屏幕空间进行)

l         光照计算

l         透视分割——变换到裁剪空间

l         裁剪

l         变换到屏幕空间

3. 三角形生成

l         背面剔除(或者可以在观察空间中进行光照计算前完成)

l         斜率 / 角度计算

l         扫描线转换

4. 渲染 / 光栅化

l         着色

l         纹理

l         雾化

l         Alpha 透明度测试

l         深度缓冲

l         反走样(可选)

l         显示

通常我们会把所有的多边形放入不同种类的列表中,然后将列表按照纹理进行排序(这样只需将纹理放入显卡一次,而不是每个多边形都传送一次)。过去这些多边形通常根据它们与照相机的距离进行排序,距离照相机较远的首先被渲染。但现在随着 Z 缓冲的出现,这种方法就不那么重要了。当然,透明的多边形除外,它们需要在所有的非半透明多边形渲染后再进行渲染,这样才能保证场景中位于透明多边形后的内容能够正确显示。很明显,我们需要采用由后到前的方式渲染这些多边形。但是,所有 FPS 游戏的场景中通常没有太多的透明多边形。它可能看起来像有,但相对于那些不具备 Alpha 值的多边形,透明多边形所占的比例非常低。

一旦应用程序将场景传送给 API API 就可以利用硬件加速的变换和光照计算( T&L ),这在现在是很平常的事情。这里我们不打算介绍矩阵运算的相关内容。几何变换允许 3D 显卡按照你的意图,根据相机任意时刻的位置与指向,对这些多边形进行渲染。

每个顶点都可能包含大量的运算,如,由于部分顶点可能不在屏幕上或者部分在屏幕上,我们需要通过裁剪操作来判断给定的多边形是否可见。光照操作根据场景中的光照射到顶点的方式和角度,计算纹理的色彩明亮程度。过去 CPU 负责这些计算,现在这些计算则由图形卡本身完成,这意味着 CPU 可以去做其他事情。很明显这是件好事,但由于不能保证所有的显卡都具有 T&L 的功能,因此程序员必须自己完成这些程序。

曲面片(高次表面)

除了三角形,曲面片的使用现在变得越来越普遍。曲面片不是采用列出大量多边形以及多边形在场景中的位置的方法,而是采用数学公式表示几何体(通常是包含某类曲线的几何体)。通过这种方式,我们可以首先根据实时的方程生成(和变形)多边形网格,然后根据需要决定实际想要从该曲面片上看到的多边形数量。我们可以为实例描述一个管道,现实中有很多这种管道的例子。例如在某些房间中,如果你已经显示了 10 000 个多边形,那么你会说,“好了,这个管道应该只有 100 个多边形,因为我们已经显示了太多的多边形,再显示更多的多边形会降低帧速率。”但是在另外一个房间,如果视窗中只有 5000 个多边形,那么你可以说:“现在,这个管道里可以有 500 个多边形,因为我们还没有达到这一帧的显示预算。”非常美妙的东西——但我们需要首先解这些方程并生成网格,这并不是一件简单的工作。尽管如此,通过 AGP 传送同一对象的曲面方程要比传送顶点更节省成本。 SOF2 改进了这种方法,并构建了它的地形系统。

事实上, ATI TruForm 功能可以将一个基于三角形的模型转换为基于高次方程的曲面,使其平滑,然后再把该模型转换回十倍于原数目的三角形模型。转换后的模型然后被送到管线进行进一步的处理。实际上 ATI 只是在他们的 T&L 引擎前面增加了一个步骤来进行此操作的处理。这种方式的缺点在于控制哪些模型平滑处理而哪些模型不需要进行平滑处理。常常出现的情况是,有些你希望尖锐的边缘,例如鼻子,可能被不合时宜地进行了平滑处理。但仍不能否认这是一个非常聪明的技术,可以预见将来它会得到更多的使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值