近些年来,开放世界游戏越来越受玩家喜欢,与传统游戏相比,这些游戏往往可以让玩家体验更丰富的内容。对于开发者来说,内容量的提升会大幅增加研发成本,因此,程序化生成就成为了制作开放世界游戏非常重要的工具。
在此前的GDC演讲中,育碧技术美术Etienne Carrier详细介绍了通过Houdini引擎程序化生成《孤岛惊魂5(Far Cry 5)》大世界用到的管线和制作流程。
以下是GameLook听译的完整内容:
《孤岛惊魂5》游戏内世界展示
Etienne Carrier:
我的名字是Etienne Carrier,在育碧蒙特利尔担任技术美术师已经3年了,我加入育碧的时候,《孤岛惊魂5》项目刚刚开始,这是我参与的第一个3A项目。由于我之前没做过3A游戏,不过我加入育碧担任这个岗位的秘密武器是在程序化研发方面的经验,这方面我已经做了六年。
加入育碧之前我是做独立游戏的,这个过程中我把程序化生成加入到了我的研发管线当中,所以今天很高兴能够与大家分享我们育碧在《孤岛惊魂5》项目上用到的程序化生成技术。
开始之前,这是今天我们要分享的内容列表,首先是做一个快速的介绍并说明我们希望解决的是哪些挑战,随后是这个管线的目标,接着我们了解程序化生成工具,这些工具都是从用户角度看待的。
随后我们会对这个管线进行更多的了解,然后详细介绍Cliff和biome工具,接着会说一些研发过程中做出的改变,最后是总结部分。
1.挑战
在《孤岛惊魂5》的研发过程中,我们遇到的一个挑战是持续变化中的地形,通过动图我们可以看到地形在两年半之内的多次变化,所以我们的挑战是迭代如此频繁的情况下,如何管理这么多的内容。
我们来看地形当中的一小部分,这是我们的关卡美术最初做的一个森林地形,这个植被分布与最开始的地形吻合,但随着地形的迭代,我们会看到植被的位置就会有问题。
因此,我们希望能够在每次迭代的时候,这些植被都能够根据地形合理分布,另外我们还希望确定的是,当游戏世界里有多个玩家的时候,如何让他们看到的游戏世界是持续和连贯性的。
当然,面对这些问题,我们也可以在项目一开始就锁定这些地形的位置,但这很明显是不现实的,因为我们必须不断地迭代,才能达到最终发布的质量要求。
2.目标
考虑到这些挑战之后,我们打造了一个专门的管线。
第一个目标就是打造微观管理工具,用更加自然的游戏内容填充到游戏世界当中。然后,这些内容需要与地形拓扑(terrain topology)一致,比如这是同一个案例,但植被完全按照地形的变化进行分布。
随后,这些管线需要是自动化的,所以我们使用了Houdini和Houdini引擎,以及在build machine上打造的一个Nightly build,后者主要用于每天晚上刷新游戏世界。我们有多个工具处理游戏世界里的不同部分,也就是地图。
产生的所有数据必须是确定的,意味着同样的输入需要导致同样的结果,不管我们对地形如何烘焙,都可以看到中间的地形是不变的,因为我们有Nightly build,所以地图之间的衔接需要是无缝的。
最后一个目标,我们希望这些东西是对玩家友好的,只有Nightly build是无法保证用户获得最新数据的,我们还需要在工作过程中随时改变这些数据,所以我们加入了很多的工具。
3.工具
现在我们快速了解一下能够使用的工具,最初我们只做了解决biome分布的工具,但随着项目的扩大,我们创造了很多的工具。
首先是淡水工具,它用来生成湖泊、河流、瀑布等水体。
还有栅栏和电线工具
在陡峭表面生成悬崖的工具
控制游戏内植被分布的Biome工具
我们还有一个云雾生成工具,可以在2D地图上根据地形生成不同浓度的雾气。
最后,我们还有一个根据地形、树木等因素生成世界的世界地图。
4.用户视角
我比较喜欢通过用户视角来使用这些工具,所以接下来我们会快速介绍一下使用这些工具生成游戏世界带来的感觉是什么。
首先,用户会做terraforming pass,我们的确用了wall machine pass开启项目,但大部分的terraforming都是用已经存在的工具。
然后美术师可以在上面分布淡水,主要是通过使用曲线实现,比如这里我们直接使用淡水工具,这时候就会在地面上生成水体以及水中分布的一些物体。
随后,用户可以生成悬崖,工具基本上是通过地形斜坡实现的,虽然可以微观控制,但大部分时候用户都不需要做任何事,只需要运行工具就可以在这些地形区域生成悬崖。
接下来,关卡美术师可以增加植被,主要是使用biome painter实现,然后biome工具就会在整个地形上自动生成合理分布的森林草木,而且它们还可以与水体资源互动。
这些工具都很不错,但用户还希望对它进行定制化,美术师可以对一片区域进行清理,然后增加一条道路,因为我们做的内容比较自然,所以可以直接加到地图上。
可以看到植被会在路上投下倒影,路上的树木也已经被清理掉。
不过,还有些资源需要手动替换,比如车辆,这些东西都是关卡美术师做的,在此基础上,我们可能要稍微调整一下biome painting,比如增加驾驶道路,在周围增加一些树木等。
刷新biome工具之后,我们可以看看效果:
差不多就是这些。
为了用到我们所有的工具,可以在这里增加栅栏,主要也是通过画曲线的方式实现,篱笆就自动生成了。
用户还可以在地形当中增加电线,这里我们可以增加两种相互连接的电线,所以我们增加了单条线和相连接的电线,如果用户没有对远处的地形设计电线,系统会自动生成电线。
这时候,树木挡住了电线的道路,所以我们需要刷新biome工具,因为这些工具是可以相互影响并交换信息的,一个变化之后,其他可以对应做出改变。
以上就是用户视角的工具。不过为了展示有些无法被打断的工作流程,地形是可以随时改变的,比如降低地形,或者在这里增加一个小岛,一旦我们对这些改变满意,只需要刷新这些工具,这个案例中,我只需要刷新cliff和biome工具就行了。
5.管线
接下来我们看所有工具在Houdini引擎是如何工作的,我们使用了内部引擎DUNIA作为输入,然后通过Houdini输出,基本上是在两个引擎之间交换数据。
你们可能知道Houdini是什么,不过这张图可以简单展示它能够做什么。
有些输入是用Python从dunia到Houdini,比如世界信息、世界名字,我们不止做了一个世界,而是为了测试做了多个世界,并且在其之上运行这些管线。
我们还发送了文件路径,以确保文件在PC上被保存在同一个文件夹内,这些文件路径也发送到了Houdini引擎。我们还发送了地形,以及曲线和形状等等,还包括地形,比如栅栏,不止有栅栏分布情况,还有栅栏的形状与风格。
不过,程序化工具最主要的输入还是地形本身,因为生成是在地形上的特定区域完成的,所以地形是被分为很多小扇区(sector)的,也就是图中的粉色部分(64mx64m),当用户想要使用工具的时候,这是他们可以调整的最小区域。
生成区域的时候,用户可以根据不同情况做出选择,比如整个地图或者某个区域,所有的调整都是可以直接通过摄像头视角看到的。
然后我们看看Houdini可以发回的数据,比如2D地形数据、地形纹理布局,几何体则是程序化生成的,还有地形逻辑区域,后者主要是地形的id编号,所有这些数据都是暂时以缓存形式存在于磁盘,因为避免了数据传输的加载时间,所以这样在Dunia和Houdini之间传输数据比较简单。
有些具体信息存储在Entity Point Cloud,比如某些物体的具体位置信息,植被资源、岩石、可收集物品、decals以及VFX特效数据、预制建筑等,甚至可以是环境中的动物,这些物体都有对应的ID以便精准做出调整。
这些工具的关键是其能够互连,程序化生成是按照顺序的,所以build machine始终会按照这个顺序处理它们,每个工具都可以产生影响下一个的数据,一些工具会写mask,其他工具可以读取之前工具的mask。
比如淡水工具会写一个water mask,随后的biome工具可以读取,以便在分布的时候考虑到水源。
6.Cliff工具
这个工具的目的是在比较大的陡峭表面制作悬崖,并且在地形之上创造细节。
在这个环节,我们首先会谈谈之前孤岛惊魂系列使用过的技术,随后会了解工具输入、分层、几何形状、着色方法、地形数据、腐蚀、植被表面以及向工具外输出数据等内容。
实际上,孤岛惊魂系列之前没有用过这样的工具,在《孤岛惊魂4》以及之前,游戏里的悬崖就只是地形,虽然我之前没有在这些项目工作过,但通过这张图我们可以看出来,以前的项目没有对悬崖进行细节填充,因为手动做这些资源会让研发成本提高。
但在《孤岛惊魂5》项目上,我们要做更大的项目,让悬崖有更深层次的表现,这也是我们为之研发工具的原因。
这是只有地形的悬崖
这是Cliff工具程序化生成之后的悬崖
我们实际上是通过使用Terrain slope作为起始点,并且在特定的坡度阈值之内删掉了所有的表面,如右侧缩减,这就是悬崖输入几何体,这种几何体还给玩家视觉提醒,这里有悬崖,是不能在上面行走的。
由于我们是在这样的几何体上开始,斜坡上就会有拉长的菱形,为了摆脱这种情况,我们对几何体上的通道进行remeshing,以得到统一风格的三角形。
随后,还有一种有趣的现象是我们希望通过这些工具重现的,那就是地理分层。图中的这条黄线是岩石随着时间变化而形成的,为此我们打造了地层工具,每一层都是随机的,我们给每一层都有独特的ID,以便让这些地层做debug的时候更方便。
然后我们还需要控制这些地层的角度,达到我们想要的效果,通常我们是在RGB地形上实现,随后Houdini就会给出角度参数。
选择参数的时候,我们并没有选择无穷多的数据,而是只用了4个,以便让用户选择最适合他们游戏世界的地形形状。
不过,只是这样的话,看起来是不自然的,所以我们做了进一步处理。
首先是做了一些噪点(noise)网格并将它们分为两组,实际上噪点是在比较低分辨率的网格形成的,然后转换到高分辨率网格,通过这些做更大面积的内容,我们发现如果做很很小的岩石或者悬崖,带来的效果不是很好。
通过使用噪点,悬崖表面被分为这样的两组。
然后我们在两个悬崖表面用不同的种子值运行Stratification工具,这样几何体就有了真正的体积。
这里使用了挤压工具和displacement地图
这时候几何体看起来还是不理想,所以我们需要减少三角形的数量。在调出之前,几何体是分布在不同网格之中的。
更精确的说,是每个扇区都有一个网格,如果你还记得,扇区的面积是64×64米,所以这里每一个颜色都代表了不同的扇区,我们这么做是为了更好的加载和流媒体传输,这就是悬崖网格部分。
需要说明的是,悬崖是在游戏里着色的,我们使用了Terrain Shader,它可以用和地形同样的通道渲染物体,所以悬崖可以使用它底下地形同样的纹理。如果我们在悬崖下面有草丛,悬崖也会有一部分是草色,为了避免这种情况我们必须确保悬崖网格转换为它们底下的地形颜色。
出于这个原因,我们使用了最后的工具Cliff mesh,将数据转回地形之上,替换cliff mask和strata attribute。
然后,用刚刚转换的attribute,我们生成颜色布局,可以控制游戏世界的宏观变化。另外需要说的是,这个颜色本来可以做成悬崖颜色,但我们没有这么做,主要有两个原因,其一,我们希望保留远方的内容,即使是因为距离的原因它们还没有被加载;另外,我们希望用于纹理颜色处理程序化生成的内存占用尽可能低,而且因为我们使用的Terrain shader,悬崖网格(cliff mesh)不需要任何uv。
从我们转移到地形上的cliff mask,我们通过运行缓慢模拟的方式对悬崖进行拓展,我们可以看到分散于悬崖表面的点向下滑落以适应腐蚀效果,值得注意的是,原来的颜色依然保持在腐蚀区域。
通过使用我们分散在腐蚀表面的碎石,这些随后会被输出到entities point cloud上。
从mask我们生成了地形纹理id,还用噪点做两个不同的悬崖纹理,实际上还可以做更多,但考虑到我们给地形纹理分配的内存限制,我们只做了2个。
我们还可以在悬崖表面分布植被,我们检测的方式是将面朝上的悬崖单独分离,然后可以在这些悬崖表面分布树木和其他植物,如图所示。
最后,这些是通过Cliff工具传输到编辑器的数据,包括几何体与碰撞,还有岩石与斜坡上生长的树木entities point cloud,有地形纹理ID,还包括地形上的悬崖颜色以及cliff mask。
这是游戏里一些悬崖的截图。
7.Biome工具
这个工具的目的是将生物群系内容遍布游戏世界,这里我们会覆盖大量的功能,主要是因为我们想要制作的内容形式有很多,所以这个工具可以做很多事。
第一步是从Heightmap生成地形,很简单。
然后,从地形拓扑我们生成了Abiotic Data,也就是这片土地的物理特征。
值得注意的是,所有这些地形属性都会成为我们做大多数生物群系的关键。
完成这些之后,地形数据会存储在terrain attributes上,我们还引入其他2D数据,以便让用户通过工具生成的Biome Painter数据与程序化生成的数据一致,比如淡水、路径、栅栏、电线、悬崖等。
Biome工具比较有趣的是,它并不会一次性生成游戏世界里的所有生物,而是分为主要的生物和次要生物进行制作。
主要的生物群系覆盖了《孤岛惊魂5》大部分的世界面积,大约在75%-85%之间。这些生物应该基于地形来决定,比如有些地方是森林,有些地方是草地。
这个工具给了我们外观看起来非常自然的宏观世界。
Main Biome还制作其他东西,比如生成的森林当中留出给用户放置电线的地方,我们可以看到power lines mask被加到了biome工具之上,以取代山区当中的数据和草地。
随后我们做了Sub-Biomes Recipes,它由很多部分组成,每一个都代表了不同的生物,比如森林、草地等,在这些biome的中心是generate terrain entities。
它可以将单位分布在地形上,还可以修改并创造地形属性,但其中核心的部分是对每个物种确定生存能力。
每个物种都为它们的生长和繁荣而努力,生存能力就是为每个物种确定其最喜欢的地形属性。最适应地形生存的物种会(在这个地形上)胜过其他物种。
比如A物种喜欢生存在这种封闭地形之上,B物种喜欢生长在底部FlowMap地形,如果Flowmap值足够高,那么B物种就会胜过A物种。
在选取获胜物种的时候,还会加入一个生存半径, 比如在这个案例中,如果蓝色物种生长到了绿色物种的半径之中,它就会被移除,也就是图中红色的物种。另外,我们也希望树底下有草丛,所以这些不会被移除。
为此,我们设计了优先半径,所以首先会评估优先级,如果两个物种的优先级相同,我们这时候会采取优胜劣汰。比如在这张图中,蓝色树木的优先半径更长,而黄色物种的优先级是0,所以它会生长在离绿色树木较近的地方。不过,我们不希望它离蓝色树木太近,所以只要黄色草丛离蓝色树木距离太近,它们就会被移除。
生存能力在植被分布方面起到了很大的作用,接下来我们看一些自然现象。
左侧图片中,在流动的线上没有任何植被,右侧的图中,山脉的盐碱表面几乎没有任何植被。为了模仿类似的事物,我们需要将不同的地形数据结合起来。
通过融合地形非生物数据,我们可以为植被的分布创造比较特别的节奏,然后我们将两种地形结合起来,还可以将噪点融合到地形数据中。
随后我们还希望加入之前工具生成的exclusion mask,比如淡水、路、悬崖等。
这样我们就得到了一个物种在这个地形的生存分布图。
另外,我们的biome工具还包含了一个物种不同大小的资源,在之前的《孤岛惊魂》系列制作中,这些物种的大小只是随机选择的,与地形以及物种偏好的生存环境并不一致,如今我们的工具可以根据不同情况为这些物种挑选不同的大小。
比如在这个场景中,很多因素可以影响树木的大小,比如小树苗会分布在森林边缘,高大的树木会在比较大片的森林中心。
另外,海拔也会影响树木大小。
虽然我们限制了素材大小,但我们给工具设计了很多的选择,以便实现植被的多元化。比如我们开始是50米的树,随后可以加入40米、30米等等,并确保不同大小的树木分布在适合它们的地形上。
不过,这也会遇到一个问题,那就是不同大小的树木会形成阶梯状分布,为了解决这个问题,我们让每个大小的树木都可以按比例缩小或者增加,以填补不同大小之间的空隙。
如果不希望把多样性搞砸,还可以使用随机分布。
另外,不同大小的同一种树不一定是完全一样的,所以我们的工具还给出了多样化选择,比如这个案例当中每棵树都有自己的多样性。
我们再来看另一个现象,也就是树冠的生态演替,前面提到,小树往往位于森林边缘,但有时候也有些会分布在森林中间,当你走进森林的时候,你会发现一些小树也已经开始生长,并且已经达到一定高度。
生存能力会影响到树木的选择,然而由于我们使用的地形不同,最终带来的多样性并不总是那么流畅,这就会在有些时候让最高的树木被分布在森林边缘。
出于这个原因,我们增加了年龄参数,我们可以看到通过年龄的变化会出现不同的效果。
得益于此,我们还可以用斜坡来改变轮廓形状。
我们需要控制的另一个重要因素是这些树木的密度,如果采取同样的密度,那么小树之间的距离可能看起来比较自然,但对于高大的树木,就会显得很拥挤,而且我们为不同树木做的资源也会导致不一样的性能开销,比如小树占用的GPU比大树低,因此控制不同类型树木的密度也很必要。
基本来说,我们的密度是通过大小进行斜坡式控制的,但也会考虑到年龄等多个方面的影响。
另外,我们还可以通过坡面来调整植被密度。
这就是一个不错的游戏内案例,比如背风坡的树木就比迎风坡稀少。
另外一个要说的是,我们希望游戏里的颜色也有多样化,比如这里的草丛颜色就非常多。为了节约资源创作成本,我们增加了单独的颜色分布功能,比如可以通过生存能力或者时间来控制。
我们可以看到游戏里的效果。
这些植被分布还需要有各自的旋转,默认设置下,他们是按照地形坡度进行旋转的。
比如在游戏里,河边的草始终倒向水面方向,那是因为水岸上总会有斜坡存在。
另外一个案例,就是树干的弯曲,你们可能在大自然中看到过这种现象,如果没有注意到,你们可以专门去观察。不幸的是,我们并没有在游戏里这么做,我很希望我们的系统能够允许这么做。
我们做了其他一些比较酷的事情,比如这里的草丛会随风摇曳。
我们这里根据wind vector map对草丛做了旋转,但还按照地形影响对风向进行了调整,所以只要有风吹来,这些草丛都会按照风的方向旋转。
我们的植被旋转可以基于斜坡角度进行水平旋转,这是通过百分比控制的。
还有旋转抖动,我们对植被使用这些旋转的时候是随机的,除了岸边和固定地点的旋转是不便的,但大多数地方的植被旋转方式都是随机的。
说完了大部分控制植被生成的参数之后,然而biome取决于资源放置,因为有些植被的资源也会影响到地形资源,甚至会影响周围资源,比如两种不同的大树就影响了周边的物体分布。
比如这个树皮皴裂的树干周围有一些枯枝分布,另外,这些树木产生的阴影也会影响其他物种的生长。
为了实现这样的效果,我们在系统中将一些资源数据传回到地形当中,主要用于4种不同的事情,包括地形畸变、地形纹理、地形数据输出和地形颜色。
我们可以通过地形畸变举例,我们可以在这些分布位置生成一个mask,还能够与地形数据相结合,比如使用road mask获得与路相邻的信息。
这里实际上我们把地形提高了1米,对应在游戏里就会出现地形畸变。
我觉得这是一个非常有趣的功能,因为大多数时候,在游戏里树木都是直接长在地上,我们不会填补它所处位置的更多信息,但在大自然中,树根往往会会比地面高一些,他们还可以防止水土流失。
接下来近距离看看这些树根,我们非常希望它们与地形无缝融合,可以通过生成匹配的地形纹理来实现。
再次对分布单位生成mask,我们设置想要对这个mask生成的纹理数量,然后从菜单选择air texture,我们的工具随后会选择哪个纹理应用到哪些地方。
随后,地形id会被发送到编辑器,它就会按照需要与地形混合在一起。
接下来,我们或许还想要给树木添加额外的东西,可以通过树木生成attributes的方式实现,不过,这时候mask会存储于地形上,我们可以设置自己想要的参数和范围。
然后我们来到下一个节点,森林岩石,重新使用生成的mask。
然后就会触发这些资源之间的对应行为。
回到黄松树案例,我们还可以加入年龄数据,这就可以让我们根据树木年龄增加新的元素,如果你是生活在这样的环境中,就可以发现有些自然现象也出现在了游戏里,而不只是被阴影挡住。
我们还可以在树木中间增加小树,以增加生态演替效果。
做植被的时候,我们会不断迭代,增加更多的因素,直到实现这样完整的组合。
《孤岛惊魂5》的一个地图是一平方公里,这样的一个地图当中有60多万个单位分布,都是通过biome工具程序化生成的。实际上,我们在游戏里增加了大概70张这样的地图,以组成一个栩栩如生的完整世界,所以生成的资源量很大。
这是一张谷歌地球图片,比较有趣的现象是地形差异,可以看到干燥区域和湿润区域的绿色是不一样的。
为了让游戏世界更真实,我们还做了地形纹理色调,并且将它与自然的纹理颜色混合,这实现了地形颜色的多样化,中性的草皮是灰绿色的,我们通过与地形颜色的混合来实现光暗效果下的颜色变化。
放大世界一部分,我们可以看到地形颜色与植被混合的效果。
另外值得注意的是,以上提到的地形与植被颜色还可以用到草皮上,因为草丛着色器也可以选择地形颜色。
最后,我们从Houdini将数据传送到编辑器里,包括entities point cloud、地形高度图、地形纹理id等等。
由于时间原因,这部分我决定跳过,不过,这里展示一些研发过程中的改变,因为事情并不总会按照计划进行。
总结
我直接跳到心得部分,首先要说的是,更大的权利需要更多的责任,程序化工具可以生成大量数据,这让我们可以很好的控制性能,但这也会影响整体玩法以及美术。比如他们想在《孤岛惊魂5》做非常浓密的森林,但在实际上它并不是那么有趣,因为树木、动物不可能在这样的森林里生活,因此做生物分布的时候我们必须专门做一套工具进行控制,在提升密度的同时确保不影响玩法。
其次,设计优雅的工具带来更多机会。比如我们的biome就给我们带来了大量机会。
另外,将事情做的简单一些。虽然这已经看起来很复杂,但大多数时候工具都是偏工程化的,一旦确定了整体观,就可以投入一些时间对其进行简化。
聆听用户的意见,有些时候,他们可能更喜欢控制UI,而不是处理过程,比如我们的淡水工具,你可以自动生成河流,也可以让美术师手动设计,但大多数时候可能都用不到手动功能。
保持灵活,因为最初的计划并不总是最好的。
还有,确保在控制与自动化之间有比较好的平衡,过于强调手动控制会增加成本,但太多的自动化会让事情失去控制。
最后,感谢这些人对管线做出的贡献。
····· End ·····
GameLook每日游戏产业报道
全球视野 / 深度有料