《调教UE5:编辑器拓展指南》Material 相关基础
《调教UE5》系列记录的是笔者在开发UE引擎中总结的些许经验。文中所有观点和结论仅代表个人见解,作为自学笔记和日后反思之参考,亦可能存在谬误或过时之处。如有错漏和不当,恳请多加指正。<hr/><hr/>目录不可忽略的事前准备
Material 操作
使用 C++ 创建材质
使用 C++ 创建材质实例
使用 C++ 更改材质参数
使用 C++ 编辑材质蓝图
Material 拓展
使用 C++ 拓展 Nodes<hr/>\large\textbf{“请不要迷恋昙花一现,刹那即永恒是胆小者的托词。} \\ ~\\ \large\textbf{没有一样真正美丽的东西,可以廉价到在瞬间就展尽它的华彩。”} \\
<hr/>在为编辑器开发拓展的时候,简化材质的制作流程也是一个需求十分旺盛的方向,例如 Megascans 插件提供的一键创建材质和混合材质的功能。这一章我们来讨论一些与 Material 相关的话题。
不可忽略的事前准备
为了拥有相对易读的内容,我们来为本章内容准备一个独立的模块。
创建 MaterialRelated 模块
在项目 Source 文件夹下新建“MaterialRelated”文件夹,并组织如下结构。我们不打算将此模块用于其他模块中,因此可以不需要“Public”和“Private”文件夹。
[*]MaterialRelated
[*]MaterialRelated.h
[*]MaterialRelaed.cpp
[*]MaterialRelated.Build.cs
在 MaterialRelated.h 文件中声明模块类“FMaterialRelated”。
// MaterialRelated.h
#pragma once
#include &#34;Modules/ModuleInterface.h&#34;
class FMaterialRelated : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
virtual ~FMaterialRelated() {}
};
在 MaterialRelated.cpp 中实现模块类,设置它的名称为“MaterialRelated”。
// MaterialRelated.cpp
#pragma once
#include &#34;MaterialRelated.h&#34;
IMPLEMENT_MODULE(FMaterialRelated, MaterialRelated)
void FMaterialRelated::StartupModule()
{
IModuleInterface::StartupModule();
}
void FMaterialRelated::ShutdownModule()
{
IModuleInterface::ShutdownModule();
}
在 MaterialRelated.Build.cs 中设置模块依赖项。
// MaterialRelated.Build.cs
using UnrealBuildTool;
public class MaterialRelated : ModuleRules
{
public MaterialRelated(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
&#34;Core&#34;,
&#34;CoreUObject&#34;,
&#34;Engine&#34;,
&#34;InputCore&#34;,
});
PrivateDependencyModuleNames.AddRange(new string[] {});
}
}
我们的项目名称为“ExtendEditor”,打开“ExtendEditor.uproject”,添加模块加载方式。
// ExtendEditor.uproject
{
&#34;FileVersion&#34;: 3,
&#34;EngineAssociation&#34;: &#34;5.0&#34;,
&#34;Category&#34;: &#34;&#34;,
&#34;Description&#34;: &#34;&#34;,
&#34;Modules&#34;: [
{
&#34;Name&#34;: &#34;ExtendEditor&#34;,
&#34;Type&#34;: &#34;Runtime&#34;,
&#34;LoadingPhase&#34;: &#34;Default&#34;,
&#34;AdditionalDependencies&#34;: [
&#34;Blutility&#34;
]
},
...
{
&#34;Name&#34;: &#34;MaterialRelated&#34;,
&#34;Type&#34;: &#34;Editor&#34;,
&#34;LoadingPhase&#34;: &#34;Default&#34;,
}
],
...
打开“ExtendEditorEditor.Target.cs”,将模块添加到项目依赖。
// ExtendEditorEditor.Target.cs
using UnrealBuildTool;
using System.Collections.Generic;
public class ExtendEditorEditorTarget : TargetRules
{
public ExtendEditorEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[]
{
&#34;ExtendEditor&#34;,
...
&#34;MaterialRelated&#34;
} );
}
}
重启代码编辑器并编译,完成“FMaterialRelated”模块创建。
创建 MaterialSpawner 类
我们创建一个名为“MaterialSpawner”的类,该类继承自 UEditorUtilityWidget。我们的实际逻辑都在这个类中编写。
运行引擎编辑器,来到“C++Classes”文件夹中,在文件夹中单击右键,选择 NewC++Class,创建新的 C++ 类。搜索“EditorUtilityWidget”,选中要继承的父类,单击下一步。
指定新类名为“MaterialSpawner”,选择创建到 MaterialRelated 模块中。
等待 MaterialSpawner 类被自动添加到 MaterialRelated 模块下,这时会收到一条报错,因为 MaterialRelated 模块还没有依赖 MaterialSpawner 所需的 UEditorUtilityWidget 所在模块。
来到 MaterialRelated.Build.cs 中,添加“Blutility”依赖项。
// MaterialRelated.Build.cs
using UnrealBuildTool;
public class MaterialRelated : ModuleRules
{
public MaterialRelated(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
&#34;Core&#34;,
&#34;CoreUObject&#34;,
&#34;Engine&#34;,
&#34;InputCore&#34;,
&#34;Blutility&#34;,
});
PrivateDependencyModuleNames.AddRange(new string[] {});
}
}
重启引擎编辑器并编译,报错就消失了。
创建 MaterialSpawner 资产
打开引擎编辑器,在合适的文件夹中单击右键,选择 Editor Utilites > Editor Utility Widget,创建 EditorUtilityWidget 资产。我们将其命名为“EUW_MaterialSpawner”。
双击打开 EUW_MaterialSpawner,选择菜单 File > Reparent Blueprint,更改父对象。将父对象选择为我们创建的 MaterialSpawner。
编译并保存 EUW_MaterialSpawner。至此我们的准备工作就全部完成了。
{\quad今天的捉弄结束了 \quad }\\
Material 操作
使用 C++ 创建材质
兼用 C++ 与 Blueprint 两者之长是使用 Unreal 引擎的最佳实践。在本章中,我们将在 C++ 中书写所有的逻辑,并在 EUW_MaterialSpawner 中来调用执行它们。
首先来到 MaterialSpawner.h 中,添加可在蓝图中执行的函数“CreateMaterial”。
// MateralSpawner.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;EditorUtilityWidget.h&#34;
#include &#34;MaterialSpawner.generated.h&#34;
UCLASS()
class MATERIALRELATED_API UMaterialSpawner : public UEditorUtilityWidget
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
void CreateMaterial(const FString& AssetName);
};
然后到 MaterialSpawner.cpp 中实现 CreateMaterial。
// MaterialSpawner.cpp
#pragma once
#include &#34;MaterialSpawner.h&#34;
#include &#34;AssetToolsModule.h&#34;
#include &#34;ContentBrowserModule.h&#34;
#include &#34;EditorAssetLibrary.h&#34;
#include &#34;IContentBrowserSingleton.h&#34;
#include &#34;Factories/MaterialFactoryNew.h&#34;
void UMaterialSpawner::CreateMaterial(const FString& AssetName)
{
// 获取选中的路径视图的文件夹
FContentBrowserModule& ContentBrowserModule =
FModuleManager::LoadModuleChecked<FContentBrowserModule>(&#34;ContentBrowser&#34;);
TArray<FString> PathViewFolders;
ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);
// 创建 MaterialAsset
FAssetToolsModule& AssetToolsModule =
FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT(&#34;AssetTools&#34;));
UMaterialFactoryNew* MaterialFactory = NewObject<UMaterialFactoryNew>();
UObject* CreatedObject = AssetToolsModule.Get().CreateAsset(AssetName, PathViewFolders,
UMaterial::StaticClass(), MaterialFactory);
// 保存 MaterialAsset
// FString AssetPath = CreatedObject->GetPathName();
// UEditorAssetLibrary::SaveAsset(AssetPath);
}
最后,来到 MaterialRelated.Build.cs 中,添加相应的模块依赖项。
// MaterialRelated.Build.cs
using UnrealBuildTool;
public class MaterialRelated : ModuleRules
{
public MaterialRelated(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
&#34;Core&#34;,
&#34;CoreUObject&#34;,
&#34;Engine&#34;,
&#34;InputCore&#34;,
&#34;Blutility&#34;,
&#34;UMG&#34;,
&#34;EditorScriptingUtilities&#34;,
&#34;UnrealEd&#34;
});
PrivateDependencyModuleNames.AddRange(new string[] {});
}
}
编译并运行编辑器,双击打开 EUW_MaterialSpawner,在其中添加一个按钮。
来到 Graph 视图中,选中按钮,添加 On Clicked 事件,为事件添加 CreateMaterial 函数。
运行 EUW_MaterialSpawner,点击按钮生成 Material Asset。
{\quad今天的捉弄结束了 \quad }\\
使用 C++ 创建材质实例
创建材质实例,需要为材质实例分配父材质。父材质可以是一个材质(UMaterial),也可以是一个材质实例(UMaterialInstanceConstant),二者都继承自 UMaterialInterface。
首先来到 MaterialSpawner.h 中,添加可在蓝图中执行的函数“CreateMaterialInstance”。
// MaterialSpawner.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;EditorUtilityWidget.h&#34;
#include &#34;MaterialSpawner.generated.h&#34;
UCLASS()
class MATERIALRELATED_API UMaterialSpawner : public UEditorUtilityWidget
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
void CreateMaterial(const FString& AssetName);
UFUNCTION(BlueprintCallable)
void CreateMaterialInstance(UMaterialInterface* ParentMaterial, const FString& AssetName);
};
然后到 MaterialSpawner.cpp 中实现 CreateMaterialInstance。
我们将创建一个材质实例,并在其后为它指定父材质。
// MaterialSpawner.cpp
void UMaterialSpawner::CreateMaterialInstance(UMaterialInterface* ParentMaterial, const FString& AssetName)
{
// 获取选中的路径视图的文件夹
FContentBrowserModule& ContentBrowserModule =
FModuleManager::LoadModuleChecked<FContentBrowserModule>(&#34;ContentBrowser&#34;);
TArray<FString> PathViewFolders;
ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);
// 创建 Material Instance
FAssetToolsModule& AssetToolsModule =
FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT(&#34;AssetTools&#34;));
UMaterialInstanceConstantFactoryNew* MaterialInstanceConstantFactory =
NewObject<UMaterialInstanceConstantFactoryNew>();
UObject* MaterialInstance = AssetToolsModule.Get().CreateAsset(AssetName, PathViewFolders,
UMaterialInstanceConstant::StaticClass(), MaterialInstanceConstantFactory);
// 设置 Parent
if(UMaterialInstanceConstant* CreatedMateialInstance =
Cast<UMaterialInstanceConstant>(MaterialInstance))
{
CreatedMaterialInstance->SetParentEditorOnly(ParentMaterial);
CreatedMaterialInstance->PostEditChange();
ParentMaterial->PostEditChange();
}
}
编译并运行编辑器,双击打开 EUW_MaterialSpawner,在其中添加一个按钮。
来到 Graph 视图中,选中按钮,添加 On Clicked 事件,为事件添加 CreateMaterial 函数。
运行 EUW_MaterialSpawner,点击按钮生成 Material Instance Asset。
{\quad今天的捉弄结束了 \quad }\\
使用 C++ 更改材质参数
接下来探索对材质中已有参数进行修改的方法。我们先准备将要修改的材质和资源。这里准备一张常规颜色图片,和一个普通材质。
在材质蓝图中创建三个不同类型的参数,依此连接。并设置 BlendMode = Opaque,TwoSided = false。
保存并关闭材质蓝图,来到 MaterialSpawner.h 中,添加可在蓝图中执行的函数“SetMaterialParameter”。
// MaterialSpawner.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;EditorUtilityWidget.h&#34;
#include &#34;MaterialSpawner.generated.h&#34;
UCLASS()
class MATERIALRELATED_API UMaterialSpawner : public UEditorUtilityWidget
{
GENERATED_BODY()
...
UFUNCTION(BlueprintCallable)
void SetMaterialParameter();
};
来到 MaterialSpawner.cpp 中实现 SetMaterialParameter。
材质本身的参数(存在于材质蓝图细节面板中的那些参数)一般存在于材质对象中,我们只需获取到材质对象然后直接修改即可。
UMaterial* Material = ***;
Material->BlendMode = EBlendMode::BLEND_Masked;
Parent->TwoSided = true;
...
对于材质蓝图中的参数节点,则可使用下面的方法。
UMaterial* Material = ***;UMaterialInstanceConstant* MaterialInstance = ***;// 修改标量参数UMaterial->SetScalarParameterValueEditorOnly(...);// 修改矢量参数UMaterialInstanceConstant->SetVectorParameterValueEditorOnly(...);// 修改纹理参数UMaterialInstanceConstant->SetTextureParameterValueEditorOnly(...);
详细代码如下。
我们将创建一个名为“MI_SetMaterialParameter”的材质实例。然后从外部导入一个纹理资源和一个材质资产。接着我们将导入的材质指定为 MI_SetMaterialParameter 的父材质,并修改这个父材质的 BlendMode 和 TwoSided 属性。然后修改 MI_SetMaterialParameter 中的材质参数。
// MaterialSpawner.cpp
void UMaterialSpawner::SetMaterialParameter()
{
// 在当前路径视图文件夹中创建 “MI_SetMaterialParameter”
FContentBrowserModule& ContentBrowserModule =
FModuleManager::LoadModuleChecked<FContentBrowserModule>(&#34;ContentBrowser&#34;);
TArray<FString> PathViewFolders;
ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);
FAssetToolsModule& AssetToolsModule =
FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT(&#34;AssetTools&#34;));
UMaterialInstanceConstantFactoryNew* MaterialInstanceConstantFactory =
NewObject<UMaterialInstanceConstantFactoryNew>();
UObject* MaterialInstance =
AssetToolsModule.Get().CreateAsset(&#34;MI_SetMaterialParameter&#34;, PathViewFolders,
UMaterialInstanceConstant::StaticClass(), MaterialInstanceConstantFactory);
// 从外部导入 Texture 资源。这次我们也直接从已有资产获取 Material
UMaterial* Parent =
LoadObject<UMaterial>(nullptr, TEXT(&#34;Material&#39;/Game/Material/M_Parent.M_Parent&#39;&#34;));
UTexture* Texture =
LoadObject<UTexture>(nullptr, TEXT(&#34;Texture2D&#39;/Game/Material/DefaultVolumeTexture2D.DefaultVolumeTexture2D&#39;&#34;));
if(UMaterialInstanceConstant* CreatedMaterialInstance =
Cast<UMaterialInstanceConstant>(MaterialInstance))
{
CreatedMaterialInstance->SetParentEditorOnly(Parent);
CreatedMaterialInstance->PostEditChange();
Parent->PostEditChange();
// 修改 Material 属性
Parent->BlendMode = EBlendMode::BLEND_Masked; // 修改为 Mask 模式
Parent->TwoSided = true; // 设置为双面显示
Parent->PostEditChange();
// 修改 Scale 参数
CreatedMaterialInstance->SetScalarParameterValueEditorOnly(
FMaterialParameterInfo(TEXT(&#34;RoughnessValue&#34;)), 0);
// 修改 Vector 参数
CreatedMaterialInstance->SetVectorParameterValueEditorOnly(
FMaterialParameterInfo(TEXT(&#34;EmissiveColor&#34;)),
FLinearColor(FVector3d(0.2f, 0.0f, 0.0f)));
// 修改 Texture 参数
CreatedMaterialInstance->SetTextureParameterValueEditorOnly(
FMaterialParameterInfo(TEXT(&#34;BaseColorTexture&#34;)),Texture);
CreatedMaterialInstance->PostEditChange();
}
}
编译并运行编辑器,双击打开 EUW_MaterialSpawner,在其中添加一个按钮。
来到 Graph 视图中,选中按钮,添加 On Clicked 事件,为事件添加 SetMaterialParameter 函数。
运行 EUW_MaterialSpawner,点击按钮生成一个已经更改过参数的 Material Instance。
{\quad今天的捉弄结束了 \quad }\\
使用 C++ 编辑材质蓝图
接下来探索在材质蓝图中添加和连接节点的方法。添加和连接节点的步骤一般分为 3 步。
[*]创建一个指定类型的节点。
[*]为创建的节点设置参数。
[*]连接节点输出到其他输入。
来到 MaterialSpawner.h 中,添加可在蓝图中执行的函数“EditMaterial”。
// MaterialSpawner.h
#pragma once
#include &#34;CoreMinimal.h&#34;
#include &#34;EditorUtilityWidget.h&#34;
#include &#34;MaterialSpawner.generated.h&#34;
UCLASS()
class MATERIALRELATED_API UMaterialSpawner : public UEditorUtilityWidget
{
GENERATED_BODY()
...
UFUNCTION(BlueprintCallable)
void EditMaterial(UMaterial* Material);
};
来到 MaterialSpawner.cpp 实现 EditMaterial。
我们首先创建两个普通节点,标量节点“ConstantExpression”和纹理采样节点“TextureExpression”。材质蓝图中的每个节点都有一个与其对应的 C++ 类,可以在 \Runtime\Engine\Classes\Materials\ 文件夹下查看它们。
然后我们创建了一个矢量参数节点“VectorParameter”,以及一个 Multiply 节点“MultiplyExpression”。接着我们将 TextureExpression 连入 Multiply 节点的 A 输入,并将 VectorParameter 的 3 号输出(即 RGBA 中的 B)连入 Multiply 节点的 B 输入。注意,我们有两种方式为 Multiply 节点设置输入。
接着我们设置每个节点在蓝图中的位置。
最后,我们正式将它们添加到材质中,并连接到材质面板的输入端口。
// MaterialSpawner.cpp
#include &#34;Materials/MaterialExpressionConstant.h&#34;
#include &#34;Materials/MaterialExpressionMultiply.h&#34;
#include &#34;Materials/MaterialExpressionScalarParameter.h&#34;
#include &#34;Materials/MaterialExpressionVectorParameter.h&#34;
#include &#34;Materials/MaterialInstanceConstant.h&#34;
#include &#34;Materials/MaterialInstanceDynamic.h&#34;
...
void UMaterialSpawner::EditMaterial(UMaterial* Material)
{
/*
* 创建和设置 Node
*/
// 创建并设置 Constant node
UMaterialExpressionConstant* ConstantExpression = NewObject<UMaterialExpressionConstant>(Material);
ConstantExpression->R = 0.5f;
// 创建并设置 TextureSample node
UMaterialExpressionTextureSample* TextureExpression =
NewObject<span class="o"><UMaterialExpressionTextureSample>(Material);
TextureExpression->Texture =
LoadObject<UTexture>(
nullptr,
TEXT(&#34;Texture2D&#39;/Game/Material/DefaultVolumeTexture2D.DefaultVolumeTexture2D&#39;&#34;));
// 创建并设置 Constant parameter node
UMaterialExpressionVectorParameter* VectorParameter =
NewObject<UMaterialExpressionVectorParameter>(Material);
VectorParameter->ParameterName = TEXT(&#34;VectorParameter&#34;);
VectorParameter->DefaultValue = FLinearColor(FVector3d(0.2f, 0.0f, 0.5f));
// 准备 FExpressionInput,稍后赋值给 MultiplyExpression 的 A 输入
FExpressionInput TextureExpressionInput;
TextureExpressionInput.Expression = TextureExpression;
// 创建并设置 Multiply node
UMaterialExpressionMultiply* MultiplyExpression = NewObject<UMaterialExpressionMultiply>(Material);
MultiplyExpression->A = TextureExpressionInput;
// 用这种方式可以选择要连接的输出编号
MultiplyExpression->B.Connect(3, VectorParameter);
/*
* 设置 node position
*/
TextureExpression->MaterialExpressionEditorX = -600;
TextureExpression->MaterialExpressionEditorY = 0;
VectorParameter->MaterialExpressionEditorX = -600;
VectorParameter->MaterialExpressionEditorY = 240;
MultiplyExpression->MaterialExpressionEditorX = -300;
MultiplyExpression->MaterialExpressionEditorY = 0;
ConstantExpression->MaterialExpressionEditorX = -300;
ConstantExpression->MaterialExpressionEditorY = 240;
/*
* 将所有 Node 添加到材质,并连接材质输出
*/
Material->Expressions.Add(ConstantExpression);
Material->Expressions.Add(TextureExpression);
Material->Expressions.Add(MultiplyExpression);
Material->Expressions.Add(VectorParameter);
Material->BaseColor.Expression = MultiplyExpression;
Material->BaseColor.Mask = 0;
Material->Metallic.Expression = ConstantExpression;
// 用这种方式可以选择要连接的输出编号
Material->Roughness.Connect(2, VectorParameter);
Material->PostEditChange();
Material->MarkPackageDirty();
}
编译并运行编辑器,添加一个默认材质。
双击打开 EUW_MaterialSpawner,在其中添加一个按钮。
来到 Graph 视图中,选中按钮,添加 On Clicked 事件,为事件添加 EditMaterial 函数。并将 Material 参数设置为刚才创建好的默认材质。
运行 EUW_MaterialSpawner,点击按钮更改默认材质。
{\quad今天的捉弄结束了 \quad }\\
Material 拓展
使用 C++ 在材质蓝图中添加材质函数
使用 C++ 在材质蓝图中添加材质参数集
使用 C++ 拓展 Nodes
come soon ...
页:
[1]