Unreal – Material Blueprint Node Source Code Implementation Analysis

engine: UE4.26.2 build version

The cause of the matter is that when I was writing the voxel builder, I wanted to generate a ball, so I thought of copying the SphereMask node of the material.

Here we take the SphereMask node as an example to analyze the definition of the material blueprint node.

UMaterialExpression

The C++ parent class of material nodes is UMaterialExpression, which is defined in the MaterialExpression.h header file. All native material nodes are subclasses of this class. The main members of this class are as follows.

virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex);

Compile node, used to generate corresponding HLSL code, requires subclass coverage.

virtual void GetCaption(TArray<FString>& OutCaptions) const;
virtual FText GetKeywords() const;

Get the title and keywords of the node and selectively cover them.

virtual TArray<FExpressionOutput>& GetOutputs();
virtual const TArray<FExpressionInput*> GetInputs();
virtual FExpressionInput* GetInput(int32 InputIndex);
virtual FName GetInputName(int32 InputIndex) const;

Used for the system to obtain input and output information of nodes, with a default implementation of obtaining information through reflection, and selective coverage.

UMaterialExpressionSphereMask

UMaterialExpressionSphereMask is a direct subclass of UMaterialExpression, defined in MaterialExpressionSphereMask.h but shares the implementation file with other system nodes, located in MaterialExpressions.cpp.

Input and output pin definitions

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;

Parameter default definition

When the Radius and Hardness pins are not connected, the default defined constants are used.

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;

Compile function implementation

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);
    }
}

The most important function is used to generate the HLSL code of the SphereMask node. The two judgment branches at the beginning check whether the A and B inputs exist. If they do not exist, the compilation will be terminated and an error message will be output. After the checks are correct, the code will be generated. There are two The judgment is to check whether the Radius and Hardness need to use the default options. Next, enter the UV according to A, B is a fixed value of 0.5, the default value of Radius is 0.25, and the default value of Hardness is 0.0 for analysis.

The generated HLSL code is:

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);

Corresponding to C++ code analysis:

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);

Easy to get, the material compiler uses int32 to store the index of the HLSL variable, and generates HLSL code according to the calling order.

Classes and functions

  • FExpressionInput – used to define material node input
  • FExpressionInput::Compile – compile input
  • FMaterialCompiler – material compiler
  • FMaterialCompiler::Add – Add compilation
  • FMaterialCompiler::Sub – subtractive compilation
  • FMaterialCompiler::Mul – multiplication compilation
  • FMaterialCompiler::Div – except compilation
  • CompileHelperLength – Compile with length
  • CompileHelperSaturate – Clamp compilation

Post Reply