引擎: UE4.26.2 构建版本
事情的起因是写体素构建器的时候,想生成个球,就想到抄材质的 SphereMask 节点。
这里以 SphereMask 节点为例,分析材质蓝图节点的定义。
UMaterialExpression
材质节点的 C++ 父类为 UMaterialExpression ,定义在 MaterialExpression.h 头文件中,所有原生材质节点均为此类的子类,此类主要成员如下。
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex);
编译节点,用于生成对应的 HLSL 代码,需要子类覆盖。
virtual void GetCaption(TArray<FString>& OutCaptions) const;
virtual FText GetKeywords() const;
获取节点的标题和关键词,选择性覆盖。
virtual TArray<FExpressionOutput>& GetOutputs();
virtual const TArray<FExpressionInput*> GetInputs();
virtual FExpressionInput* GetInput(int32 InputIndex);
virtual FName GetInputName(int32 InputIndex) const;
用于系统获取节点的输入输出信息,具有通过反射获取信息的默认实现,选择性覆盖。
UMaterialExpressionSphereMask
UMaterialExpressionSphereMask 是 UMaterialExpression 的直接子类,定义于 MaterialExpressionSphereMask.h 但是与其他系统节点共享实现文件,位于 MaterialExpressions.cpp 。
输入输出引脚定义
UPROPERTY()
FExpressionInput A;
UPROPERTY()
FExpressionInput B;
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'AttenuationRadius' if not specified"))
FExpressionInput Radius;
UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'HardnessPercent' if not specified"))
FExpressionInput Hardness;
参数缺省定义
在 Radius 和 Hardness 引脚没有连接时,使用缺省定义的常量。
UPROPERTY(EditAnywhere, Category=MaterialExpressionSphereMask, meta=(OverridingInputProperty = "Radius"), DisplayName = "Radius")
float AttenuationRadius;
UPROPERTY(EditAnywhere, Category=MaterialExpressionSphereMask, meta=(UIMin = "0.0", UIMax = "100.0", ClampMin = "0.0", ClampMax = "100.0", OverridingInputProperty = "Hardness", DisplayName = "Hardness"))
float HardnessPercent;
编译函数实现
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override
{
if(!A.GetTracedInput().Expression)
{
return Compiler->Errorf(TEXT("Missing input A"));
}
else if(!B.GetTracedInput().Expression)
{
return Compiler->Errorf(TEXT("Missing input B"));
}
else
{
int32 Arg1 = A.Compile(Compiler);
int32 Arg2 = B.Compile(Compiler);
int32 Distance = CompileHelperLength(Compiler, Arg1, Arg2);
int32 ArgInvRadius;
if(Radius.GetTracedInput().Expression)
{
ArgInvRadius = Compiler->Div(Compiler->Constant(1.0f), Compiler->Max(Compiler->Constant(0.00001f), Radius.Compile(Compiler)));
}
else
{
ArgInvRadius = Compiler->Constant(1.0f / FMath::Max(0.00001f, AttenuationRadius));
}
int32 NormalizeDistance = Compiler->Mul(Distance, ArgInvRadius);
int32 ArgInvHardness;
if(Hardness.GetTracedInput().Expression)
{
int32 Softness = Compiler->Sub(Compiler->Constant(1.0f), Hardness.Compile(Compiler));
ArgInvHardness = Compiler->Div(Compiler->Constant(1.0f), Compiler->Max(Softness, Compiler->Constant(0.00001f)));
}
else
{
float InvHardness = 1.0f / FMath::Max(1.0f - HardnessPercent * 0.01f, 0.00001f);
ArgInvHardness = Compiler->Constant(InvHardness);
}
int32 NegNormalizedDistance = Compiler->Sub(Compiler->Constant(1.0f), NormalizeDistance);
int32 MaskUnclamped = Compiler->Mul(NegNormalizedDistance, ArgInvHardness);
return CompileHelperSaturate(Compiler, MaskUnclamped);
}
}
最重要的函数,用于生产 SphereMask 节点的 HLSL 代码,开头的两个判断分支检查 A 和 B 输入是否存在,不存在则会终止编译并输出错误信息,检查无误后开始生成代码,其中有两个判断是检查 Radius 和 Hardness 是否需要使用缺省选项,下面按照 A 输入 UV , B 为 0.5 定值, Radius 缺省为 0.25 , Hardness 缺省为 0.0 分析。
生成的 HLSL 代码为:
MaterialFloat2 Local0 = (Parameters.TexCoords[0].xy - 0.50000000);
MaterialFloat Local1 = dot(Local0, Local0);
MaterialFloat Local2 = sqrt(Local1);
MaterialFloat Local3 = (Local2 * 4.00000000);
MaterialFloat Local4 = (1.00000000 - Local3);
MaterialFloat Local5 = (Local4 * 1.00000000);
MaterialFloat Local6 = min(max(Local5,0.00000000),1.00000000);
对应 C++ 代码分析:
int32 Distance = CompileHelperLength(Compiler, Arg1, Arg2);
MaterialFloat2 Local0 = (Parameters.TexCoords[0].xy - 0.50000000);
MaterialFloat Local1 = dot(Local0, Local0);
MaterialFloat Local2 = sqrt(Local1);
int32 NormalizeDistance = Compiler->Mul(Distance, ArgInvRadius);
MaterialFloat Local3 = (Local2 * 4.00000000);
int32 NegNormalizedDistance = Compiler->Sub(Compiler->Constant(1.0f), NormalizeDistance);
MaterialFloat Local4 = (1.00000000 - Local3);
int32 MaskUnclamped = Compiler->Mul(NegNormalizedDistance, ArgInvHardness);
MaterialFloat Local5 = (Local4 * 1.00000000);
return CompileHelperSaturate(Compiler, MaskUnclamped);
MaterialFloat Local6 = min(max(Local5,0.00000000),1.00000000);
易得,材质编译器用 int32 储存 HLSL 变量的索引,按照调用顺序生成 HLSL 代码。
类与函数
- FExpressionInput – 用于定义材质节点输入
- FExpressionInput::Compile – 编译输入
- FMaterialCompiler – 材质编译器
- FMaterialCompiler::Add – 加编译
- FMaterialCompiler::Sub – 减编译
- FMaterialCompiler::Mul – 乘编译
- FMaterialCompiler::Div – 除编译
- CompileHelperLength – 取长度编译
- CompileHelperSaturate – Clamp 编译