Authored by TraceYang
前言
传统的制作做比较真实大世界3D关卡地形时,通常的采用的方式是把HeightMap和SplatMap(Layer Mask)导入到引擎的地形系统里,生成Terrain LandScape的地形信息和地表材质的图层信息。再由美术在引擎编辑器做进一步的细化工作。而这些Map的生成,是使用WorldMachine(简称WM)软件来制作的。

图为Ghost Recon Wildlands前期使用WolrdMachine制作的地图

图 除了HeightMap之外,SplatMap也是要从World Machine中生成的
从WorldMachine中Bake出Map后,导入到UE4引擎里,这个教程wiki上比较多,就不多做描述了。

图 把Map导入到UE生成的Landscape
但是传统的WM机制通常会有以下几个问题
- WM里预览的地形生成效果跟DCC软件或游戏引擎相比并不美观,也不能像其他工具里直接由美术参与修改
- WM导入到引擎需要通过导出Heightmap再导入引擎,当地形比较大时,map生成和写入到硬盘的时间会超过10分钟,对美术迭代修改的成本非常大
- WM生成后到引擎后,关卡设计还需要在地形上进行二次开发,从而影响到地形和地表的Map,这时如果需要WM生成部分地形时,整个流程会变的非常混乱。
像Ubisoft在近年来的大世界项目,Ghost Recon Wildlands和FarCry5如里引入了Houdini Pipeline技术,GDCVault上都有介绍,具体的Heightfield替换WorldMachine的制作,会在地形篇提到,管线章节里就不多做叙述了。这里假设你已经用Houdini的HeightField制作了地形,接下来看下怎么能够快速的通过Houdini Engine快速的导入到游戏编辑器里。
Houdini HeightFiled 到UE4 Landscape的无缝生成
下图是Houdini HieghtFiled到UE4 Landscape的无缝生成的结果示例,不需要导入贴图,只需把HDA文件拖入到编辑器的Viewport,赋予预制作好的Landscape材质,就可以自动把HDA Cook成UE的landscape资源。 这里假定假定读者已经掌握Landscape Material和Landscape Layer的创建方法,就不多做叙述了。


从Houdini Engine的机制图可以看到。我们就是通过HDA文件 Houdini HeightFiled的每一个volume的信息转入到UE4里,这里UE4 Houdini Engine Plugin已经实现了功能帮我们做转换
首先这里要要让HeightField的Size的设置和最终在UE4里Bake的Landscape Sizes一致。下图是UE官方建议的Landscape的配置。

HDA文件导入到场景后,这里可以看到Landscape 的Component的情况。

管线介绍部分不会对Heighfiled的制作做太多介绍,用Houdini生成地形信息,通常是用两种途径:
方法一:使用HeightField File节点加载一张已有的高度图来生成

在HeightField File 节点里可以配置生成到UE4的Landscape的Size

而具体的对应的UE Landscape的Overall Resultion,可以使用Heightfiled Resample节点

而GridSpacing则对应的是他实际在UE4里的Overall Resultion

当Heighfiled的Grid Spacing比较大时,说明同样大小的地形,对应的更少的顶点,换算到UE里,就是比较小的Landscape Overall Resultion。 Overall Resultion越小,UE里Landscape生成后的顶点数也会更少一些。这样适合在移动终端用较少的资源做出相对比较大的场景。

下面我做几组Height Field Size与UE Landscape Componment的对比,他们最终在UE里的Landscape szie都是8129x8129。只是细节有所不同。



业余学习中我们能把Houdini Engine作为一个黑盒,根据需求来做Size和Grid Spacing的组合来达到想要的目标配置,而真正项目工程里,还是建议由引擎程序根据项目来定制Hengine Engine的Cook Landscape的配置。这个我们在后续的定制更新和修改时也会提到。
方法二:使用Heightfiled节点从0开始生成:

创建这个节点时,就可以选择整个地图的尺寸,其中Size就是为导入到UE4后对应的大小,和方法一一样Grid Spacing来决定Overall Resultion。

其他的就跟方法一是一样的了。这样,就可以根据机器性能,在场景用较低的几何体生成出相对较大的场景。
Heightmap的导入解决了,然后就是HeightField Layer Mask与UE4 Landscape Material Layer的对应设置。

上图中HDA里HeightFiled的volume信息,height对应的就是HeightMap信息,这个Houdini Engine已经自动支持了,而其他的Layer Mask与UE的Landscape Material Layer,则可以通过命名来一一对应。

这里我们让HeightField的Layer Name与UE的Landscape Layer Name保持一直。

左侧的Heithfiled Layer名称和右边UE的Landscape移植,就可以通过Houdini Engine,不导出中间的Map资源,这个过程相比WM的10几分钟要快很多,通常HDA的Cook只要10几秒。
具体的制作过程可以看附件的HDA事例,这里只介绍几个Houdini关键节点的使用。


HeightField Copy Layer的功能就是把你在Houdini里生成的Layer Mask重命名为你想在UE引擎里对应的Landscape Layer的名字。
这样,我们可以从HDA文件直接生成出带地形高度和地表图层信息的场景了。

Houdini HeightFiled 对World Composition的支持
无缝大世界游戏场景开发中,一个Landscape不管是在多人协作开发,迭代,还是在Streaming的优化以及打包等,诸多方面都会有很多的不便,所以需要我们把一个大世界的Landscape‘Tile化’。比如一个8x8km的Landscpe可能会切成4x4或8x8的Tile。UE也提供了World Composition的功能。

传统WorldMachine里可以通过Tiled build生成出无缝的Tiled Heightmap和Mask,然后import到UE的Landscape里,这样其实也会增加bake时间,另外把UE4 Landscape的Mask导回给Houdini也是很痛苦的事情(很慢,需要非常大的系统内存)。

好在Houdini 16.5版本后,其实对这部分的支持也加强了,Houdini提供了一个heightfield tilesplit节点就可以做Tile化了,这里我切成了4x4个tile。


再次把HDA加入场景Cook。就可以生成4x4=16个tile的Landscape了。

但是这个用heightfield tilesplit的方法有他的缺陷,一个是会在tile上产生边缘产生接缝露空。

而且这种多个Landscape的方式,和UE4的World Composition机制也有冲突。也不能跨Tile来做地形编辑,其实并不是做无缝大世界地图的正确方法。其实World Composition还是一个整体Landscape,通过把LandscapeStreamingProxy分配到每个子关卡的方式,来进行的Tile分割。
这里提供一个不修改Houdini Engine也能快速的使用World Composition的比较笨的人力办法来解决。
首先,我们不使用splite节点,直接导入一整张地图进来,这样肯定是没有接缝的。

然后类似创建出对应个子关卡,这里假定我们生成2x2的。

接下来,我们选择对应的sublevel,用Move to level工具,就可以把对应的Landscape Component移动到指定的Level里,生成一个LandscapeStreamingProxy


最后给每个Level生成对应的LandscapeStreamingProxy。进行保存。

然后删除掉原始的Landscape,删除之前创建的SubLevel,

勾选Enable World Composition。

UE4就会自动的帮你加载之前的sublevel,生成World Composition。

这样就和UE4默认的Import tiled map生成的World Composition是一样的了。
和前面的地形尺寸对应一样,这里正确的方法其实是要修改Houdini Engine里面的Bake机制,参考Import tiled map里代码的方法,自动的创建关卡和分配Landscape Component,实现整个流程的自动化。否则将来的迭代也会成为问题。
后续问题
虽然这里看似Houdini HeightFiled to UE4 Landscape的流程已经打通,但是在实际项目开发中还是会有以下几个问题
- 目前流程中Heightfiled还是整体的修改和导入,无法支持Landscape Component和Section的更小级别的增量更新
- 还要考虑World Composition的支持,可以自动的从Landscape的某个单元更新到LandscapeStreamingProxy
- 后续的基于地形的植被自动生成,山体自动建模等等的生成功能,也是要支持Landscape的最小单元。
- 对应手游的硬件的考虑,还需要要能生成不同细节的资源等。
- 最重要的,要把这流程做成引擎内的闭环,让开发人员不需要了解Houdini就能UE4里执行和调用功能。
另外Unity虽然没有提及,但相对实现应该跟UE4类似,而且也可以将Heightfile转换为Terrain Mesh来适配移动设备,这里就不多做介绍了。而上面描述的问题,会在今后的分享中有所解答。关于本文的事例资源会在近期提供一个分享专用的git地址。