简单梳理UE4的Houdini官方插件代码

2024-09-06 23:48

本文主要是介绍简单梳理UE4的Houdini官方插件代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

Houdini官方插件名字叫 “Houdini Engine”,它搭建了Houdini数据与UE4数据间的桥梁。我接触这个插件已经有段时间了,我想是时候梳理一下插件的结构了。(当前我用的UE4版本是4.24.2,Houdini版本18.0.348)

需要说明的是,这篇博客主要是从代码出发的。我准备先分析插件整体的代码结构,再逐个翻阅每个文件试图搞明白他角色。但如果不准备研究代码结构和实现细节,而只是想从使用层面上学习的话,那么官方文档:Houdini Engine for Unreal是个很好的参考资料。

值得一提的是,虽然这个插件是专门服务于Houdini的,但是我在做项目里其他和Houdini无关的插件时,也会查阅这个插件中的代码,因为我发现其中有很多是拓展编辑器功能的,这对于一个UE4编辑器插件新手来说还是很有用的。

结构

Houdini Engine插件包含了两个模块,HoudiniEngineRuntimeHoudiniEngineEditor

"Modules" :
[{"Name" : "HoudiniEngineRuntime","Type" : "Runtime"},{"Name" : "HoudiniEngineEditor","Type" : "Editor","LoadingPhase" : "PostEngineInit"}
]

其中,HoudiniEngineEditor模块里是专门为编辑器服务的内容,他依赖于HoudiniEngineRuntime。而对于HoudiniEngineRuntime,我将其主要分为了三部分的内容,他们实际上也是逐步依赖的三个层级,从底层到上层依次是:

  1. HAPI:Houdini Engine API,包含了最基础的定义以及操作,函数是从Houdini提供的dll中导出的。
  2. Utils:包含一些公用的操作,主要是一系列static函数。他们是对HAPI中函数的更高一层的封装。
  3. Actor:主要包含了HoudiniAssetActor以及相关的类的定义,他们会调用Utils中的函数。这一部分实际上对应了官方所设计的插件使用流程:hda会从外部导入变为一个UHoudiniAsset,将其拖到场景中会变为一个AHoudiniAssetActor,而这个Actor会负责维护与Houdini的交互。

也就是说,我将插件代码理解为层层依赖的三个部分:
在这里插入图片描述
如果对插件代码进行编辑,就可以使其相对于当前的项目更加定制化。除了可以进行“拓展”外,也可以从当中的某一层开始开发,“抛弃”在这一层之上的官方设计。具体来讲:
(1) 只依赖于HAPI。这意味着只用最底层的API,完全自定义如何让Houdini中的数据与UE4交互。这是完全可行的,不过会比较辛苦,并且会产生很多和Utils中类似的代码,如果真的要这么做的话可以多参考Utils中的代码。
(2) 依赖于HAPI以及对其封装的Utils函数等公用操作。这么做看起来是最明智的,但实现时还有点问题:Utils中的函数并没有保证完全对HoudiniAssetActor无知,这导致没办法完全舍弃掉官方的HoudiniAssetActor的内容。不过好在Utils依赖他们的内容十分有限,有时可以耍些花招来解决,例如:有的函数的参数需要HoudiniAssetComponent对象,就临时构造一个HoudiniAssetComponent对象给他,测试看有没有影响核心功能。
(3) 完全采纳了HoudiniEngineRuntime模块,但自定义开发编辑器相关的内容,理论上也是可以的,不过我觉得意义不大。理由是HoudiniEngineEditor就是专门为官方这套HoudiniAssetActor的设计服务的,既然已经接纳了HoudiniAssetActor,那么为其定制的界面也没理由舍弃。

下面,开始讨论这四个部分的具体职责,并翻阅一遍其中的文件

HAPI

这一部分主要提供了最底层的API函数以及定义。值得一提的是,这部分并不是UE4专用的,所有的Houdini插件都是基于这套API的。官方文档中有关于名为Houdini Engine这套API的文档。

这一部分包含的文件有:HAPI_Version.hHAPI_API.hHAPI_Common.hHAPI.hHAPI_Helpers.hHoudiniApi.h/cpp,下面逐个看他们:

HAPI_Version.h

宏定义了插件对应的一些版本的信息。
例如Houdini版本:

#define HAPI_VERSION_HOUDINI_MAJOR 18
#define HAPI_VERSION_HOUDINI_MINOR 0
#define HAPI_VERSION_HOUDINI_BUILD 348
#define HAPI_VERSION_HOUDINI_PATCH 0

HAPI_API.h

定义了少量最基础的宏,例如:

#define HAPI_RETURN HAPI_Result HAPI_CALL

HAPI_Common.h

有一些最基础的定义。

例如,约定了顶点Attribute名字所对应的意义:

#define HAPI_ATTRIB_POSITION                "P"
#define HAPI_ATTRIB_UV                      "uv"
#define HAPI_ATTRIB_UV2                     "uv2"
...

例如,定义了,节点类型:

enum HAPI_NodeType
{HAPI_NODETYPE_ANY       = -1,HAPI_NODETYPE_NONE      = 0,HAPI_NODETYPE_OBJ       = 1 << 0,HAPI_NODETYPE_SOP       = 1 << 1,HAPI_NODETYPE_CHOP      = 1 << 2,...

例如,定义了Attribute包含什么信息:

struct HAPI_API HAPI_AttributeInfo
{HAPI_Bool exists;int count;...

HAPI.h

包含了大量的API函数,有丰富的注释

HAPI_Helpers.h

包含少量补充的API函数,不过注释较少。

HoudiniApi.h/cpp

包含了上面HAPI.hHAPI_Helpers.h中提到的函数,他们将会作为FHoudiniApi这个struct的static函数。
在模块的初始化阶段,FHoudiniApi::InitializeHAPI被调用,然后通过名字获得dll中这些函数的地址:

void FHoudiniApi::InitializeHAPI(void* LibraryHandle)
{if(!LibraryHandle) return;FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute"));FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup"));

这个dll就是Houdini安装目录的bin文件夹中的libHAPIL.dll
在这里插入图片描述

Utils

直接调用HAPI的函数理论上已经足够完成Houdini与UE4的交互了,但是HAPI本身是对UE4的内容“无知”的,因此在做Houdini和UE4的交互时,往往需要补充不少UE4相关的操作。而这些操作并不仅在一处出现,他们是“会重复”且“允许公用”的。于是,这些操作被封装到了一个个static函数中,并且有一些类被定义用来配合这些操作,这一部分我称为Utils部分。
Utils部分的内部,各种文件(或者说“概念”、“类”)之间也有着依赖关系,大致可以表示为:
在这里插入图片描述
下面按照依赖的顺序逐个看一下他们的细节。

HoudiniEngineRuntimePrivatePCH.h

这个头文件中定义了很多UE4和Houdini相互转换时的约定。角色和HAPI_Common.h类似,但是HoudiniEngineRuntimePrivatePCH.h是专门服务于UE4的。

例如:
当Houdini中的Attribute名字是"unreal_material"时,将其值视为材质对象的路径:

#define HAPI_UNREAL_ATTRIB_MATERIAL                     "unreal_material"

当Houdini中的Attribute名字是"unreal_uproperty_"时,那就尝试设置UE4对象的某个UProperty属性

#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX         "unreal_uproperty_"

HoudiniEngineString

使用HAPI来对Houdini进行交互时,得到的字符串类数据并不是字符串类型,例如节点名字:

struct HAPI_API HAPI_NodeInfo
{HAPI_StringHandle nameSH;

名字是一个HAPI_StringHandle类型,而它实际上一个int

typedef int HAPI_StringHandle;

要想转换成字符串,需要调用HAPI的函数。如果还想继续转为UE4的FString,则还需要更多操作。为了方便这一常用且重复的操作,FHoudiniEngineString被定义:

class HOUDINIENGINERUNTIME_API FHoudiniEngineString
{public:FHoudiniEngineString( int32 InStringId );bool ToStdString( std::string & String ) const;bool ToFName( FName & Name ) const;bool ToFString( FString & String ) const;bool ToFText( FText & Text ) const;

它以一个id作为构造函数的参数,并提供转换成其他字符串类型的操作。

HoudiniCookHandler.h

这个文件中定义了两个类。
IHoudiniCookHandler是一个接口类,被UHoudiniAssetComponent继承。它定义了一些cook时用到的接口:

class HOUDINIENGINERUNTIME_API IHoudiniCookHandler
{
public:virtual FString GetBakingBaseName( const struct FHoudiniGeoPartObject& GeoPartObject ) const = 0;virtual void SetStaticMeshGenerationParameters( class UStaticMesh* StaticMesh ) const = 0;virtual class UMaterialInterface * GetAssignmentMaterial( const FString& MaterialName ) = 0;virtual void ClearAssignmentMaterials() = 0;virtual void AddAssignmentMaterial( const FString& MaterialName, class UMaterialInterface* MaterialInterface ) = 0;virtual class UMaterialInterface * GetReplacementMaterial( const struct FHoudiniGeoPartObject& GeoPartObject, const FString& MaterialName) = 0;
};

另一个类是FHoudiniCookParams,指定了cook时候的诸多通用参数:

struct HOUDINIENGINERUNTIME_API FHoudiniCookParams
{FHoudiniCookParams(class UHoudiniAsset* InHoudiniAsset);FHoudiniCookParams(class UHoudiniAssetComponent* HoudiniAssetComponent);// The attached HoudiniAssetclass UHoudiniAsset* HoudiniAsset = nullptr;// Object that handles material and mesh caches as they are builtIHoudiniCookHandler* HoudiniCookManager = nullptr;...

它在多个Utils函数中被用作一个参数。

HoudiniGeoPartObject

一个FHoudiniGeoPartObject对应了Houdini中的一个Part。但是关于“Part”的定义,就有些令人迷惑了,因为在Houdini编辑器里只能显式的看到“Point”、“Primitive”这种概念,却看不到“Part”的划分。官方文档有关于Part的解释。按照我目前的理解,给设置不同的Primitive级别Group可以划分成不同的Part,但是完整的Part划分规则我可能需要研究一下了。

FHoudiniGeoPartObject中有表明它的所属节点和所属PartID的变量:

/** Id of corresponding HAPI Geo. **/
HAPI_NodeId GeoId;/** Id of corresponding HAPI Part. **/
HAPI_PartId PartId;

也有判断这个Part是否是PackedPrimitiveInstancer还是Volume的操作:

/** Return true if this geo part object corresponds to an instancer. **/
bool IsInstancer() const;/** Return true if this geo part object is a volume. **/
bool IsVolume() const;

FHoudiniGeoPartObject被用作很多Utils函数的参数。
但他内部也有些操作借用了FHoudiniEngineUtils中的函数。

HoudiniEngineUtils

HoudiniEngineUtils中有大量函数,是众多Utils函数的基础。
例如:

CreateStaticMeshesFromHoudiniAsset函数有2200行代码,它包含了将节点中的几何数据转换为UE4的StaticMesh牵扯到的操作:

/** Construct static meshes for a given Houdini asset. **/
static bool CreateStaticMeshesFromHoudiniAsset(HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams,bool ForceRebuildStaticMesh, bool ForceRecookAll,const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn,TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, FTransform & ComponentTransform );

GetUPropertyAttributeList函数寻找所有有unreal_uproperty_标识的Attribute。

/** Return a list with all the UProperty attributes found **/
static int32 GetUPropertyAttributeList( const FHoudiniGeoPartObject& GeoPartObject,TArray< UGenericAttribute >& AllUProps );

HoudiniEngineBakeUtils

官方的设计中,提供了一个“Bake”操作:
在这里插入图片描述
大致意思是将一个HDA的产出转换为一个与Houdini无关的UE4对象,这样游戏发布时就不需要依赖Houdini了。
Bake相关的操作多在HoudiniEngineBakeUtils中,例如点击【BakeBlueprint】所执行的函数:
在这里插入图片描述

HoudiniEngineMaterialUtils

材质相关的通用操作,详细内容目前我接触不多。

HoudiniLandscapeUtils

包含地形相关的通用操作。

例如:
CreateAllLandscapes负责将Houdini的高度场数据转换为UE4的ALandscapeProxy

// Creates all the landscapes/layers from the volume array
static bool CreateAllLandscapes(FHoudiniCookParams& HoudiniCookParams,const TArray< FHoudiniGeoPartObject > & FoundVolumes,TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >& Landscapes,TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >& NewLandscapes,TArray<ALandscapeProxy *>& InputLandscapeToUpdate,float ForcedZMin = 0.0f, float ForcedZMax = 0.0f );

其中作为参数的几个FHoudiniGeoPartObject应该都是Volume

CreateHeightfieldFromLandscape将UE4的ALandscapeProxy转换为Houdini节点:

// Creates a heightfield from a Landscape
static bool CreateHeightfieldFromLandscape(ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId );

HoudiniEngineInstancerUtils

Copy to Points节点有个选项Pack and Instance
在这里插入图片描述
勾选后,则表示这个Part是PackedPrimitiveInstancer。插件将尝试将其转换为InstancedStaticMeshComponent
CreateAllInstancers函数做了这一步:

static bool CreateInstancedStaticMeshComponent(	    UStaticMesh* StaticMesh,const TArray< FTransform >& InstancedObjectTransforms,const FHoudiniGeoPartObject& InstancerGeoPartObject,USceneComponent* ParentComponent,USceneComponent*& CreatedInstancerComponent,UMaterialInterface * InstancerMaterial = nullptr );

其中的FHoudiniGeoPartObject应为InstancerPackedPrimitiveInstancer

HoudiniParamUtils

它只有一个函数,就是构造所有的UHoudiniAssetParameter

/** Update parameters from the asset, re-uses parameters passed into CurrentParameters.
@AssetId: Id of the digital asset
@PrimaryObject: Object to use for transactions and as Outer for new top-level parameters
@CurrentParameters: pre: current & post: invalid parameters
@NewParameters: new params added to thisOn Return: CurrentParameters are the old parameters that are no longer valid, NewParameters are new and re-used parameters.
*/
static bool Build( HAPI_NodeId AssetId, class UObject* PrimaryObject, TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& CurrentParameters,TMap< HAPI_ParmId, class UHoudiniAssetParameter * >& NewParameters );

它的行为实际上和UHoudiniAssetParameter高度相关,随后再看。

Actor

AHoudiniAssetActor以及众多继承UObject的对象,代表着官方所安排的插件使用流程。

HoudiniAsset

UHoudiniAsset是Houdini插件定义的Asset对象:

class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject

将一个HDA拖入引擎,就创建了一个UHoudiniAsset
在这里插入图片描述
HoudiniAsset本身并没有太多的设计。大多数时候它只作为一个标识作用。

HoudiniAssetActor

AHoudiniAssetActor是插件定义的Actor:

class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor

将一个HoudiniAsset拖到场景中,就会创建一个AHoudiniAssetActor:
在这里插入图片描述
AHoudiniAssetActor本身也没有太多的设计。但是他的HoudiniAssetComponent有相当多的内容。

HoudiniAssetComponent

class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniCookHandler

HoudiniAssetComponent包含了相当多的内容。其中一项最重要的内容是,他会不断地Tick,检查是否需要重新cook数据,如果是的话则触发一次cook。

HoudiniAssetComponentMaterials

class HOUDINIENGINERUNTIME_API UHoudiniAssetComponentMaterials : public UObject

UHoudiniAssetComponent有一个UHoudiniAssetComponentMaterials成员:

/** Material assignments. **/
UHoudiniAssetComponentMaterials * HoudiniAssetComponentMaterials;

它参与材质方面的逻辑,目前我还没有研究太多。

HoudiniAssetParameter

UHoudiniAssetParameter继承自UObject,它对应了一个Houdini节点中的参数。
它拥有能知道自己是否改变的函数:

/** Return true if this parameter has been changed. **/
virtual bool HasChanged() const;

UHoudiniAssetParameter有很多的子类,每个子类都代表着一种参数,并会生成不同的UI:
在这里插入图片描述
UHoudiniAssetComponent存储了它所拥有的参数:

 /** Parameters for this component's asset, indexed by parameter id. **/
TMap< HAPI_ParmId, UHoudiniAssetParameter * > Parameters;

HoudiniAssetInstanceInputField

class HOUDINIENGINERUNTIME_API UHoudiniAssetInstanceInputField : public UObject

UHoudiniAssetInstanceInput也是一种HoudiniAssetParameter,它代表的参数是作为Houdini生成的Instance的输入。他拥有UHoudiniAssetInstanceInputField列表:

 /** List of fields created by this instance input. **/
TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields;

界面上就是:
在这里插入图片描述

HoudiniSplineComponent

如果将HDA的Editable Nodes设置一个curve类型的节点:
在这里插入图片描述
插件将试图创建一些“控制手柄”来协助调整曲线的位置:
在这里插入图片描述
如图红色的部分即创建出的“控制手柄”。不过,完成这个效果不仅需要UHoudiniSplineComponent,还需要对其可视化的FHoudiniSplineComponentVisualizer,后者一会儿在Editor部分看下更多细节。

这一部分详细见官方文档:Curves

HoudiniHandleComponent

和上面的类似。详细见官方文档:Handles

HoudiniInstancedActorComponent

这一部分和instance有关。
其中定义了一个component:

class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent

这个component有没有用到我不确定,不过我确定的是它的一个static函数被用到了:

/** Update instances of a given instancer component. (could be ISMC, IAC or MSIC) **/
static void UpdateInstancerComponentInstances(USceneComponent * Component,const TArray< FTransform > & ProcessedTransforms,const TArray<FLinearColor> & InstancedColors );

看样子这个函数处理的不仅是UHoudiniInstancedActorComponent,还包括UInstancedStaticMeshComponentUHierarchicalInstancedStaticMeshComponentUHoudiniMeshSplitInstancerComponent。这其实有些让我迷惑,我觉得这样一个函数不应该放到UHoudiniInstancedActorComponent中,但这可能有其他原因吧。

我创建了一个包含instance的HDA,放到场景中可以断到这个函数中:
在这里插入图片描述
我看了这里的四个变量,除了ISMC其他都是NULL。也就是说,UHoudiniInstancedActorComponent的函数处理了其他component的事情,而这其他的component还和自己是“平级”的,这让我很是迷惑。。。

UHoudiniMeshSplitInstancerComponent

这是另外一个和instance有关的类,暂时我还不知道他将在何处发挥作用。它的注释可以参考:

/**
* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being
* 'instanced' multiple times by multiple UStaticMeshComponents.  This is as opposed to the
* UInstancedStaticMeshComponent wherein a signle mesh is instanced multiple times by one component.
*/
UCLASS( config = Engine )
class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent

HoudiniAttributeDataComponent

这一部分也很疑惑,它似乎定义了一个和Attribute相关联的Component:

class HOUDINIENGINERUNTIME_API UHoudiniAttributeDataComponent : public UActorComponent

但是我没有找到创建这个component的逻辑,甚至出现它的地方都只有一处,而那里也只是尝试找这个类型的component。我怀疑这个概念目前并没有被真正使用。

Editor

这一部分包含了那些只在编辑时用到的内容,基本上是一些界面。

HoudiniAssetComponentDetails

它指定了UHoudiniAssetComponent的细节面板该如何显示
在这里插入图片描述
HoudiniEngineEditor.cpp中,FHoudiniAssetComponentDetails被指定用来显示HoudiniAssetComponent的细节面板。

// Register details presenter for our component type and runtime settings.
PropertyModule.RegisterCustomClassLayout(TEXT( "HoudiniAssetComponent" ),FOnGetDetailCustomizationInstance::CreateStatic( &FHoudiniAssetComponentDetails::MakeInstance ) );

HoudiniParameterDetails

它定义了每个参数会有怎样的UI,例如Button会是按钮,Float会是滑块等。
FHoudiniAssetComponentDetails在制作细节面板时会调用它:

FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, HoudiniAssetParameter );

HoudiniAssetFactory

class UHoudiniAssetFactory : public UFactory, public FReimportHandler

它定义了一个HDA拖入引擎中变为UHoudiniAsset的操作。
在他的构造函数中,它指定了他将与UHoudiniAsset这个类对应:

UHoudiniAssetFactory::UHoudiniAssetFactory( const FObjectInitializer & ObjectInitializer ): Super( ObjectInitializer )
{// This factory is responsible for manufacturing HoudiniEngine assets.SupportedClass = UHoudiniAsset::StaticClass();...

HoudiniAssetActorFactory

它定义了将一个UHoudiniAsset从内容浏览器拖入到场景中变为AHoudiniAssetActor的操作。
在他的构造函数中,指定了它所创建的Actor的类:

UHoudiniAssetActorFactory::UHoudiniAssetActorFactory( const FObjectInitializer & ObjectInitializer ): Super( ObjectInitializer )
{DisplayName = LOCTEXT( "HoudiniAssetDisplayName", "Houdini Engine Asset" );NewActorClass = AHoudiniAssetActor::StaticClass();
}

CanCreateActorFrom函数中,指定了他必须由UHoudiniAsset所创建。

bool UHoudiniAssetActorFactory::CanCreateActorFrom( const FAssetData & AssetData, FText & OutErrorMsg )
{if ( !AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass() ) ){OutErrorMsg = NSLOCTEXT( "CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified." );return false;}return true;
}

ComponentVisualizer

包含了对UHoudiniSplineComponent可视化的FHoudiniSplineComponentVisualizer,和对UHoudiniHandleComponent进行可视化的FHoudiniHandleComponentVisualizer

他们都继承自FComponentVisualizer,包含一些例如“画线”、“画点”操作,并且定义了移动控制手柄所触发的操作。

HoudiniEngineEditor.cpp中,一个FComponentVisualizer和它想要可视化的Component对应起来。

if ( !SplineComponentVisualizer.IsValid() )
{SplineComponentVisualizer = MakeShareable( new FHoudiniSplineComponentVisualizer );GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(),SplineComponentVisualizer);SplineComponentVisualizer->OnRegister();
}if ( !HandleComponentVisualizer.IsValid() )
{HandleComponentVisualizer = MakeShareable( new FHoudiniHandleComponentVisualizer );GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(),HandleComponentVisualizer);HandleComponentVisualizer->OnRegister();
}

HoudiniToolPalette

class SHoudiniToolPalette : public SCompoundWidget, public FNotifyHook

它定义了这个面板:
在这里插入图片描述
HoudiniEngineEditor.cpp中,指定了SHoudiniToolPalette被放到名字是Houdini Engine的分栏中。

FPlacementCategoryInfo Info(LOCTEXT( "HoudiniCategoryName", "Houdini Engine" ),"HoudiniEngine",TEXT( "PMHoudiniEngine" ),25
);
Info.CustomGenerator = []() -> TSharedRef<SWidget> { return SNew( SHoudiniToolPalette ); };IPlacementModeModule::Get().RegisterPlacementCategory( Info );

HoudiniAssetTypeActions

class FHoudiniAssetTypeActions : public FAssetTypeActions_Base

它定义了右键一个内容浏览器中的UHoudiniAsset所弹出的菜单
在这里插入图片描述
它通过重载GetSupportedClass这一虚函数指出了自己对应的资源类型:

UClass *
FHoudiniAssetTypeActions::GetSupportedClass() const
{return UHoudiniAsset::StaticClass();
}

然后在HoudiniEngineEditor.cpp中被注册:

// Create and register asset type actions for Houdini asset.
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >( "AssetTools" ).Get();
RegisterAssetTypeAction( AssetTools, MakeShareable( new FHoudiniAssetTypeActions() ) );

此外需要指出,它还参与了右键一个场景中的AHoudiniAssetActor所弹出的菜单动作,具体来说:
HoudiniEngineEditor.cpp中有向关卡编辑器注册新的动作的操作:

FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked<FLevelEditorModule>( "LevelEditor" );
auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();MenuExtenders.Add( FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw( this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender ) );
LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle();

其中调用了FHoudiniEngineEditor::GetLevelViewportContextMenuExtender,而观察这个函数的细节,会发现它会遍历所选Actor中的AHoudiniAssetActor类型,然后找到其对应的HoudiniAsset,随后为这些HoudiniAsset制作新的动作菜单,而此时就用到了FHoudiniAssetTypeActions ::AddLevelEditorMenuExtenders

if ( HoudiniAssets.Num() > 0 )
{// Add the Asset menu extensionif ( AssetTypeActions.Num() > 0 ){// Add the menu extensions via our HoudiniAssetTypeActionsFHoudiniAssetTypeActions * HATA = static_cast<FHoudiniAssetTypeActions*>( AssetTypeActions[0].Get() );if ( HATA )Extender = HATA->AddLevelEditorMenuExtenders( HoudiniAssets );}
}


图中红色部分就是由FHoudiniAssetTypeActions ::AddLevelEditorMenuExtenders创建的,而剩下Houdini相关的动作则在FHoudiniEngineEditor::GetLevelViewportContextMenuExtender中。

SNewFilePathPicker

/*** Implements an editable text box with a browse button.*/
class SNewFilePathPicker : public SCompoundWidget

实现了一个点击界面,可以点击选择文件:
在这里插入图片描述
这一控件在构建File类型的参数时被用到。

HoudiniAssetLogWidget

class SHoudiniAssetLogWidget : public SCompoundWidget

构建点击log出现的窗口:
在这里插入图片描述

HoudiniShelfEdMode

class FHoudiniShelfEdMode : public FEdMode
class FHoudiniShelfEdModeToolkit : public FModeToolkit

他们想要创建一个新的编辑模式,但是目前看来他们是空的,实际也没有被注册。我想可能是未来或者过去有被用到。

HoudiniAssetBroker

文件中的内容极少,定义了一个一个类:

class FHoudiniAssetBroker : public IComponentAssetBroker

目前我对IComponentAssetBroker的理解还比较浅,看起来它将一种Component和一种UObject(更精确讲应该是Asset)对应起来:

/** Get the currently assigned asset from the component */
virtual UObject* GetAssetFromComponent(UActorComponent* InComponent)=0;

这里FHoudiniAssetBroker 就将HoudiniAssetComponentHoudiniAsset对应了起来。

UObject * FHoudiniAssetBroker::GetAssetFromComponent( UActorComponent * InComponent )
{if ( UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( InComponent ) ){return HoudiniAssetComponent->GetHoudiniAsset();}return nullptr;
}

而这种对应关系将被用于何处,目前我还没有精确的理解,未来有机会可以了解下。

其他杂项

HoudiniRuntimeSettings

UCLASS( config = Engine, defaultconfig )
class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject

这个类代表了一个配置:
在这里插入图片描述
HoudiniRuntimeSettingsDetails对这个类进行了细节面板的定制,不过主要也就是增加了一个只读的面板:
在这里插入图片描述

HoudiniEngineScheduler

由于在主线程中调用FHoudiniApi::CookNode等耗时较高的操作会卡住编辑器界面,因此插件设计了一个HoudiniEngineScheduler系统,用于将这种耗时较高的操作放到其他线程。
FHoudiniEngineScheduler是一个FRunnable。(在仅支持单线程的环境下,会支持FSingleThreadRunnable

class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable

FHoudiniEngineTaskFHoudiniEngineTaskInfo是服务于这个系统而定义的结构体

struct HOUDINIENGINERUNTIME_API FHoudiniEngineTask
struct HOUDINIENGINERUNTIME_API FHoudiniEngineTaskInfo

一次Cook的流程如下:

1.添加任务

UHoudiniAssetComponent::TickHoudiniComponent()中感到了需要添加一个cook任务:
在这里插入图片描述

2. cook

FHoudiniEngineScheduler这个线程中触发了cook。
在这里插入图片描述
随后,完成标志会记录在FHoudiniEngine::TaskInfos中。

3. 创建对应的UE4对象

UHoudiniAssetComponent::TickHoudiniComponent()中发现了有新的cook任务完成,需要将对应UE4对象(比如staticmesh)同步。
在这里插入图片描述

HoudiniEngineCommandlet

包含了两个Commandlet:

UHoudiniEngineConvertBgeoCommandlet,将一个bgeo转换为一个UAsset,可以在它的Main中看到一些参数的说明:

HOUDINI_LOG_MESSAGE( TEXT( "BGEO_IN" ) );
HOUDINI_LOG_MESSAGE( TEXT( "\tPath to the the source .bgeo file to convert." ) );HOUDINI_LOG_MESSAGE( TEXT( "UASSET_OUT (optional)" ) );
HOUDINI_LOG_MESSAGE( TEXT( "\tPath for the converted uasset file. If not present, the directory/name of the bgeo file will be used" ) );

UHoudiniEngineConvertBgeoDirCommandlet,上面那个Commandlet的批量版(转换一个路径下所有的)。

HoudiniPluginSerializationVersion

包含序列化相关的一些定义,内容很少。

HoudiniEngineSubstance

定义了Substance相关的一些函数:

struct HOUDINIENGINERUNTIME_API FHoudiniEngineSubstance
{/** Used to locate and load (if found) Substance instance factory object. **/static UObject * LoadSubstanceInstanceFactory( UClass * InstanceFactoryClass, const FString & SubstanceMaterialName );/** Used to locate and load (if found) Substance graph instance object. **/static UObject * LoadSubstanceGraphInstance( UClass * GraphInstanceClass, UObject * InstanceFactory );/** Retrieve Substance RTTI classes we are interested in. **/static bool RetrieveSubstanceRTTIClasses(UClass *& InstanceFactoryClass,UClass *& GraphInstanceClass,UClass *& UtilityClass );/** HAPI: Check if material is a Substance material. If it is, return its name by reference. **/static bool GetSubstanceMaterialName( const HAPI_MaterialInfo & MaterialInfo, FString & SubstanceMaterialName );
};

目前还没有接触,但是应该很值得未来花时间研究。

总结

目前关于整体结构的理解似乎是没问题,不过代码细节上还有一些需要继续研究的方向:

  • Material相关的逻辑
  • Instance相关的Component还是有让我迷惑的地方
  • 探究IComponentAssetBroker的含义
  • 研究HoudiniEngineSubstance

这篇关于简单梳理UE4的Houdini官方插件代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1143465

相关文章

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu

uva 10130 简单背包

题意: 背包和 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d