本文主要是介绍简单梳理UE4的Houdini官方插件代码,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
Houdini官方插件名字叫 “Houdini Engine”,它搭建了Houdini数据与UE4数据间的桥梁。我接触这个插件已经有段时间了,我想是时候梳理一下插件的结构了。(当前我用的UE4版本是4.24.2,Houdini版本18.0.348)
需要说明的是,这篇博客主要是从代码出发的。我准备先分析插件整体的代码结构,再逐个翻阅每个文件试图搞明白他角色。但如果不准备研究代码结构和实现细节,而只是想从使用层面上学习的话,那么官方文档:Houdini Engine for Unreal是个很好的参考资料。
值得一提的是,虽然这个插件是专门服务于Houdini的,但是我在做项目里其他和Houdini无关的插件时,也会查阅这个插件中的代码,因为我发现其中有很多是拓展编辑器功能的,这对于一个UE4编辑器插件新手来说还是很有用的。
结构
Houdini Engine插件包含了两个模块,HoudiniEngineRuntime
和HoudiniEngineEditor
:
"Modules" :
[{"Name" : "HoudiniEngineRuntime","Type" : "Runtime"},{"Name" : "HoudiniEngineEditor","Type" : "Editor","LoadingPhase" : "PostEngineInit"}
]
其中,HoudiniEngineEditor
模块里是专门为编辑器服务的内容,他依赖于HoudiniEngineRuntime
。而对于HoudiniEngineRuntime
,我将其主要分为了三部分的内容,他们实际上也是逐步依赖的三个层级,从底层到上层依次是:
- HAPI:Houdini Engine API,包含了最基础的定义以及操作,函数是从Houdini提供的dll中导出的。
- Utils:包含一些公用的操作,主要是一系列static函数。他们是对HAPI中函数的更高一层的封装。
- 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.h
,HAPI_API.h
,HAPI_Common.h
,HAPI.h
,HAPI_Helpers.h
,HoudiniApi.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.h
和HAPI_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
应为Instancer或PackedPrimitiveInstancer。
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
,还包括UInstancedStaticMeshComponent
、UHierarchicalInstancedStaticMeshComponent
、UHoudiniMeshSplitInstancerComponent
。这其实有些让我迷惑,我觉得这样一个函数不应该放到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 就将HoudiniAssetComponent
与HoudiniAsset
对应了起来。
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
FHoudiniEngineTask
和FHoudiniEngineTaskInfo
是服务于这个系统而定义的结构体
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官方插件代码的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!