1998年3月31日,《星际争霸》在暴雪娱乐(Blizzard Entertainment)的手中诞生了。这款革命性的即时战略游戏拥有3个截然不同的对立种族。精明的人类(terran)、神秘的神族(protoss)和冷酷的虫足(zerg)发现自己被卷入了一系列阴谋之中,为了生存与征服,战火在广阔的星系间点燃了。 发售当年,《星际争霸》的全球销量就超过了150万,成为98年名副其实的PC游戏销售冠军。迄今为止,在过去10年里该游戏已经累积售出950多万份,其中不少都流向了韩国,在那里《星际争霸》成了一种传奇,而在中国它也同样是有着无数fans的超级经典。
正所谓十年磨一剑,在fans们望穿秋水苦苦等待了近十个春秋之后,暴雪终于在2007年5月19日于韩国首尔正式宣布了《星际争霸》续集的开发计划。人、神、虫的故事将在单人战役中继续下去,而多人对抗的游戏性预计会达到一个新的顶峰。星际争霸传奇的第二章就要开始了。《星际争霸Ⅱ》的故事背景将延续《星际争霸 母巢之战》展开,故事发生在“星际Ⅰ”的后200年。人类科技没有多大的进步,而神族科技一直进步到了一定巅峰!虫族已没有什么威胁了,是人类和神族开始的时代了,但是人类连败。最后议会决定使用人类多年禁用的魔鬼力量——新一代核武器。后面是为了展示神族母舰用黑洞,人类用暗黑核武器与黑洞一起抵抗神族,神族被迫撤离人类的区域……
不过我们今天文章的主题并不在《星际争霸2》的故事或游戏系统本身,而在于介绍探讨开发《星际争霸2》的引擎。前不久由于AMD宣布将于暴雪展开合作计划,所以在AMD的官方网站上公开了《星际争霸2》引擎的技术白皮书,这也给给了星际迷们一个了解《星际争霸2》图形画面方面信息的机会。本文将透过这一份技术白皮书来了解探讨星际2引擎技术的独到之处。
为一个拥有10年辉煌历史的游戏开发续作显然需要承受巨大的压力与挑战,特别是暴雪这种精益求精的公司,实际上《星际争霸1》的第一个内部开发版本就因为画风太过于类似魔兽2而被彻底推翻重来,暴雪对作品要求的严格程度由此可见一斑。而如今面对这个十年磨一剑的续作,我们不难猜想暴雪对其重视程度已经达到呕心沥血的程度!为了完成这个艰巨的目标,暴雪为《星际争霸2》的引擎开发小组提出下列要求:
首先最主要的要求就是该引擎必须具备广泛的硬件支持幅度,能够自上而下充分发挥各等级硬件的运算资源。目前PC游戏销量萎靡(相对于游戏机)的主要病根之一就是PC平台不同等级的硬件性能相差甚巨,而大部分PC游戏对不同级别的硬件支持范围又不够广,所以往往要么是配置需求过高,要么就是画质跟不上时代,导致玩家接受面小而严重影响销量。而过去暴雪的游戏之所以能够赢得如此庞大的玩家追捧,除了作品本身的优秀质量以外,相对较广的硬件支持同样功不可没。像《魔兽争霸3》跟《魔兽世界》在刚推出的时候,就能够很好兼顾当时高配置下的优秀画质以及低端配置下同样能够提供流畅的游戏体验而闻名,而时至今日,这两个游戏的画面也仍然具备可圈可点之处。
对于《星际争霸2》,暴雪的目标是既能够在最新的AMD HD 4800/NVIDIA GF G200系列显卡上提供“次世代”画质,同时也能在4、5年前的Radeon 9800/GeForce FX显卡上保证可玩性,跨越如此之大的硬件支持幅度一方面保证了游戏广泛的受众面,另一方面也对一个现代引擎的开发带来不小的挑战!为了满足这一需求,开发小组为星际2的引擎设计了一个非常巧妙的shader系统。
跟其他游戏引擎采用专门的shader创建工具不同,开发小组的目标跟随业界的发展趋势,设计一套灵活易用的shader系统,在星际2的引擎中编写着色程序代码就像是往整个程序里添加C++文件一样简单。形象点讲,shader代码是作为C++的外部函数库而独立于主程序,为游戏添加sheder就像是从C++的外部函数库调用函数一样简单。shader函数库预先定义好若干“入口”,通过这些入口可以直接调用不同的着色程序,而且每个入口都可以自由调用任一部分的shader代码,这样一来就使得shader的概念在星际2中显得相对模糊,也很难具体说明《星际争霸2》将使用多少条独立的shader,但其简单易用的特性让程序员可以很快上手而节省shader的测试、除错所花费的时间。此外,美工也可以在3DMAX中测试他们的新shader,如果效果满意就可以将其优化版本添加到shader代码的函数库里以供日后调用。
总的算来星际2的shader代码库将拥有8000行无重复、各不相同的shader代码,分成大约70个文件。值得一提的是这个规模比早期很多游戏的整个代码函数库还要大。
第二个目标是将运算负担主要集中到GPU身上,一方面是因为当前GPU的图形性能已经远远超过CPU的运算能力,另外RTS类游戏不仅需要CPU在同屏处理成百上千甚至上万个作战单位的AI(在8人网络对战的情况下,每帧需要绘制的作战单元最高可达500个!)而且在多人游戏的时候还需要协调各设备之间的通讯,比较遗憾的是《星际2》的引擎并不支持DX10,模型顶点的生成同样需要消耗大量的CPU资源,所以其它画面运算的负担就必须尽可能分给GPU承担,为CPU腾出足够的资源用于其他运算。
GPU方面,由于RTS需要处理大量作战单元的特性,可能有不少玩家会认为负担主要在顶点着色器方面,的确暴雪声称在出现海量作战单元的场景下顶点吞吐量可以超过百万,不过这个数目对于目前的GPU来说已经是小菜一碟。再加上通常玩家在实际游戏的时候为了统揽全局往往会把视角拉得比较远,这时候已经很难看清作战单位的模型细节,所以高多边形的精细模型并不会为游戏的画质带来多少好处。此外RTS类游戏所采用的视角不会带来多少无效渲染,所以暴雪最终将提高画质的关键锁定在象素着色器身上,在确定这一目标之后,暴雪的美工们将竭尽所能用Pixel Shder绘制出一个光影绚丽的星际战场!
最后一个目标是打造一个引“擎中的多面手”,暴雪要求星际2的引擎必须具备适应不同需求场景的营造力,比如在实际游戏场景里,游戏要营造的场景必须做到气势恢弘,必须烘托出战斗中痛快淋漓杀戮的气氛,这时候就要求引擎具备高batch counts,对细节的表现力则不用过于注重;除了传统的RTS场景外,这次暴雪还将在星际2的单人战役中注入RPG元素,玩家在任务与任务之间能够加入到剧情的互动中来,通过跟NPC对话,选择不同的任务并对剧情的发展产生影响。而为了能够让剧情表现饱满鲜活,增进玩家的带入感,《星际争霸2》特别设计了一个剧情模式,在该模式里的所有动画过场将不再是简单播放一段段预先录制好的暴雪招牌式CG,而是改用即时渲染的方式进行,这就要求星际2的引擎必须同时具备渲染细腻、高品质场景的能力,这种场景通常跟RTS的游戏场景有很大不同,需要渲染大量光源下的物体光照、细腻的物体建模、复杂的表面反射、近距离的人物特写以及各种电影特效,简而言之就是更加接近FPS类游戏的场景特征。
为了达到这一目标,暴雪的引擎工程师们开始筛选合适的算法,力求做到效能质量两不误。接下来我们就来详细介绍一下星际争霸2引擎所采用的先进算法与特效技术。
延迟渲染
暴雪对《星际争霸2》的场景设计目标之一就是在剧情模式下营造一个光源丰富、光照复杂的场景,同时也希望在实际游戏场景中能够呈现出更多的光源互动效果。在他们的上一部RTS作品《魔兽争霸3》中,由于当时的技术所限,场景中动态光源对作战单位的打光效果就有着严格的限制。由于计算不精确,其结果就是物体从一个光源区域进入到另一个光源区域的时候,物体身上的光照会出现闪烁bug,为了解决这一问题,他们唯有大幅度减少动态光源的数量。另一方面,在当时使用传统“顺序渲染”的情况下,大量的动态光源计算会造成draw call数量的激增,性能损失达到不可容忍的地步。为此暴雪还特意举了一个典型的例子:他们在一个场景中尝试为每一个marine单位添加动态光源,这样一来就必须计算每一个光源对周围环境所产生的光影效果,而由此带来的draw call很快就让性能低到无法忍受的地步;而同样的问题也出现在地形渲染上,最终开发小组唯有降低地形的复杂度才能解决问题。
随着GPU的飞速发展,近年来一种新的渲染方式正悄然走红,这就是“延迟渲染”(Deferred Rendering)技术。目前已经有不少游戏采用这种着色技术,像是UBI的《幽灵行动尖峰战士》系列、跳票王STALKER以及目前最流行的UE3引擎都已经采用了这种新生的着色技术。跟传统的“顺序渲染”不同的是,在Deferred Rendering模式下,我们可以把光照计算需要的depth、normal、diffuse、specular等参数先渲染到若干render target(保存在G-Buffer中),然后可以在最后再根据G-Buffer的内容进行打光着色。
那么这样做有哪些好处呢?我们举个例子:假设在一个场景中GPU需要渲染一堵多个光源影响下的复杂砖墙,可是这堵砖墙在最终场景里被另一个物体挡住大半,玩家只能看到砖墙的一小部分,这时如果采用“顺序渲染”的话就必须从头到尾一步步完成所有的shader计算,即一开始对砖墙进行的大部分运算都是在做无用功;相反如果采用Deferred Rendering的话就可以将砖墙被遮挡部分的无效渲染省略掉,大大提高了运算资源的利用率!不过Deferred Rendering也有自己的缺点,首先是G-Buffer需要消耗掉部分显存;其次还会消耗掉部分显存带宽。所以只有当场景中光源数量众多的情况下才有必要使用Deferred Rendering,否则在shading不够复杂的情况下Deferred Rendering带来的显存、带宽开销就显得有些入不敷出了。最后延迟渲染还有另一个更加严重的弊端——不支持透明或半透明物体,为了解决这一问题,开发小组特别采取了一些措施,具体我们留在后文再详细来看这个问题。
暴雪的引擎开发小组通过研究对比发现,在星际2光源数目众多、shading复杂的游戏环境中采用Deferred Rendering可以大大节省运算资源开销。他们发现在这种渲染模式下为画面添加多重特效所带来的性能损失要比forward rendering线性许多,始终处于可以接受范围内,而且不会受到场景中战斗单元数量的影响,这一点对于一款RTS游戏来说尤其重要!此外,程序员还可以提前进行Z轴缓冲测试跟stencil测试来减少无效渲染,其做到大幅提升效率的目的。
尽可能减少draw call是另一个保证RTS游戏流畅运行的关键,所以引擎开发小组决定放弃拆分多个pass渲染到单个render target的做法,取而代之改用单个pass渲染到multiple render targets(多重渲染目标,下文简称MRTs),这样一次draw call就能画到最多四个面上,有效减少了draw call的数量。为什么说最多四个是因为DX9限制最多一次只能绑定四个render target,所以主流显卡也只能做到四个RT,而且长宽还必须一致。虽然DX10已经允许最多8个RT但很遗憾《星际2》的引擎就目前的资料来看并没有提供对DX10的支持,希望日后暴雪能够考虑添加支持DX10。
这样一来在星际争霸2中对所有不透明物体的渲染就必须将下列参数写到对应的若干MRT中:
? 不受本地光源影响的颜色值,比如环境贴图和顺序渲染下的光照颜色值
? ? 深度值
? ? 逐象素光照法线值
? ? 环境光遮蔽值(开启静态环境光遮蔽的情况下);如果开启屏空间环境光遮蔽的话则忽略预计算环境光遮蔽贴图
? ? 无光照漫反射颜色值
? ? 无光照镜面高光颜色值
前面我们谈到在《魔兽争霸3》中,由于当时的技术所限,对于地形的渲染只能尽量简化效果。而如今在星际2的引擎中,制作小组只需一个pass就能够将用于地形渲染的多层效果写到MRT中,大大提高了效率。保存在MRT里的各种渲染参数为开发人员提供了绘制各种绚丽特效的条件,经常需要写到MRT里的常用数据包括:
? 用于各种光照跟特效的深度值(这一步跟CryEngine 2所采用的“逐象素场景深度计算”技术类似,由于《星际争霸2》中出现大量需要深度值参与运算的特效,比如容积雾、动态环境光遮蔽、景深、边缘检测等等~~提前计算出深度值然后供后面的渲染步骤反复使用能够有效减少shader运算的浪费,大大提高了效率。)
? 用于环境光遮蔽运算的法线值
? 用于光照的漫反射与高光值
延迟光照
在《星际争霸2》中,只有本地点光源与聚光灯光源采用延迟光照模式,因为这两种光源均被限制了光照区域,所以能从deferred rendering 中获益。而照射全景的方向光源则仍然沿用forward rendering,因为其光照区域为整个场景,所以不存在光照效果被遮挡的情况,如果采用deferred rendering的话效率反而更低。总而言之,不管是“延后”还是“顺序”,两种算法最终得出的画面效果都是一致的,区别只在于不同场景下的效率高低而已,而星际2引擎也同时支持这两种渲染模式,以供程序员在不同场景中灵活选用以保证最高的硬件利用率!
延迟渲染模式大幅减少了draw call的需求,让需要大量动态光源烘托气氛的《星际争霸2》剧情模式成为了现实。上图场景中包含了将近15个动态光源,其中小到圣诞节装饰用的彩灯,大到电视荧幕上的亮光,都能够产生真实的光影效果,为营造真实的光照环境创造了条件。
透明物体渲染
前面我们已经提过延迟渲染比较严重的缺陷就是不支持透明物体,而且目前也没有什么有效的修正办法,星际2引擎的开发人员发现,就算彻底修正延迟渲染在这方面的缺陷,整个引擎的渲染管线在处理透明物体的时候又会产生新的问题。所以开发人员并不打算这么做,他们发现在整个游戏场景中,只有少数的透明或半透明物体会对画面效果产生比较明显的影响,因为这些物体的透明属性会让背面物体的阴影透过而影响自己身上的打光效果。所以程序员先把场景中受到光照效果明显的透明物体挑选出来,然后再对其进行手工标识,命令引擎对这些物体采用forward rendering的方式进行光照运算,虽然这种处理方式需要多个pass才能完成,增加了部分开销,但这样就巧妙地避开了延迟渲染模式下的缺陷,而不用对引擎做大规模手术改造。而且拆分多个pass还有另一个优点,那就是在渲染阴影贴图的时候不用开辟多个阴影缓冲,不同光源对应的阴影贴图可以依次连续渲染到对应的物体上。
屏空间环境光遮蔽
随着容积阴影与阴影贴图等先进投影技术的相继引入,3D游戏中的光影效果在过去几年里有了翻天覆地的改进。尽管如此,几乎当前所有的游戏在光影处理上都存在一个重大缺陷——那就是缺乏对“间接光照”的表现。比如当年以出色的光影效果而闻名的DOOM 3,虽然其光影效果令玩家们印象深刻,但当时仍有很多人发现了该游戏在处理光照效果上存在一个重大缺陷:游戏中的场景处于光源之内的就“灯火通明”,而光源之外就是“漆黑一片”,明亮区域与黑暗区域之间缺乏过渡,完全没有所谓的“半影区域”,这样整个环境看起来就显得相当生硬突兀。实际上这就是由于DOOM 3缺乏对“间接光照”的计算所造成的,在现实环境中,光束的行程并非只局限于从光源出发然后到接收物体处结束,事实上由于自然界中的大部分物体对于光线都存在不同程度的反射率,所以光束在到达接收物体表面之后又有一部分被反射到周围的物体上,这时光线的接收体已经变成了“环境光源”,对其周围的物体跟场景能够产生光照效果。光线的跳转、转移使得“直接光照”以外的区域也能受到一定程度的光照,从而产生一种“半亮半影”的自然过渡区域,而这种效果正是“间接光照”所要表现的。
事实上不仅仅是DOOM3,后来发布的Riddick、FEAR、SCCT、Quake 4等光影效果出众的游戏同样缺乏对间接光照的支持。只有HL2采用Radiosity Normal Maps技术在一定程度上实现了间接光照,但由于HL2中动态光源太少加上蹩脚的投影技术,最终出来的效果并不出众。而跳票王STALKER也秘密内置了对间接光照的支持,虽然效果比HL2要好许多,但由于性能代价太大以及算法还不够完善等问题而被开发商隐藏起来,玩家只能通过控制台来打开。
Real-Time Ambient Map(实时环境光遮蔽贴图,近来开始走红的实时间接光照实现算法,STALKER在室外场景就采用了该技术来实现环境光照,下文简称RAM),跟之前的做法相比,采用RAM每象素只需保存一个标量环境遮蔽值,该数值可以通过向所有方向发射光线来计算,而且最终得出的数值还可以反复使用。RAM在着色程序中根据带有遮蔽值的象素、相对于表面的光照位置以及光照颜色和表面法线等信息来计算物体表面对光线的接收程度。值得一提的是,这里计算得出的结果只是一个近似值,精确度不高,但是对于欺骗人眼还是没有问题的。
为了提高RAM的效率跟适用范围,Crytek曾对该算法进行了改进,并将其纳入CryEngine 2用于模拟全局光照效果,改进后的算法称为“Screen-Space Ambient Occlusion” (屏空间环境光遮蔽,下文简称SSAO)。SSAO通过采样象素周围的信息,并进行简单的深度值对比来计算物体身上环境光照无法到达的范围,从而可以表现出物体身上在环境光照下产生的轮廓阴影。由于可以CryEngine2“逐象素场景深度计算”技术计算得出的深度值直接参与运算,所以SSAO的效率相比RAM有了显著的提高。
SSAO让Crysis在光棚化渲染下模拟全局光照方面取得了突破性成功,以致于近来许多新游戏(比如跟CRYSIS有莫大渊源的《FarCry 2》和XB360的赛车移植大作《Burnout》)不约而同纷纷仿效!而《星际争霸2》也不例外,暴雪更是坦诚之所以采用SSAO真是收到Crysis成功范例的启发。当然星际2引擎也不是完全照搬Crysis的算法,而是作了一些改进,而且前面提到的保存于G-Buffer中的深度值也有效提高了SSAO的效率,这点跟CryEngine 2 的做法也非常相似。