[UE4]以选择区域的方式对地形做生成和更新

  前一节里,解决了Houdini地形无缝导入到UE4的流程问题。但这种方法也有它的局限性,在实际游戏项目里,LA和LD还是偏向在游戏引擎编辑器里工作,他们的一些设计也会影响到地形的信息,那么就需要Houdini对已经导入UE4中并Bake成Landscape的地形资源做二次修改。通常会选择两种方案:

  • 方案一:把整个地形和建筑都导回到Houdini里,重新过程化和调整生成后,再全部导入回UE4做处理。
  • 方案二:使用HDA节点的Input和Output,通过调用Houdini Engine API,直接在UE4里完成调用Houdini过程化节点对地形做修改。

    这里方案一不但要求美术和策划对Houdini有一定了解,而且因为Houdini里和引擎的渲染效果不一致。可能还需要导入到UE4里才能看到最终效果,大地形还要做WorldCompositon和LandscapeStreamingProxy的生成。除了地形以外的的场景和建筑部分,还会和GamePlay以及优化显示逻辑相关,通常会包装成BP或Prefab的形式,这些东西要导入Houdini再导回也不仅仅是资源处理的工作。这么看方法一是非常费时费力的方法。这也导致了国内一部分项目虽然是把Houdini的地形引入到制作管线里,但也仅仅是作为WordMachined的替代品,并不能完全发挥Houdini的全部功能。

    所以,我们的目标还是方案二的开发方式,除了第一次在Houdini里做完初始地形导入到UE4里生成WorldCompositon后,就不再需要重新导回到Houdini,而是在UE4里调用预先封装好过程化功能的HDA节点来完成功能。这也是近年来Ubisoft在Tom Clancy’s Ghost Recon: Wildlands和Far Cry 5里使用Houdini的方案。具体案例在GDCVault上有GDC2017和GDC2018的相关视频,这里的目标也是要UE4里用类似他们的方法来实现功能。

 

[UE4]以选择区域的方式对地形做生成和更新_第1张图片

图:Far Cry 5的地形编辑工具示例。可以直接在编辑器里调用Houdini功能对地形做修改。

 

    但是使用原生的UE4 Houdini Engine的前提下,无缝大地型的UE4内部修改还是会有以下几个问题:

  • 虽然Houdini Engine支持Landscape的读回到Houdini,但他Output只支持使用回读的Landacape Data信息创建一个新的Landscape:
    • 每次修改,都要重新跑一遍上节提到的生成WorldCompositon和LandscapeStreamingProxy的过程,重新对地形对切割,还是非常耽误时间
    • 虽然提供了基于Landscape Component的Input,但Output时每个Component会Cook成一个landscape,导致生成多个Component。
  • 在原生配置下,即便只处理一部分地形的迭代也必须把整个Landsacape Data通过HDA Input到Houdini来进行处理:
    • 假如使用Houdini里读入8x8k的地形,内存占用和过程化处理和交换数据量都会变大,从而导致Cook时间变长,降低迭代的速度。
    • 不能基于Componment的控制生成范围,那就需要给HDA额外加入一个选择区域的Input,导致美术工作上变的更加繁琐。
  • 很难在UE里做资源的版本管理,也不方便多人合作地形

 

    对前面提到的一些概念和方法不了解也没关系,我接下来会用示例还原这些过程,直到最终的目标方案的原型,也就是Far Cry 5的功能效果。

[UE4]以选择区域的方式对地形做生成和更新_第2张图片

图 Far Cry 5 地形编辑器,可以选择一个Terrain的Section或Sector区域来做过程化生成,极大的减小了处理和引擎与Houdini交换的资源量。

    时间和篇幅的缘故,本节会分为上下篇,上半部分主要是在如何修改Houdini Engine源码,可以在UE4里基于Landscape Component进行小规模的迭代迭代的功能,下篇则是实现Houdini Engine管线的基础上,如何制作HDA节点,实现各种不同的编辑效果。

使用Houdini制作闭环

    本节继续使用上一节的场景资源做作为测试用例,讲解一些HDA Input基础知识,让没有Houdini开发经验的程序人员也能很快接手Houdini Engine的改造。首先创建一个用来开发调试用的HDA节点,它的功能就是可以选中UE4场景里的一个Landscape作为Input,通过Houdini Engine把UE4 Landscape Height Data和Layer Data 转换为Heightfiled Height和Mask Data传入到HDA,在HDA里不做任何处理直接输出原始的Height和Mask,再次经过Houdini Engine生成UE4的Landscape Data。

下图就是一个Houdini闭环处理地形的流程展示,不需要打开Houdini,在UE4里就能完成闭环的操作。

[UE4]以选择区域的方式对地形做生成和更新_第3张图片

 如何创建一个SOP(Surface OPerators or geometry nodes )类型的Houdini Node Input的流程,Houdini Engine的官方文档里也有讲解 https://www.sidefx.com/docs/unreal/_inputs.html 

方法一,Houdini里File->New Asset,按下图的创建一个新的Operator

[UE4]以选择区域的方式对地形做生成和更新_第4张图片

方法二 创建一个Gemotry节点,Create Digital Asset,在HDA的Parameter里添加一个Operate Path的参数,把这个参数的与Object_Merge的Object做关联。

[UE4]以选择区域的方式对地形做生成和更新_第5张图片

 

    不论用这两种哪个方法制作都可以得到这个HDA文件,把它加入到UE资源并拖入到Level的话,按下图那样把Input类型选择为Landscape Input,就可以选择要处理LandscapeStreamingProxy。而勾选上最下面的“Export Selected Landscape Components Only”,就可以把Landscape Component作为Input输入给Houdini。但就像一开始提到的,原生Houdini Engine的这个功能并不能满足我们的需求。

[UE4]以选择区域的方式对地形做生成和更新_第6张图片

 

基于component的更新的问题

如下图所示,原生UE4的Landscape的是支持多选Component Selection的,Houdini Engine也是支持多个Landscape Component 的Input。

[UE4]以选择区域的方式对地形做生成和更新_第7张图片

选择4x4个Component作为要处理的Landscape信息,然后用Recommit看下结果.

[UE4]以选择区域的方式对地形做生成和更新_第8张图片

 

    如下图所示,虽然在效果面上,Houdini Enine把读入的LandScape Data转成HeightField Data 输入给Houdini又没有丝毫误差的的Output后转为LandScape Data,但Houdini Engine把这16个Component创建成了16个Landscape,这明显不是想要的结果。

[UE4]以选择区域的方式对地形做生成和更新_第9张图片

 

    另外要注意的是,新创建的Landscape的Transform和老的Landscape的Transform也不一样,这是Houdini和UE4的高度信息单位不同导致,这个问题也会在后面修改Houdini Engine时造成一定的困扰。而且原生的Component多选功能在Height和Mask更新上也会有一些问题。另外预先提到的一点,虽然Houdini Engine的Landscape的更新上有以上各种问题,但类似读取Landscape的信息来动态摆放,生成植物生态系统等不会修改LandscapeData的功能并不会受影响,这个具体的HDA开发也会在下篇里涉及到。

[UE4]以选择区域的方式对地形做生成和更新_第10张图片

图:类似FarCry5根据选择地块的Mask信息生成植被的管线,原生的Houdini Engine也是可以胜任的。只需要一些Houdini HDA的功能开发就可以了。

 

定制Houdini Engine支持基于Component的生成和更新

因为时间和篇幅关系,这里提供一个不需要修改UE4源码,只少量修改Houdini Engine就可以解决问题的方法,先进入到UE4的Houdini Engine Plugin工程代码里。

[UE4]以选择区域的方式对地形做生成和更新_第11张图片

 

和Landscape相关的功能,是在HoudiniLandscapeUtils和HoudiniEngineUtils里,建议有时间还是全看一遍,这里用注释简单描述下整个数据流程方便定位问题。

Input部分

调用FHoudiniLandscapeUtils::CreateHeightfieldFromLandscapeComponentArray函数,把选择的Landscape Component的Height Data信息转为Houdini的HeightField Data。

 1. Extracting the height data

 2. Convert the height uint16 data to float

 3. Set the HeightfieldData in Houdini

 4. Extract and convert all the layers

    // 1. Extract the uint8 values from the layer

    // 2. Convert unreal uint8 to float

    // 3. Set the heighfield data in Houdini


Output部分:

当数据在Houdini里处理完成后,调用FHoudiniLandscapeUtils::CreateAllLandscapes基于Houdini的volume数据生成Landscape。

    // First, we need to extract proper height data from FoundVolumes

    // Check that all layers/mask have not changed too

    // Extract the Float Data from the Heightfield

    // Convert the height data from Houdini's heightfield to Unreal's Landscape

  // Look for all the layers/masks corresponding to the current heightfield

  // Extract and convert the Landscape layers

  // Create the actual Landscape

定位到FHoudiniLandscapeUtils::CreateLandscape函数里,它的核心功能把转换后的的uint16的Landsacpe的Height信息(TArray< uint16 >& IntHeightData)和Layer信息(TArray< FLandscapeImportLayerInfo >& ImportLayerInfos),通过调用UE4的ALandscapeProxy::Import来生成一个全新的Landscape。

    这里选择的解决方案是不创建新的Landscape,把HeightData和LayerData做适当的封装,直接使用FLandscapeEditDataInterface的SetHeightData和SetAlphaData输入到需要修改的Landscape的对应Component的数据做更新。

Layer Data的处理方法

    为了讲解简单起见,直接在CreateAllLandscapes函数的后面加上这部分功能。其中的Height Layer的更新相对简单,把之前Import用的TArray< FLandscapeImportLayerInfo > ImportLayerInfos的数据对应的用LandscapeEdit.SetAlphaData传给老的Landscape就可以了。

?

1

2

3

4

5

6

7

8

9

10

11

12

// Set Layer Data

for (int32 LayerIndex = 0; LayerIndex < ImportLayerInfos.Num(); LayerIndex++)

{

    LandscapeEdit.SetAlphaData(ImportLayerInfos[LayerIndex].LayerInfo,

    SelectLandscapeComponent->GetSectionBase().X,

    SelectLandscapeComponent->GetSectionBase().Y,

    SelectLandscapeComponent->GetSectionBase().X +

    SelectLandscapeComponent->ComponentSizeQuads,

    SelectLandscapeComponent->GetSectionBase().Y +

    SelectLandscapeComponent->ComponentSizeQuads,

    (uint8*)ImportLayerInfos[LayerIndex].LayerData.GetData(), 0);

}

 

然后选中一个Component,给HDA配置上Landscape材质,以及一个HeightField  Mask Noise节点,给地形的Groud信息图层信息增加一些噪声 。

[UE4]以选择区域的方式对地形做生成和更新_第12张图片

左边是处理前的效果,右边是增加噪声后的效果。

[UE4]以选择区域的方式对地形做生成和更新_第13张图片

可以看到,这里已经把Houdini处理过的Height Mask信息写回到了UE4原本的Landscape Layer上,实现了目标的效果。

Height Data的处理办法

和处理Layer Data的方法类似,把Houdini Engine Output的(TArray< uint16 >& IntHeightData)用LandscapeEdit.SetHeightData函数传回给Landscape Component。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

ULandscapeInfo* LandscapeInfo =

SelectLandscapeComponent->GetLandscapeProxy()->GetLandscapeInfo();

FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo);

 

int Num = IntHeightData.Num();

for (int i = 0; i < Num; i++)

{

    // Convert Transform

    IntHeightData[i] = (IntHeightData[i] - ZeroValueInDigit)*

    SelectLandscapeScale.Z / OldLandscapeScale.Z + 32768.f;

}

// Set HeightData

LandscapeEdit.SetHeightData(SelectLandscapeComponent->GetSectionBase().X,

    SelectLandscapeComponent->GetSectionBase().Y,

    SelectLandscapeComponent->GetSectionBase().X +

    SelectLandscapeComponent->ComponentSizeQuads ,

    SelectLandscapeComponent->GetSectionBase().Y +

    SelectLandscapeComponent->ComponentSizeQuads ,

     (uint16*)IntHeightData.GetData(), 0, false );

  

    Convert Transform注释部分的处理,是因为经过重新处理后Houdini的Height Field的Data Range和之前的发生了变化,导致ConvertHeightfieldDataToLandscapeData里生成的HeightData和Transform信息和Input时的Transform信息不匹配,ZeroValueInDigit和OldLandscapeScale.Z分别代表了新地形的Transform信息。需要把两个Transform信息的差异对HeigtData做修正,才能把正确的Height Data写回到Landscape。如果你发现写回的地形整体高了一块或低了一块,或者高度比例和原来不一致,那通常就是这个HeightData的还原处理出错了。所以原始的Landscape Transform也要尽量标准,例如本节示例里初始Landscape的Transform就做的尽量正规化

看下修改代码后的效果,选择一个Landscape Component地块,在HDA节点里增加一个Heightfiled Noise的节点,对Landscape Height Data做一些轻微的噪声修改:

[UE4]以选择区域的方式对地形做生成和更新_第14张图片

左侧是未处理的,右侧是处理完的。可以看到地块有了噪声的高低差的效果。

[UE4]以选择区域的方式对地形做生成和更新_第15张图片

 

继续做一个Height Field Erode的测试,用TimeShift来控制Height Field Erode的演算帧数。

[UE4]以选择区域的方式对地形做生成和更新_第16张图片

 

    下图结果是TimeShift = 30 和 TimeShift = 60的效果对比。基本上实现了用Houdini Engine对一个Landsape Component的修改功能。但是问题也很明显。处理的Component和未处理的Component的边缘高度无法很好的衔接。距离推上生产线,还有不少功能需要开发和支持。

[UE4]以选择区域的方式对地形做生成和更新_第17张图片

图:Height Field Erode效果生成,相比整个Landscape的演算,一个Component只要几秒内就能完成效果计算。

总结

Houdini Engine基于Landscape Component的过程化生成,确实可以大幅度的提升生成效率和速度,但是Houdini Engine和HDA制作都还需要一系列的定制开发

  • 对多选Landscape Component的支持,并且解决多个Component之间的接缝问题。
  • Input不能只有Landscape Component来控制范围,还需要为美术提供选区的功能来控制生成范围,避免Component边界问题。

在下篇中,我们会针对这些问题,继续对Houdini Engine进行定制,以及提供针对不同功能的HDA开发的示例。

你可能感兴趣的:([UE4]以选择区域的方式对地形做生成和更新)