背景

在上篇中,实现了使用Houdini在UE4里根据地形过程生成植被的最基本的原型。并且支持把植被在UE4里Bake成使用的HierarchicalInstancedStaticMeshComponent的BP形式,一定程度上解决了植被渲染效率的问题。
 
但这种方法在开发效率和运行效率上都还有他的问题:
  • 开发效率方面,这个方案并不支持UE4的Foliage Mode Editor:
    • 每个植被区域都被Bake成BP的形式,场景美术规划阶段就需要格外小心防止区域之间穿插造成植被之间的叠加
    • 当出现比较大的改动需求时,一个BP的范围发生改动就会造成大量BP重新生成的连锁反映。
    • 就像地形生成一样,完全的自动化并不现实,美术需要能通过UE4传统手绘方式来进行修改植被的方式。
    • 一个区域的BP植被需要重新生成时,还要重新绘制一遍之前的生成区域,这个过程除非预先保存,否则很难完全重现上次绘制的区域。
  • 运行效率优化方面,同一类的Instance并不能放到一个InstancedStaticMeshComponent 这样必定会造成一定程度的性能损耗
 
正因为如此,还有必要Houdini的植被管线与UE4的Foliage Mode编辑的植被系统串联起来。这样Hoduini生成后的内容,美术可以很方便的修改,也可以用Houdini来做二次修正,最终生成的植被也可以使用UE4的植被系统的优化方案。而UE4的Foliage System,其实就是每个Level里有一个AInstancedFoliageActor,每种Foliage Type对应的Instanc Mesh实例都保存在AInstancedFoliageActor的FFoliageMeshInfo里。
 
如果要把Houdini过程化生成与Foliage Mode衔接起来,那么就需要Houdini Engine Input和Output部分可以支持UE4的Foliage System。也就是每个Level的AInstancedFoliageActor里,概括来说就是:
  • Houdini Input要增加FoliageType的选项,生成实例的对象不再是用Statice Mesh,而是UE4的Foliage Type
  • Houdini Output直接输出植被实例不再Bake到BP里,而是直接Add到UE4的Foliage System的Foliage Instance里
 
接下来就讲解下如何通过只修改Houdini Engine,不需要触碰UE4引擎源码,来把Houdini植被管线与UE4的植被系统整合到一起的方法。

Houdini Input对FoliageType的选项支持

上篇中也提到过,原生的Houdini Engine的过程化实例放置功能,并没有把植被做特殊的Input处理,而是作为Geometry来对待。首要任务就是在Houdini Engine Input里可以支持Foliage Type。
 
先进入到HoudiniAssetInput.h里,在EHoudiniAssetInputType的Enum里增加FoliageTypeInput。
 
namespace EHoudiniAssetInputType
{
enum Enum
{
GeometryInput = 0,
AssetInput,
CurveInput,
LandscapeInput,
FoliageTypeInput, // Add foliage type input
WorldInput
};
}

 

然后,在UHoudiniAssetInput类的CreateWidgetResources(),ChangeInputType(),CreateWidgetResources(),UploadParameterValue()的函数里,参考EHoudiniAssetInputType中其他的InputType的处理方式,加入对FoliageTypeInput的处理,此外,还要在FHoudiniParameterDetails类的CreateWidgetInput,加入针对FoliageTypeInput的菜单UI,这里可以参考
InParam.ChoiceIndex == EHoudiniAssetInputType::GeometryInput

  

部分的代码给FoliageTypeInput实现一遍,但要自己实现一下Helper_CreateFoliageWidget
 
for ( int32 Ix = 0; Ix < NumInputs; Ix++ )
{
UObject* InputObject = InParam.GetInputObject( Ix );
//Helper_CreateGeometryWidget( InParam, Ix, InputObject, AssetThumbnailPool, VerticalBox );
Helper_CreateFoliageWidget(InParam, Ix, InputObject, AssetThumbnailPool, VerticalBox);
}

  

Helper_CreateFoliageWidget和Helper_CreateGeometryWidget的区别就在与UI里对UObject子类的筛选,把UStaticMesh替换成UFoliageType
 SNew( SAssetDropTarget )
.OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda(
[]( const UObject* InObject ) {
return InObject && InObject->IsA< /*UStaticMesh*/ UFoliageType >();

  

这样,HDA里就增加了FoliageTypeInput的选项,并添加到Input里了。这里不得不说Unity写工具界面比UE4的效率高太多了。
 
虽然通过修改Houdini Engine,把FoliageType的Input读入了,但是Houdini Engine的管线的实例化部分,还是只能对Geometry来进行处理,这里就需要在FHoudiniEngineUtils::HapiCreateInputNodeForObjects函数里,获取FoliageType对应的Static Mesh再输出给Houdini Input Node了。
 
if (UFoliageType_InstancedStaticMesh * InputFoliageType = Cast<UFoliageType_InstancedStaticMesh>(InputObjects[InputIdx]))
{
UStaticMesh* InputStaticMesh = InputFoliageType->GetStaticMesh();
// Creating an Input Node for Static Mesh Data
if (!HapiCreateInputNodeForStaticMesh(InputStaticMesh, MeshAssetNodeId, OutCreatedNodeIds, nullptr, bExportAllLODs, bExportSockets))
{
HOUDINI_LOG_WARNING(TEXT("Error creating input index %d on %d"), InputIdx, ConnectedAssetId);
}
SelectInputFoliageTypeArray.Add(InputFoliageType);
}

  

这样改造Houdini Engine后,输入FoliageType以及Landscape的Draw SelectRegion,就可以和上篇一样输出植被了。
Houdini Input支持Foliage Type后,接下来要实现的就是Houdni Output到Foliage System的功能了。

Houdini Output与Foliage Editor的关联

Output与FoliageEditor关联方面最基础的需求有以下几点。
  • Houdini输出的Entity Point Cloud所对应的Instance可以直接Add到UE4的Foliage System里。
  • 美术可以通过绘制区域来对已经生成部分再次做过程化生成,或者直接利用FoliageEdit手绘的方式来进行迭代调整。
  • 手绘调整部分和Houdini自动化生成部分可以分Layer保存,可以根据情况选择自动生成部分是否影响到手工调整部分。
FC5里也没有提及第三项的实现方式,所以基础管线部分主要讲解前两项的实现方法,而第三条在后续文章里会参考GDC2017上GHOST RECON的地形工具的方法来实现。
 
这里先定位到Houdini Engine生成Output到UE4的类函数UHoudiniAssetComponent::CreateObjectGeoPartResources里
 
#if WITH_EDITOR
if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) )
{
// Create necessary instance inputs.
CreateInstanceInputs( FoundInstancers ); // Create necessary curves.
CreateCurves( FoundCurves ); // Create necessary landscapes
CreateAllLandscapes( FoundVolumes );
}
#endif

  

其中CreateInstanceInputs函数功能会迭代关卡里的每一种Instancer,再通过UHoudiniAssetInstanceInput::CreateInstanceInput(),根据这个Instancer对应的Cloud Point,在UHoudiniAssetComponent::CreateInstanceInputs创建InstancedStaticMesh。
 
for ( const FHoudiniGeoPartObject& GeoPart : Instancers )
{
HoudiniAssetInstanceInput->CreateInstanceInput();
}

  

在UHoudiniAssetInstanceInput::CreateInstanceInput()里,参考FEdModeFoliage::AddInstancesImp的方法, 在当前Level的AInstancedFoliageActor中对应FoliageType的FFoliageMeshInfo里,根据植被在Entity Point Cloud的Tranform信息,来添加Instance。
 
UWorld* World = GEditor->GetEditorWorldContext().World();
ULevel* TargetLevel = World->GetCurrentLevel(); AInstancedFoliageActor* IFA = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(TargetLevel, true);
FFoliageMeshInfo* MeshInfo;
UFoliageType* FoliageSettings = IFA->AddFoliageType(FoliageType, &MeshInfo); GLevelEditorModeTools().ActivateMode(FBuiltinEditorModes::EM_Foliage);
FEdModeFoliage* FoliageEditMode = (FEdModeFoliage*)GLevelEditorModeTools().GetActiveMode(FBuiltinEditorModes::EM_Foliage); for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); ++InstanceIdx)
{
FTransform InstanceTransform; FHoudiniEngineUtils::TranslateHapiTransform(InstancerPartTransforms[InstanceIdx], InstanceTransform);
FFoliageInstance Inst;
Inst.Location = InstanceTransform.GetLocation();
Inst.Rotation = InstanceTransform.GetRotation().Rotator();
MeshInfo->AddInstance(IFA, FoliageSettings, Inst, nullptr, true);
}

  

如下图所示,过程化植被也加入到了Level的AInstancedFoliageActor里。这样生成后也可以使用Foliage Editor来做二次修改。
 
当场景美术需要做二次修改时,那么首先需要把修改区域的植被先清除掉,再使用Houdini对这块绘制区域重新过程化生成植被。这就需要Houdini Engine可以支持移除掉绘制区域植被的功能。这个可以参考void FEdModeFoliage::ReapplyInstancesForBrush的方法。使用MeshInfo->InstanceHash->GetInstancesOverlappingBox来获取美术绘制范围的Instance,在利用MeshInfo->InstanceHash->RemoveInstance把范围内对应的Foliage Type移除,再使用HDA来重新生成。在上一篇中我们也讲到Select Tool其实绘制的是地表的Mask,也就是对应地形Tile的X,Y值,所以这里还需要把Landscape的X,Y值转成世界空间的Box,来做判断,这部分的实现代码如下:
 
ULandscapeComponent* SelectLandscapeComponent = FHoudiniLandscapeUtils::SelectLandscapeComponentArray[Index];
int32 MinX = MAX_int32;
int32 MinY = MAX_int32;
int32 MaxX = -MAX_int32;
SelectLandscapeComponent->GetComponentExtent(MinX, MinY, MaxX, MaxY); ULandscapeInfo* LandscapeInfo = SelectLandscapeComponent->GetLandscapeProxy()->GetLandscapeInfo();
for (int32 X = MinX; X <= MaxX; X++)
{
for (int32 Y = MinY; Y <= MaxY; Y++)
{
float RegionSelect = LandscapeInfo->SelectedRegion.FindRef(FIntPoint(X, Y));
if (RegionSelect > 0)
{
SelectRegionNum++;
FBoxSphereBounds ComponentBounds =
SelectLandscapeComponent->CalcBounds(SelectLandscapeComponent->GetComponentTransform()); FBox CachedLocalBox;
CachedLocalBox.Min = FVector(X, Y, 0);
CachedLocalBox.Max = FVector(X+1, Y+1, 0);
CachedLocalBox.IsValid = 1;
FBox MyBounds = CachedLocalBox.TransformBy(SelectLandscapeComponent->GetLandscapeProxy()->
GetLandscapeActor()->GetActorTransform());
MyBounds.Max.Z = ComponentBounds.GetBox().Max.Z ;
MyBounds.Min.Z = ComponentBounds.GetBox().Min.Z ;
FBoxSphereBounds RegionBounds = FBoxSphereBounds(MyBounds);
auto TempInstances = MeshInfo->InstanceHash->GetInstancesOverlappingBox(RegionBounds.GetBox());
for (int32 Idx : TempInstances)
{
if(InInstancesToRemove.Find(Idx) == INDEX_NONE)
InInstancesToRemove.Add(Idx);
}
}
}
}
MeshInfo->RemoveInstances(IFA, InInstancesToRemove, true);

  

迭代SelectRegion的每一个绘制点,把这个绘制点根据地形世界变化转换为对应的Box,再判断Box里是否有Instance,再进行移除操作。如果是基于Landscape Component做再生成就简单很多了,获取这个Component的Box,移除掉Box范围内的植被实例。
分步的看一下修改改后的效果。首先Houdini Engine会把绘制区域的植被全部清除掉。
 
然后再根据HDA里的Scatter算法,来摆放Instance并加入到关卡的AInstancedFoliageActor里。
之前的代码示例只是移除其中一种FoliageType,如果需要删除掉绘制区域的所有Foliage Type的话,只需要迭代每个FoliageType对应的FFoliageMeshInfo,再进行删除Instance操作即可。
 
TMap<UFoliageType*, FFoliageMeshInfo*> InstancesFoliageType = IFA->GetAllInstancesFoliageType();

for (auto& MeshPair : InstancesFoliageType)
{
FFoliageMeshInfo* MeshInfo = MeshPair.Value;
UFoliageType* FoliageSettings = MeshPair.Key;
}

  

就此,一个最基本的Houdini驱动的UE4植被系统的FoliageType部分就完成了。而一些具体的细节改动和工具开发,会放在内容制作部分再做讲解。

GrassType的对应

上一节也讲到,UE4的植被系统除了Foliage Type外,还有Grass Type,Grass Type除了没有碰撞外,保存和生成方式也不一样,Foliage Type的植被是制作阶段就保存在AInstancedFoliageActor里,在游戏运行时跟随Level加载后作为Instance来渲染。而Grass Type虽然也是跟Foliage Type一样,使用的HierarchicalInstancedStaticMeshComponents来进行渲染,区别在于它是在游戏运行时根据相机的视角来生成的。而GrassType的分布信息,则是根据UE4的地形材质系统来输出的。就如下图所示,默认的哪种GrassType被布置地面的哪个位置,是通过采样Landsacpe Layer的信息来确定的。但这种方式就导致了Grass和Layer之前的强制绑定关系。在一些特殊需求上,比如在一些特定的地标区域生成某种特定的草,或者排除掉某些特定草的需求,使用默认的方案都很难解决。
一种折衷的方案,是像下图这样,自己输入一张全场景的植被布局Mask图,来作为GrassType的采样信息使用,但这受限于植被的种类,地图大小,很难保障精确,只能作为临时的方案。
 
    还有一种方法就是直接改引擎源码,void ALandscapeProxy::UpdateGrass(const TArray<FVector>& Cameras, bool bForceSync)的部分。这个就不是光修改Houdini Engine引擎就能解决的了,文章篇幅关系也只能放到后文单独挑出一章节来做讲解。

总结

至此,地形和植被相关的整个管线部分已经基本上打通,但和国外AAA级产品的过程化工具比,还是有很大的差距。
管线上的主要的差距还是在工具易用性和完善程度上,比如幽灵行动:荒野里,通过把过程化生成的地形,道路,铁路,以及手工修改的部分做分层保存,这样当一层做修改或恢复时,才不会影响到其他的层的修改。
 
后续的文章中,会逐步的深入到具体的场景地形和植被制作上,届时也会涉及到更多Houdini Enigne管线修改的细节上。
 
 
 
 
 

Houdini技术体系 基础管线(四) :Houdini驱动的UE4植被系统 下篇的更多相关文章

  1. Houdini技术体系 基础管线(三) :UE4 Landscape Component的多选支持 下篇

    背景 上篇中,我们介绍了如何修改Houdini Enigne来设置单个Landscape Compnent的Height和Layer的数据,但原生Houdini Engine并不支持多选Compone ...

  2. Houdini技术体系 基础管线(三) :UE4以选择区域的方式对地形做生成和更新 上篇

    背景     前一节里,解决了Houdini地形无缝导入到UE4的流程问题.但这种方法也有它的局限性,在实际游戏项目里,LA和LD还是偏向在游戏引擎编辑器里工作,他们的一些设计也会影响到地形的信息,那 ...

  3. Houdini技术体系 基础管线(四) :Houdini驱动的UE4植被系统 上篇

    背景 之前在<Houdini技术体系 过程化地形系统(一):Far Cry5的植被系统分析>一文中已经对AAA游戏中过程化植被的需求有了一定的定义,后续工作就是如何用Houdini开发功能 ...

  4. Houdini技术体系 基础管线(一) : Houdini与Houdini Engine的安装

    Houdini 下载与安装 在官网 https://www.sidefx.com/download/ 下载最新的Production Build 版本,当前是16.5版本,需要注册帐号 PS:公司内网 ...

  5. Houdini技术体系 基础管线(二) :Heightfiled与UE4的无缝导入以及对World Composition的支持

    Authored by TraceYang 前言    传统的制作做比较真实大世界3D关卡地形时,通常的采用的方式是把HeightMap和SplatMap(Layer Mask)导入到引擎的地形系统里 ...

  6. Houdini技术体系 过程化地形系统(一):Far Cry5的植被系统分析

    背景     在大世界游戏里,植被(biome)是自然环境非常重要的组成部分,虽然UE4里的也有比较不错的地形+植被系统,但相比国外AAA级游戏的效果,还是有不少的差距,简介如下: UE4的植被分为( ...

  7. 01-java技术体系基础

    java体系基础 理论 编程语言: 系统级: C, C++, go, erlang ... 应用级: C#, Java, Python, Perl, Ruby, php 虚拟机: jvm(java虚拟 ...

  8. Houdini技术体系大纲

    Houdini for UE4 Pipeline的系列教程,前言等想好再写吧

  9. 测试需要了解的技术之基础篇四__UI自动化测试体系

    UI自动化测试体系 1.Andriod 自动化测试:Appium 环境安装与架构介绍.Appium Desktop用例录制.Appium测试用例流程.元素定位方法 IA/AID/XPATH/UISel ...

随机推荐

  1. Vue常用V-标签

    1.v-once: 只绑定一次 2.v-html <div v-html='name'></div> //会渲染标签 var app = new Vue({ el:'#app' ...

  2. C#中#region和#endregion的用法

    一.用法说明 #region 说明 Program1 #endregion 二.作用 (1)注释其中间的代码段 (2)折叠中间的代码块(折叠后的说明文字为#region后面的说明)

  3. Yii2 表单(form)

    表单 1.表单的创建 在 yii 中主要通过 yii\widgets\ActiveForm 类来创建表单 ActiveForm::begin() 不仅创建了一个表单实例,同时也标志着表单的开始. 放在 ...

  4. click()和onclick()的区别

    click()和onclick()的区别: 1.onclick是绑定事件,告诉浏览器在鼠标点击时候要做什么 click本身是方法作用是触发onclick事件,只要执行了元素的click()方法,就会触 ...

  5. python之装饰器篇

    一.基本装饰器 基本装饰器的作用: 在不改变原函数的基础上, 通过装饰器, 给原函数新增某些功能 实现方法: 在原函数上加 @装饰器名字 其中@叫做语法糖 定义装饰器 第一层函数传入参数(用于传入原函 ...

  6. jquery 上下滚动显示隐藏

      function scroll(fn) { var beforeScrollTop = document.body.scrollTop, fn = fn || function() {}; win ...

  7. 【LCA&倍增】货物运输 @upcexam5909

    时间限制: 1 Sec 内存限制: 128 MB 题目描述 在一片苍茫的大海上,有n座岛屿,岛屿与岛屿之间由桥梁连接,所有的岛屿刚好被桥梁连接成一个树形结构,即共n-1架桥梁,且从任何一座岛屿出发都能 ...

  8. 需要看源码的java类

    1.数据结构相关的类,如String.ArrayList,LinkedList,HashMap和ConcurrentHashMap等等.2.线程并发相关的类,如Synchronized.Reentra ...

  9. InfluxDB meta文件解析

    操作系统 : CentOS7.3.1611_x64 go语言版本:1.8.3 linux/amd64 InfluxDB版本:1.1.0 influxdb默认配置: /etc/influxdb/infl ...

  10. html5学习笔记——基础

    一:Canvas <canvas> 标签只是图形容器,图形的绘制需要用JS来定义. 1:绘制与填充 stroke():绘制,空心. fillXX():填充,实心. 2:绘制线条 var c ...