maltadirk 发表于 2023-3-3 10:22

《调教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 "Modules/ModuleInterface.h"

class FMaterialRelated : public IModuleInterface
{
public:
       
        virtual void StartupModule() override;
        virtual void ShutdownModule() override;
        virtual ~FMaterialRelated() {}
};

在 MaterialRelated.cpp 中实现模块类,设置它的名称为“MaterialRelated”。
// MaterialRelated.cpp

#pragma once

#include "MaterialRelated.h"

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[]
                {
                        "Core",
                        "CoreUObject",
                        "Engine",
                        "InputCore",
                });

                PrivateDependencyModuleNames.AddRange(new string[] {});
        }
}

我们的项目名称为“ExtendEditor”,打开“ExtendEditor.uproject”,添加模块加载方式。
// ExtendEditor.uproject

{
        "FileVersion": 3,
        "EngineAssociation": "5.0",
        "Category": "",
        "Description": "",
        "Modules": [
                {
                        "Name": "ExtendEditor",
                        "Type": "Runtime",
                        "LoadingPhase": "Default",
                        "AdditionalDependencies": [
                                "Blutility"
                        ]
                },

                ...

                {
                        "Name": "MaterialRelated",
                        "Type": "Editor",
                        "LoadingPhase": "Default",
                }
        ],
...

打开“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[]
                {
                        "ExtendEditor",

                        ...

                        "MaterialRelated"
                } );
        }
}

重启代码编辑器并编译,完成“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[]
                {
                        "Core",
                        "CoreUObject",
                        "Engine",
                        "InputCore",
                        "Blutility",
                });

                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 "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "MaterialSpawner.generated.h"


UCLASS()
class MATERIALRELATED_API UMaterialSpawner : public UEditorUtilityWidget
{
        GENERATED_BODY()

        UFUNCTION(BlueprintCallable)
        void CreateMaterial(const FString& AssetName);
};

然后到 MaterialSpawner.cpp 中实现 CreateMaterial。
// MaterialSpawner.cpp

#pragma once

#include "MaterialSpawner.h"

#include "AssetToolsModule.h"
#include "ContentBrowserModule.h"
#include "EditorAssetLibrary.h"
#include "IContentBrowserSingleton.h"
#include "Factories/MaterialFactoryNew.h"


void UMaterialSpawner::CreateMaterial(const FString& AssetName)
{
        // 获取选中的路径视图的文件夹
        FContentBrowserModule& ContentBrowserModule =
                FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");

        TArray<FString> PathViewFolders;
        ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);

        // 创建 MaterialAsset
        FAssetToolsModule& AssetToolsModule =
                FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));

        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[]
                {
                        "Core",
                        "CoreUObject",
                        "Engine",
                        "InputCore",
                        "Blutility",
                        "UMG",
                        "EditorScriptingUtilities",
                        "UnrealEd"
                });

                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 "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "MaterialSpawner.generated.h"


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>("ContentBrowser");

        TArray<FString> PathViewFolders;
        ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);

        // 创建 Material Instance
        FAssetToolsModule& AssetToolsModule =
                FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));

        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 "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "MaterialSpawner.generated.h"


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>("ContentBrowser");
       
        TArray<FString> PathViewFolders;
        ContentBrowserModule.Get().GetSelectedPathViewFolders(PathViewFolders);
       
        FAssetToolsModule& AssetToolsModule =
                FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
       
        UMaterialInstanceConstantFactoryNew* MaterialInstanceConstantFactory =
                NewObject<UMaterialInstanceConstantFactoryNew>();
       
        UObject* MaterialInstance =
                AssetToolsModule.Get().CreateAsset("MI_SetMaterialParameter", PathViewFolders,
                UMaterialInstanceConstant::StaticClass(), MaterialInstanceConstantFactory);

        // 从外部导入 Texture 资源。这次我们也直接从已有资产获取 Material
        UMaterial* Parent =
                LoadObject<UMaterial>(nullptr, TEXT("Material'/Game/Material/M_Parent.M_Parent'"));
        UTexture* Texture =
                LoadObject<UTexture>(nullptr, TEXT("Texture2D'/Game/Material/DefaultVolumeTexture2D.DefaultVolumeTexture2D'"));

        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("RoughnessValue")), 0);
                // 修改 Vector 参数
                CreatedMaterialInstance->SetVectorParameterValueEditorOnly(
                        FMaterialParameterInfo(TEXT("EmissiveColor")),
                        FLinearColor(FVector3d(0.2f, 0.0f, 0.0f)));
                // 修改 Texture 参数
                CreatedMaterialInstance->SetTextureParameterValueEditorOnly(
                        FMaterialParameterInfo(TEXT("BaseColorTexture")),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 "CoreMinimal.h"
#include "EditorUtilityWidget.h"
#include "MaterialSpawner.generated.h"


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 "Materials/MaterialExpressionConstant.h"
#include "Materials/MaterialExpressionMultiply.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialInstanceDynamic.h"

...

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("Texture2D'/Game/Material/DefaultVolumeTexture2D.DefaultVolumeTexture2D'"));
       
        // 创建并设置 Constant parameter node
        UMaterialExpressionVectorParameter* VectorParameter =
                NewObject<UMaterialExpressionVectorParameter>(Material);
        VectorParameter->ParameterName = TEXT("VectorParameter");
        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]
查看完整版本: 《调教UE5:编辑器拓展指南》Material 相关基础