基础实现部分
代码里面有注释,直接看注释吧,我就不多解释啦。
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
我们在这里再补充遍历材质球里的所有节点进行获取:

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

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

