All Houdini Unreal Engine 4

Houdini 支持 unreal world composition 的地形的多样输入

 
 

Houdini 基础工作流

 
 

生成基础地形,单独做一个hda

 
 

 
 

然后bake成地形完成这一步骤。

 
 

再做一个hda专门作为mask的修改的实现,其中输入为地形,输出也为地形。

 
 

结果同样可以bake成地形,然后在结果的基础上,我们可以实现再修改,比如我们在结果的基础上通过unreal自带的工具修饰地形如下:

 
 

再次运行mask工具即可得到新的mask标记:

 
 

 
 

纪录:

本地改用tcp生成成功,改用其他电脑失败

需要重新尝试。

 
 

 
 

 
 

https://zhuanlan.zhihu.com/p/68628338

 
 

先定制一张地形图:

 
 

houdini节点:

 
 

传入houdini再导出:

 
 

结果验证得到的是:

1、layername或者layerinfoname中带有_custom的话,不传入houdini的层。

2、layername中带有_remain的话,不会在输入houdini engine的时候被清除

 
 

核心修改:

 
 

 
 

CheckStateChangedUpdateInputLandscape 在每一次重新cook之前,都会先保存备份当前的landscape,在计算完毕后恢复。

 
 

 
 

 
 

 
 

https://zhuanlan.zhihu.com/p/68927318

 
 

 
 

首先分析一下效能部怎么实现切块的,分为以下几步

  1. 首先是生成整体的高度图【houdini导出的结果】

    得到

     
     

  2. 然后是切成各个块的高度图【图像处理】

    得到

     
     

  3. 然后在unreal里面实现高度图的导入,生成子关卡【Unreal实现】

    得到

 
 

 
 

 
 

因此猜测做法是:得到一张高度图,然后做图像的切割,然后直接输出子关卡

首先在PS里面制作子关卡的高度图,注意导出的时候需要一个一个裁切出来,首先slice成四个,然后一个个crop:

导出的格式如下(export as):

 
 

导入结果:可以完美对应上,但是坐标位置需要非常精确,不然就得手动对位,对不齐就如下会有缝隙。

 
 

 
 

 
 

 
 

然后才发现的Unreal关卡的正确打开方式:

  1. 创建一张总的地图,然后保存地图在一个空的目录位置,再在world setting里面勾上开启world composition
  2. 创建子关卡,并在每个子关卡里面创建一张地图
  3. 调整每个关卡的地图在大世界中的位置:

 
 

 
 

还有一种方法是:

 
 

 
 

我们需要程序化的就是上面这个步骤!!!

这样的实现是没有接缝的,但是LOD的时候还是会有接缝。

 
 

 
 

这里还需要解决的一个问题是,原来的这些高度数据不再是文件获取,而是已经存在在landscape component里面了:

Bake Landscape 的时候实际干的事情就是重新将这个actor脱离原来的父类。

 
 

 
 

 
 

实现第一步:

  1. 手动导出高度图
  2. 切割

    貌似可以用这个工具 https://imagemagick.org/script/download.php#windows https://www.serveracademy.com/tutorials/convert-height-map-to-tiled-landscape-for-unreal-engine-4-with-powershell/

  3. 导入高度图(界面自动化)

 
 

第二步

考虑houdini导出不会有缝隙的地形,然后直接创建landscape streaming

  1. houdini内的实现

     
     

     
     

    这个节点的设置都很重要,参考上图,非常要注意的就是圈出来的那行,一定要有overlap,没有的话就会出现缝隙。

    得到的结果就是四张已经标准命名的高度图:

     
     

  2. unreal内的实现

 
 

上面标准命名的图片直接可以使用下面的工具导入,得到正确的结果:

 
 

因此我们实现起来也就比较简单,只要将上述功能单独做成bake的按钮即可。
 

实现流程如下:

FHoudiniEngineBakeUtils::BakeLandscapeTiled 作为入口,改造如下,原来的做法是直接将landscapeDetachFromActor 到根位置即可,现在去调用下一级的bake实现:

 
 

下一级的实现如下,主要是去调用WorldTileCollision里面的实现:

 
 

核心实现在 FWorldTileCollectionModel::BakeTiledLandscape_Executed 函数里面,基本过程是首先调出选项窗口,获取用户设置,然后填充BakeSetting,填充的内容来自于用户设置和Inlandscape两个方面,后续则利用这个来构建levels。

 
 

这边还涉及到STiledLandcapeBakeDlg的实现,这个就是参考STiledLandcapeImportDlg改造得到。

 
 

 
 

同样上面的方法存在的缺陷是,原来地图里面的layer mask信息得不到保留,因为这些信息没有保存成图片传出。

 
 

我们再来分析一下数据:

LandscapeComponents, CollisionComponentsWeightmapUsagemap三个都是有数据的,16张。

我们需要将这个三部分的数据分别放到四张新建的新的map里面,注意,漏掉任何一个数据都不行,必须清清楚楚的全部划分到新的map里面。

 
 

实现:

首先复制一份 ImportTiledLandscape_Executed 函数,命名为新的 BakeTiledLandscape_Executed 函数来实现这个功能,使用这个函数的方法就是和Import一样。

这个函数里面首先要实现的是地形的划分,我们默认采用的是等分方式来切割地形,PerTile 这个变量的意思就是每边要切成几份。

实现如下:

这边还要注意:

  1. 函数的输入,ALandscapeProxy 就是houdini生成的地形。
  2. Savelevel,只要这个level存在的component以来没有放到这个level里面,就会无法保存,这边涉及的数据就是LandscapeComponents, CollisionComponentsWeightmapUsagemap,实现则是在MoveToLevel函数里面:

 
 

切完了以后,我们就将切分的块的数据传入到MoveToLevel函数里面,这个函数做的事情就是将原来level里面的数据全面的移动到新的level里面。

 
 

至此就可以实现直接切Houdini插件在Unreal生成的地形,并且保留所有的层次信息的功能。

 
 

 
 

  
 

https://zhuanlan.zhihu.com/p/69882278

 
 

这边先记录一下自己对代码的理解,顺便穿插原作者的实现。

 
 

HDA的Unreal Asset进入场景,就会执行一个核心函数: TickHoudiniComponent

在这个函数里面,控制的就是各个状态下应该执行什么函数:

其中比较关键的就是完成Cook以后,则会有对Houdini生成数据的处理,比如这里面根据Houdini生成数据来创建Landscape,就是在FinishedCooking下方PostCook函数下,调用CreateObjectGeoPartResources里面的CreateAllLandscapes实现。

 
 

FHoudiniLandscapeUtils::CreateAllLandscapes 里面就是实现了对于每一个Landscape,这里都对其创建一个对象,因此我们首先要做的,就是让生成结果替代原来的landscape,不再是生成新的。

 
 

核心实现,就是对于每一份高度数据,生成Landscape。

 
 

里面的一些具体细节:

 
 

获取数据信息,从HoudiniEngine的接口函数那里吐回来的。

计算吐回来的地形的大小。

最终创建landscape的地方,注意这里还存在else,里面实现了更新而不是创建landscape的方法。

 
 

但是默认情况下都不会走到这个Else里面,这边的控制参数是UpdatingInputLandscape,有个判断段落专门在处理这个参数:

我们来看看为什么默认情况下是不符合条件的,那是因为 InputLandscapeToUpdate 这个参数是空的。

事实上,整个CreateAllLandscapes函数的参数,除了第一个Houdini传入的值,其他的参数都是空的。

 
 

上面讲的是PostCook里面的内容,用于根据Houdini计算结果生成Landscape;下面是PreCook里面,向Houdini传参数的实现。

 
 

入口在UHoudiniAssetInput::TickWorldOutlinerInputs函数里面,其调用了如下函数:

其中的Landscape分支底下的HapiCreateInputNodeForLandscape函数里面,就有导出高度信息等的实现:

HapiCreateInputNodeForLandscape 函数里面实现的内容:

  1. 得到 SelectedComponents
  2. EXPORT TO HEIGHTFIELD
  3. EXPORT TO MESH / POINTS

 
 

 
 

 
 

 
 

默认情况下其实也存在将输出数据会输到原来地形的选项如下:

这个功能单独打开子关卡,子关卡里面也能用。

 
 

更新LandscapeComponent的情况:

不管上面的勾不勾都没影响,下面的勾上以后效果如下

原来:

生成:(位置不对,且一定是生成新的)

 
 

问题结果:

 
 

 
 

上面的问题在于,我们使用:

LandscapeEdit.SetHeightData(TempMinX, TempMinY, TempMaxX, TempMaxY, IntHeightData.GetData(), 0, true);

这句话的时候,我们是对LandscapeEdit来赋值,其区域是我们设置的minmax,但是输入参数IntHeightData表示的是从这个数组去取值,这里每次都是去取这个数组的开头的数据,因此是有问题的。

 
 

修改:

效果:

 
 

总结一下到此为止的做法的思路:

CreateHeightfieldFromLandscapeMultiComponents 函数里面,以 CreateHeightfieldFromLandscape 为参考,计算的是完整的地形数据,只是增加需要传出的数据如下:

ComponentRegions 会作为参数传入houdini,再返回给创建的数据部分:

CreateAllLandscapes里面获得参数:

最后就是使用这个额外的参数,来对这些区域做修改,而不是全局地图的数据都做修改,代码如前面所贴。

 
 

 
 

 
 

然后我们来组合参数,看看是否达到我们想要的效果:

默认地图:

  1. 全局生成新的Landscape

 
 

  1. Houdini修改的数据应用到旧的Landscape上,不生成新的。

 
 

  1. 利用Unreal选择工具选择想要重新计算的landscape Component,对于每一个Component区域,生成新的Landscape。

【这边输出数据只存在在第一个landscape,其实就是只能生成一个,这个BUG暂时还没解决】
 

  1. 利用Unreal选择工具选择想要重新计算的landscape Component,对于每一个Component区域,Houdini更新的数据应用到原来的地形对象上面。

【整个这部分还有个BUG是单独处理LandscapeStreamingProxy的时候,不能局部生成,这个BUG也是待处理。 】

这个BUG处理很关键,我们先来研究下怎么搞。

 
 

官方留了参数的处理,但是明显这里不起作用,因为就没有这个命名的参数传进来:

我们稍加改造,传入传出这个标记位即可实现对当前对象是StreamingProxy还是Proxy的区别:

实现:

CreateHeightfieldFromLandscape函数里面写入数据:

CreateAllLandscapes函数里面读出数据:

这样的结果就是,如果你输入的是LandscapeStreamingProxy,那么输出就同样是这个类型的地形,如果输入的是LandscapeProxy,那么输出就也是一样的。

 
 

但是这边有个问题就是,为什么要额外传入这个标记位,因为进入CreateAllLandscapes函数的参数InputLandscapeToUpdate的获得,只能是landscape,这边存在信息丢失导致的。这边的信息的获得,由

这个函数实现,这个函数的输出是Landscape对象,而不是LandscapeProxy对象,这就导致了信息丢失。

新的实现:

 
 

然后即可实现正确位置的SteamingProxy生成或者替换,但是存在的新的问题就是高度数据的拉伸。

这边也是需要处理高度拉伸的代码来避免这个问题:

但是还会有一个位置偏移。

可以确认的是,这个偏移和StreamingProxy的Location.Z值的有关,如果我们生成的StreamingProxy的Location.Z归0,则不会出现这个问题。

但是如果不归0,我们需要根据StreamingProxy的Z轴位移重新reset一下float值,然后再生成地表即可。

实现代码:

效果:

 
 

全面支持了LandscapeStreamingProxy的数据生成和替换。

  1. 生成新的LandscapeStreamingProxy

 
 

  1. 替换原来已有的LandscapeStreamingProxy

 
 

 
 

 
 

 
 

 
 

https://zhuanlan.zhihu.com/p/70318457

 
 

选区更新

这个部分按照作者的做法就是,将这个选区的数据当做是一张Mask传入Houdini运算,然后即得到结果回传给Unreal。

 
 

我觉得更好的做法就是结合Unreal4.23的新功能non-destructive Landscape editing,让Houdini生成的地形单独作为一层,然后可以使用mask工具在Unreal里面做混合。

但是在我们没有采用多层高度图之前,我们先来实现一下对这个mask的应用。

 
 

思路:

Houdini输出的地形,层次信息,全部都通过这层mask信息,来和原始地形做混合。

也就是 最终地形 = 新信息 * mask + 旧信息

 
 

高度处理的核心代码:

 
 

Layer处理的核心代码:

 
 

效果展示: