LOADING

UE5 自定义材质编辑器的节点

基础实现部分


代码里面有注释,直接看注释吧,我就不多解释啦。

UE5 自带Name节点,可以实现同样的效果,所以仅供学习使用。

UE4的话可以这么加,代码修改几乎一致。

H头文件:
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "MaterialExpressionLocalVariable.generated.h"
/**
 * 
 */
UCLASS(collapsecategories, hidecategories = Object, MinimalAPI)
class UMaterialExpressionLocalVariable : public UMaterialExpression
{
    GENERATED_UCLASS_BODY()
    
    UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Local Variable"))
    FExpressionInput VarStore;

    UPROPERTY(EditAnywhere, Category=MaterialExpressionLocalVariable)
    FString VariableKeyWord;
 
    //保存临时Input
    FExpressionInput VarStoreCache;
#if WITH_EDITOR
    //材质编辑器内,按下Apply执行
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
    //节点的名字
    virtual void GetCaption(TArray<FString>& OutCaptions) const override;
    //检查节点是否初始化
    bool IsLoaclVariableWasInitialized();
#endif
};
CPP源文件
#include "MaterialExpressionLocalVariable.h"
#include "EditorSupportDelegates.h"
#include "MaterialCompiler.h"
#if WITH_EDITOR
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "MaterialGraph/MaterialGraphNode_Composite.h"
#endif //WITH_EDITOR
#include "Interfaces/ITargetPlatform.h"
#define LOCTEXT_NAMESPACE "MaterialExpression"

UMaterialExpressionLocalVariable::UMaterialExpressionLocalVariable(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
	struct FConstructorStatics
	{
		FConstructorStatics(){}
	};
	static FConstructorStatics ConstructorStatics;
	bCollapsed = true;
#endif
	VariableKeyWord = TEXT("None");
}

#if WITH_EDITOR
int32 UMaterialExpressionLocalVariable::Compile(FMaterialCompiler* Compiler, int32 OutputIndex)
{
    //是否连接了Input
    if(!VarStore.GetTracedInput().Expression)
    {
        //检查该局部节点是否已经初始化了
        if(!IsLoaclVariableWasInitialized())
        {
            return Compiler->Errorf(TEXT("该参数还未初始化!请给一个Store值"));
        }
        //已经初始化了,复制初始化的Input属性,强转成一个新的float4,这样就不会出现自动连线的问题了。
        else
        {
            return VarStoreCache.Compile(Compiler);
        }
    }
    return INDEX_NONE;
}

void UMaterialExpressionLocalVariable::GetCaption(TArray<FString>& OutCaptions) const
{
    //节点的名字显示当前我们自定义的局部变量的名字
    FString keyword = TEXT("Local Variable : ");
    keyword.Append(VariableKeyWord);
    OutCaptions.Add(keyword);
}

bool UMaterialExpressionLocalVariable::IsLoaclVariableWasInitialized()
{
    if(Material)
    {
        //在当前材质球里面查找所有节点,获取到我们的LocalVariable节点
        for (int32 ExpIndex = 0; ExpIndex < Material->Expressions.Num(); ExpIndex++)
        {
            const UMaterialExpressionLocalVariable* DynParam = Cast<UMaterialExpressionLocalVariable>(Material->Expressions[ExpIndex]);
            //根据我们的keyword判断是否为同一个Keyword
            if (DynParam && (DynParam != this)
                &&VariableKeyWord.Compare(DynParam->VariableKeyWord) == 0	//检查keyword是否一致
                && DynParam->VarStore.GetTracedInput().Expression			//检查该节点是否连接了Input,True就获取
                )
            {
                VarStoreCache = DynParam->VarStore;
                return true;
            }
        }
    }
    return false;
}
#endif
效果:
使用起来非常简单

局部节点的输出必须一 一对应,作为输出节点可以不需要连接Input:

当出现了不存在的局部节点的时候就会报错:

因为keyword的判断是写在Compile函数里的,所以如果复制了一个新的局部节点,需要点击一次编译按钮,让材质编辑器跑一次Compile函数才能正常运行,当然可以加在其他地方,各位感兴趣的可以自行试试。

参数显示问题


如果你已经做完上面一步了,节点也正式开始使用了,你就会发现一个问题,材质球里面创建了动态参数:

但是材质实例里无法显示没有连接到Output的参数!!

材质实例编辑器就设定就是获取已经连接到Output的节点参数,所以这里我们需要修改一下引擎编辑器的源码
找到 MaterialEditorUtilities.cpp 这个源文件
找到这个函数:
void FMaterialEditorUtilities::GetVisibleMaterialParameters
我们在这里再补充遍历材质球里的所有节点进行获取:

这样就可以获取到连接到每一个节点上的动态参数了,
但是这里我其实推荐是把这个我们自定义的节点写入到引擎源码中,而不是写入到项目或者插件中,这样我们可以单独的为这个节点类型进行判断了,而不是所有节点。

看看结果如何,可以看到参数出来了:

蓝图控制参数变化也能正常运行: