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

69 篇文章 152 订阅

前言

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 HoudiniAsset
    class UHoudiniAsset* HoudiniAsset = nullptr;

    // Object that handles material and mesh caches as they are built
    IHoudiniCookHandler* 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 this

On 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 extension
    if ( AssetTypeActions.Num() > 0 )
    {
        // Add the menu extensions via our HoudiniAssetTypeActions
        FHoudiniAssetTypeActions * 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
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值