在计算机图形学的所有应用中,计算机和视频游戏可能最引人注目。为特定游戏选择的图像方法不仅会对游戏引擎代码产生深远影响,还会对美术资产创造产生影响,甚至有时还会对玩法或核心游戏机制产生影响。
尽管游戏图像依赖于前面所有章节的内容,但有两章是特别相关的。游戏需要高效地使用图形硬件,所以理解第17 章的内容很重要。
在这一章中,将详细阐述游戏开发中与图像相关的具体考虑因素,从游戏运行的平台到游戏制作过程。
在这里,我使用“平台”一词来指代硬件、操作系统和 API(应用程序编程接口)的特定组合。游戏运行在各种各样的平台上,从用于基于浏览器的游戏的虚拟机到使用专用硬件和 api 的专用游戏机。
在过去,针对单一平台设计游戏是很常见的。游戏开发成本的增加使得这种情况变得罕见。如今,多平台游戏开发已成为常态。为了支持多个平台而增加的开发成本可能会增加两倍或三倍的用户基础。
有些平台的定义非常松散。例如,当面向 Windows PC 平台开发游戏时,开发者必须考虑各种可能的硬件配置。游戏甚至被期待在不存在的 PC 配置上运行。由于 Windows 平台定义的 api 抽象接口,使得这才成为可能。
开发人员解释图形性能差异的一种方法是根据系统功能调整图形质量。这可以确保在低配系统上的合理性能,同时在高性能系统上仍然获得有竞争力的视觉效果。 这种调整有时是通过分析系统性能自动完成的,但更多的时候,这种控制权留给了用户,他们可以最好地判断自己对质量和速度的个人偏好。 显示分辨率最容易调整,其次是抗锯齿质量。为视觉效果(如阴影和动态模糊)提供几个质量级别也是相当常见的,包括完全关闭效果的选项。
图像性能的差异可能非常大,以至于有些机器可能无法以可玩的帧速率运行游戏,即使是在最低质量设置下。因此,PC 游戏开发者会针对每款游戏发布最低规格和推荐规格。
作为平台,游戏主机有着严格的定义。当开发一款游戏时,例如任天堂的 Wii 主机,开发者清楚地知道游戏将在什么硬件上运行。如果平台的硬件实现发生了变化(通常是为了降低制造成本),主机制造商必须确保新实现与前一个在表现上完全相同,包括时间和性能。 这并不是说主机开发者的任务很简单。控制台 api 往往不那么抽象,更接近底层硬件。这给主机开发带来了自己的困难。从某种意义上说,多平台开发(通常包括至少两个不同的主机平台,通常也包括 Windows)是最难的,因为多平台游戏开发者既不能保证固定的平台,也不能获得单一高级 API 的便利。
基于浏览器的虚拟机(如 Adobe Flash)是一类有趣的游戏平台。尽管这样的虚拟机运行在从个人电脑到移动电话的各种硬件上,但虚拟机提供的高度抽象得到了一个稳定和统一的开发平台。这些平台相对容易的开发和庞大的潜在用户群使它们对游戏开发者越来越有吸引力。然而,这些平台是由所支持硬件的最小公分母定义的,在任何给定平台上,虚拟机的性能都低于本机。基于这些原因,这类平台最适合图像要求不高的游戏。
平台的特点还在于其对开发的开放性,这是商业或法律上的区别,而不是技术上的区别。 例如,Windows 是开放的,因为开发工具是广泛可用的,并且不存在控制 Windows 游戏市场准入的守门人。苹果的 iPhone 是一个更受限制的平台,因为所有应用程序都需要通过认证程序,某些类别的应用程序被完全禁止。主机是最受限制的游戏平台,在这里使用开发工具受到严格控制。 随着更开放的在线主机游戏市场的出现,这种情况有所改观。一个特别有趣的例子是微软的 Xbox LIVE 社区游戏服务,在那里开发工具是免费的,“把关”主要是通过同行评审来完成的。出于安全考虑,通过该服务分发的游戏必须使用微软提供的虚拟机平台。
游戏平台决定了游戏体验的许多元素。例如,PC 玩家使用键盘和鼠标,而主机玩家使用专门的游戏控制器。 许多主机游戏支持同一主机上的多个玩家,要么共享一个屏幕,要么为每个玩家提供一个窗口。由于共用键盘和鼠标的困难,这种类型的游戏在 PC 上找不到。手持游戏系统将拥有与触屏手机不同的控制方案。
尽管游戏平台各不相同,但我们还是可以发现一些共同的趋势。大多数平台都有多个处理核心,分为通用(CPU)和图形专用(GPU)。 随着时间的推移,性能的提高主要是由于核心数量的增加;单核心表现的提升幅度不大。随着 GPU 的普及,GPU 和 CPU 内核之间的界限越来越模糊。存储容量的增长速度往往慢于处理能力的增长速度,通信带宽(核心之间以及每个核心与存储之间)的增长速度更慢。
游戏图像的主要挑战之一是需要管理多个有限资源池。每个平台对硬件资源(如处理时间、存储和内存带宽)都有自己的限制。 在更高一级,还需要管理开发资源;这是一个由程序员、美工和游戏设计师组成的固定规模团队,并在有限的时间内完成游戏。在决定采用哪种图形技术时,需要考虑到这一点(如何管理开发资源)。
早期的游戏开发者只需要担心单个处理器的预算问题。当前的游戏平台包含多个 CPU 和 GPU 内核。这些处理器需要小心地同步,以避免死锁或过多的停机。
由于单个渲染命令所消耗的时间变化很大,因此图形处理器通过命令缓冲区与系统的其余部分解耦。这个缓冲区就像一个队列;命令存储在一端,GPU 从另一端读取渲染命令。增加这个缓冲区的大小可以减少 GPU 饥饿的机会(不在工作的时间)。 游戏在将渲染命令发送给 GPU 之前缓冲整个帧的渲染命令是相当常见的;这保证了不会发生 GPU 饥饿。然而,这种方法需要为两个完整帧的命令保留足够的存储空间(GPU 在一个上工作,而 CPU 在另一个上存储命令)。它还增加了用户输入和显示之间的延迟,这对于快节奏游戏来说可能是个问题。
处理预算是由帧速率决定的,帧速率是场景的新渲染刷新帧缓冲区的频率。在固定平台(如主机)上,用户体验到的帧率基本上与游戏开发者看到的帧率相同,因此可以施加相当严格的帧率限制。大多数游戏的目标帧率是每秒 30 帧;在响应延迟特别重要的游戏中,目标通常是每秒 60 帧。在高度可变的平台(如 pc)上,帧率预算的定义更为宽松。
所需的帧速率为图形程序员提供了固定的每帧预算。在 30 fps 目标的情况下,CPU 核心有 33 毫秒的时间来收集输入,处理游戏逻辑,执行任何物理模拟,遍历场景描述,并将渲染命令发送到图形硬件。同时,其他任务(如音频和网络处理)也必须按照它们自己的响应时间进行处理。当这种情况发生时,GPU 通常执行在前一帧中提交的图形命令。
在大多数情况下,CPU 内核是各向同性资源。所有内核都是相同的,并且它们中的任何一个都同样适合给定的工作负载(也有一些例外,例如索尼 PLAYSTATION 3 主机中使用的 Cell 处理器)。
相比之下,gpu 包含了各种资源的混合,每个资源都专门用于特定的任务集。其中一些资源由固定功能硬件组成(用于三角形光栅化、alpha 混合和纹理采样),还有一些是可编程核心。在较老的 gpu 上,可编程核进一步分化为顶点处理核和像素处理核;较新的 GPU 设计有统一的着色器内核,可以执行任何可编程的着色器类型。
这些异构资源是单独预算的。通常,在任何时候,只有一种资源类型将成为瓶颈,而其他资源类型将具有多余的容量。 一方面,这很好,因为这种容量可以在不降低性能的情况下提高视觉质量。 另一方面,它使提高性能变得更加困难,因为减少任何非瓶颈资源的使用都不会产生任何效果。即使减少瓶颈资源的使用也只能略微提高性能,这取决于“下一个瓶颈”的利用程度。
与任何现代计算系统一样,游戏平台也拥有多阶段存储层次结构,更小、更快的内存类型位于顶部,更大、更慢的存储类型位于底部。这种安排是出于工程上的需要,尽管它确实使开发人员的工作变得复杂。 大多数平台都会有光盘存储,这是极其缓慢的,主要用于交付。 在 Windows 等平台上,将所有数据从光盘移动到硬盘驱动器需要执行一次漫长的安装过程,硬盘驱动器则要快得多。光盘永远不会再使用(除非作为反盗版措施)。 在主机平台上,这种情况不太常见,尽管有时主机上会有硬盘驱动器,如索尼的 PLAYSTATION 3 主机。更常见的情况是,硬盘驱动器(如果存在的话)仅用作光盘的缓存。
内存层次结构的下一步是 RAM,它在许多平台上分为一般系统 RAM 和 VRAM(视频 RAM), VRAM 受益于与图形硬件的高速接口。游戏关卡可能太大而无法放在 RAM 上,在这种情况下,游戏开发者需要根据需要管理将数据移入和移出 RAM。在 Windows 等平台上,通常使用虚拟内存。在主机平台上,通常使用自定义数据流和缓存系统。
最后,CPU 和 GPU 都拥有各种片上内存和缓存。它们非常小,速度非常快,通常由图形 API 管理。
图像资源占用大量内存,所以它们是游戏开发中存储预算的主要关注点。纹理通常是最大的内存消耗者,其次是几何(顶点数据),最后是其他类型的图形数据,如动画。 并非所有内存都可以用于图像——音频也会占用相当一部分,游戏逻辑可能会使用相当大的数据结构。 与处理时间的情况一样,Windows 上的预算往往比较宽松,因为用户系统上存在的确切内存数量是未知的,而虚拟内存解决了许多问题。 相比之下,主机平台上的内存预算是相当严格的——通常主程序员会在电子表格上记录内存,而需要更多内存的程序员则需要向别人请求、借用或窃取。
不同级别的内存不仅在大小上不同,而且在访问速度上也不同。这涉及两个独立的维度:延迟和带宽。
延迟是存储访问请求和最终实现之间的时间。这从几个时钟周期(用于芯片上的 Cache)到数百万时钟周期(用于驻留在光盘上的数据,指 IO)不等。 对于读访问来说,延迟通常是一个问题(尽管如果结果需要很快地从内存中再次读回来,写延迟也可能是一个问题)。 在某些情况下,读请求是阻塞的,这意味着提交读请求的处理器核心在请求完成之前不能做任何其他事情。在其他情况下,读取是非阻塞的。处理核心可以提交读取请求,执行其他类型的处理,然后在读取请求的结果到达后使用读取结果(进程/线程切换)。 GPU 对纹理的访问是非阻塞读取的一个例子;GPU 设计的一个重要方面是,在纹理读取完成时,通过执行不相关的计算,找到“隐藏”纹理读取延迟的方法(在用户的眼中,GPU 似乎没有读取上的延迟)。
为了使这种隐藏延迟的手法能够起作用,必须有与纹理访问相关的充足的计算量。这是着色器作者索要考虑的一个重要的问题;由于内存无法跟上处理能力的增长,计算与纹理访问的最佳组合不断变化(有利于更多的计算)。
带宽是指进出存储的最大传输速率。它通常以每秒千兆字节来衡量。
除了硬件资源(如处理能力和存储空间),游戏图像程序员还必须应对另一种有限的资源——队友的时间!在选择图形技术时,必须考虑实现每种技术所需的工程资源,以及计算输入数据所需的任何工具(在许多情况下,工具可能比实现技术本身花费更多的时间)。 也许最重要的是,必须考虑到对艺术家生产力的影响。大多数图像技术使用的是由游戏美工创造的资产,他们构成了大多数现代游戏团队的最大组成部分。图像程序员必须培养美工的生产力和创造力,这将最终决定游戏的视觉质量。
明智地利用这些有限的资源是游戏图像程序员面临的主要挑战。为此,通常采用各种优化技术。
在许多游戏中,像素着色器处理是主要瓶颈。大多数 gpu 包含基于层次的深度-剔除硬件,可以避免在遮挡的表面上执行像素着色器。为了更好地利用这些硬件,不透明的对象可以从后向前渲染。 或者,最佳的深度剔除用法可以通过执行深度预 pass来实现,即,在正常渲染场景之前,将所有不透明对象渲染到深度缓冲区(只计算深度 z,而不计算任何的颜色以及片元着色器)。这确实会带来一些开销(因为需要渲染每个对象两次),但在许多情况下,性能的提升是值得的。
渲染对象的最快方法是根本不渲染它。因此,任何早期识别物体被遮挡的方法都是有用的。这不仅节省了像素处理,还节省了顶点处理,甚至节省了将对象提交给图形 API 所花费的 CPU 时间。 视锥台剔除是一种普遍使用的方法,但在许多游戏中这还不够。高级遮挡剔除算法经常被使用,利用数据结构,如 PVS(潜在可见集)或 BSP(二进制空间分区)树来快速缩小潜在可见对象范围。
即使一个物体是可见的,它也可能距离太远,以至于它的大部分细节可以被移除而没有明显的影响。LOD(细节层次)算法根据距离(或其他因素,如屏幕覆盖率或重要性)呈现对象的不同表示。这可以节省大量的处理,特别是顶点处理。
在许多情况下,处理甚至可以在游戏开始前执行。这样的预处理结果可以存储并在每一帧中使用,从而加快游戏速度。
这最常用于照明,其中全局照明算法用于计算整个场景的照明,并将其存储在照明贴图(LightMaps)和其他数据结构中以供以后使用。
由于游戏需求的差异很大,所以图像技术的选择取决于所开发的游戏的确切类型。
处理时间的分配很大程度上取决于帧速率。目前,大多数主机游戏倾向于以每秒 30 帧为目标,因为这可以实现更高的图像质量。然而,某些具有快速玩法的游戏类型需要非常低的延迟,这些游戏通常以每秒 60 帧的速度渲染。这包括音乐游戏如《吉他英雄》和第一人称射击游戏如《使命召唤》。
帧率决定了渲染场景的可用时间。场景本身的构成也因游戏而异。大多数游戏都有背景几何(主要是静态的场景)和前景几何(角色和动态对象)的区分。这些由渲染引擎处理是不同的。例如,背景几何体通常具有包含预先计算的光照的光图 LightMaps,这对于前景对象是不可行的。 预先计算的照明通常通过某种类型的体积表示应用于前景物体,可以将每个物体随时间变化的位置考虑进来。
有些游戏拥有相对封闭的环境,即镜头仍然保持在原处。最典型的例子是《街头霸王》系列等格斗游戏,但在某种程度上,《鬼泣》和《战神》等游戏也是如此。这些游戏都有不受玩家直接控制的摄像头,游戏玩法往往会从一个封闭的环境转移到另一个封闭的环境,在每个环境中花费大量的游戏时间(箱庭地图)。这使得游戏开发者可以在每个房间或封闭的环境中投入大量资源(处理、存储和美术时间),从而产生非常高的图像保真度。
其他游戏拥有非常大的世界,玩家可以在其中自由移动。对于《侠盗猎车手》系列等“沙盒游戏”和《魔兽世界》等在线角色扮演游戏来说,情况尤其如此。这类游戏给图像开发者带来了巨大的挑战,因为当玩家在每一帧中都能看到很大范围的世界时,资源分配是非常困难的。 更复杂的是,玩家可以自由地前往之前遥远的世界并近距离观察它。这类游戏通常会改变一天中的时间,这使得灯光的预计算变得困难。
大多数游戏,如第一人称射击游戏,都处于这两个极端之间。玩家可以在每一帧中看到相当数量的风景,但在游戏世界中的移动受到一定限制。许多游戏还为每个游戏关卡设置了固定的时间,以便于灯光预计算。
前景物体的渲染数量也因游戏类型而异。即时战略游戏如《命令与征服》系列通常会在屏幕上出现几十个甚至上百个单位。其他类型的游戏的可见角色数量有限,而战斗游戏则相反,只有两个可见角色,每个角色的渲染细节都非常高。必须区分在任何时候可见的角色数量(这会影响处理时间的预算)和可能在短时间内可见的唯一角色数量(这会影响存储预算)。
游戏类型也决定了用户对图像的期望。例如,第一人称射击游戏在历史上具有非常高的图像保真度,这一期望推动了该类型新游戏的图像设计。另一方面,益智游戏通常具有相对简单的图像,所以大多数游戏开发者不会投入大量编程或美术资源为这类游戏开发逼真的图像。
虽然大多数游戏的目标都是逼真的外观,但也有一些游戏会尝试更风格化的渲染。一个有趣的例子是 Okami,如图 22.3 所示。
开发资源的管理也因游戏类型而异。大多数游戏都有一个 1 - 2 年的封闭开发周期,并在游戏发行后结束。最近游戏中出现了可下载内容(DLC),玩家可以在游戏发行后购买这些内容,所以需要为此预留一些开发资源。持久世界的在线游戏(长期运营的游戏?)有一个永无止境的开发过程,新内容不断产生,至少只要游戏在经济上可行(这可能是几十年的时间)。
创造性地利用特定游戏的特定要求和限制是熟练的游戏图像程序员的标志。游戏《小小大星球》就是一个很好的例子,它拥有一个“2.5 维”的游戏世界,包含少量的二维层面,以及一个非交互式背景。这款游戏的图像质量非常出色,因为它使用了专门针对这种环境的特殊渲染技术。
游戏制作过程始于基本的游戏设计或概念。在某些情况下(如续作),基本玩法和视觉设计是清晰的,只需要进行渐进式的改变。就新游戏类型而言,需要大量原型来确定玩法和设计。大多数情况下都处于中间位置,即存在一些新的游戏玩法元素,并且视觉设计具有一定的开放性。在这一步之后可能会进入greenlight阶段,即向游戏发行商展示一些早期演示或概念,以获得游戏的批准(和资金!)。
下一步通常是预制作。当其他团队致力于完成上一款游戏时,一个小核心团队致力于对游戏引擎和制作工具链进行必要的更改,并制定任何新游戏玩法元素的粗略细节。这个核心团队在严格的期限内工作。在现有游戏发行后,团队的其他成员结束了应得的假期回来,整个工具链和引擎必须为他们做好准备。 如果核心团队错过了这个截止日期,那么数十名开发人员可能会无所事事——这是一个非常昂贵的浪费!
下一步是完整的制作,整个团队创造美术资产,设计关卡,调整游戏玩法,并执行游戏引擎的进一步改变。在理想情况下,在此过程中所做的一切都将用于最终的游戏中,但实际上游戏开发具有迭代性质,这将导致某些工作被丢弃并重做。我们的目标是通过仔细的计划和原型来避免这种情况。
当游戏功能完成时,最后阶段就开始了。术语 alpha 发行版通常指的是开始大量内部测试的版本,beta 发行版指的是开始大量外部测试的版本,gold 发行版指的是提交给主机制造商的最终版本,但不同公司对这些术语的定义略有不同。 无论如何,测试或质量保证 QA 都是这一阶段的重要组成部分,它涉及游戏开发工作室、发行商、主机制造商以及外部 QA 承包商的测试人员。这些不同的测试会产生漏洞报告,这些报告会被提交给游戏开发者,直到下一个版本发布。
在游戏发布后,大多数开发者会休假一段时间,但一小部分团队可能不得不留下来开发补丁或可下载内容。与此同时,一个小型核心团队正在为下一款游戏进行前期制作。
美术资产创造是游戏制作的一个方面,与图像开发特别相关,所以我将详细讨论它。
虽然美术资产创造的具体过程因游戏而异,但我在此给出的概述具有相当的代表性。在过去,一个美术师会从头到尾创造一个完整的资产,但现在这个过程更加专业化,需要不同技能的人在不同的时间处理每个资产。其中一些阶段具有明确的依赖关系(例如,角色在被操纵(绑定骨骼,rigging)之前不能被动画化,在建模之前不能被操纵)。 大多数游戏开发者都有明确的审批流程,即美术总监或首席美术师在每个阶段签字,然后将资产发送到下一个阶段。理想情况下,资产在每个阶段只进行一次,但在实践中,可能需要重新提交更改。
一般来说,美术资产的创建过程是从物体几何建模开始的。这一步是在一个通用的建模包,如 Maya, MAX 或 Softimage 中执行的。建模的几何体将直接传递给游戏引擎,所以在保持良好轮廓的同时最小化顶点数是很重要的。角色网格也必须特别设计,以便适应动画。
在这个阶段,通常创建纹理的二维表面参数化。重要的是,这种参数化是高度连续的,因为不连续性需要顶点复制(不连续的话,需要多个位置相同,但 uv 不同的顶点),并可能导致过滤伪影。图 22.5 显示了一个网格及其相关纹理参数化的例子。
在过去,纹理是一个绘制颜色的简单过程,通常在 Photoshop 中。现在,专门的细节建模包,如 ZBrush 或 Mudbox 通常用于雕刻精细的表面细节。
如果要用实际的几何图形来表示这个额外的细节,将需要数百万个三角形。 相反,细节通常被“烘烤”到法线贴图中,法线贴图应用到原始的粗网格上。
除了法线贴图,还创建了包含表面属性的多个纹理,如漫反射颜色、高光颜色和平滑度(高光)。这些要么是在细节建模应用程序中直接绘制在表面上,要么是在二维应用程序中。所有这些纹理贴图都使用在初始建模阶段定义的表面参数化(uv)。 当在二维绘图应用程序中绘制纹理时,艺术家必须频繁地在绘图应用程序和其他一些可以显示应用了纹理的物体的三维渲染的应用程序之间切换。
着色器通常应用于用于初始建模的相同应用程序中。在这个过程中,一个着色器被应用到网格上。从细节建模阶段产生的各种纹理被应用为这个着色器的输入,使用初始建模期间定义的表面参数化(uv)。通过视觉实验(“调整”)设置各种其他着色器输入。
在背景场景的情况下,灯光艺术家通常会在建模,纹理和阴影完成后开始他们的工作。在预处理步骤中放置光源并计算其效果。这个过程的结果存储在光图 LightMaps 中,供渲染引擎稍后使用。
角色网格经历了与动画相关的几个额外步骤。用于动画游戏角色的主要方法是蒙皮(Skinning)。这需要一个 Rig 绑定,由连接到角色的转换节点的层次结构组成,这个过程被称为 Rigging。每个变换节点的效果被绘制到网格顶点的一个局部区域上(蒙皮权重)。最后,动画师创建移动、旋转和缩放这些转换节点的动画,“牵引”它们所控制的网格。
一个典型的游戏角色会有许多动画,对应于不同的运动模式(行走,奔跑,转身)以及不同的动作(如攻击)。在主角上,动画的数量可以达到数百个。不同动画之间的过渡也需要定义。
对于面部动画,有时采用另一种称为变形目标(Morph Targets)的技术。在这种技术中,直接操纵网格顶点来变形网格。变形网格的不同副本被存储(例如,不同的面部表情),并在运行时由游戏引擎组合(在几个网格帧之间变形)。