虚幻4的CustomNode允许我们再材质编辑器里直接进行shader代码的编写(它的原理可以看我之前的文章)
但是它有一个缺陷,那就是无法进行函数调用。下面我们就来实现一个可以进行函数调用的CustomNode。
先看一下我的实现效果吧:
第一个为主函数,后面的为我们自己定义的可调用函数。那么现在就来一步一步修改引擎,来做出我们这个自定义的Cutom节点吧。
首先建两个文件Engine/Classes/Materials/MyCustomNode.h和Engine/Private/Materials/MyCustomNode.cpp建好之后Generate一下引擎目录。
头文件MyCustomNode.h的代码如下
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionCustom.h"
#include "MyCustomNode.generated.h"
USTRUCT()
struct FHLSLFunctions
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom, meta = (MultiLine = false))
FString FunctionName;
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom, meta = (MultiLine = true))
FString FunctionCodes;
};
/**
*
*/
UCLASS(collapsecategories, hidecategories = Object, MinimalAPI)
class UMyCustomNode : public UMaterialExpression
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom, meta = (MultiLine = true))
TArray<FHLSLFunctions> HLSLFunctions;
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom)
TEnumAsByte<enum ECustomMaterialOutputType> OutputType;
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom)
FString Description;
UPROPERTY(EditAnywhere, Category = MaterialExpressionCustom)
TArray<struct FCustomInput> Inputs;
//~ Begin UObject Interface.
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif // WITH_EDITOR
virtual void Serialize(FArchive& Ar) override;
//~ End UObject Interface.
//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
virtual const TArray<FExpressionInput*> GetInputs() override;
virtual FExpressionInput* GetInput(int32 InputIndex) override;
virtual FName GetInputName(int32 InputIndex) const override;
#if WITH_EDITOR
virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Unknown; }
virtual uint32 GetOutputType(int32 OutputIndex) override;
#endif // WITH_EDITOR
//~ End UMaterialExpression Interface
};
MyCustomNode.cpp代码如下:
// Fill out your copyright notice in the Description page of Project Settings.
#include "Materials/MyCustomNode.h"
#include "CoreMinimal.h"
#include "Misc/MessageDialog.h"
#include "Misc/Guid.h"
#include "UObject/RenderingObjectVersion.h"
#include "Misc/App.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "UObject/UnrealType.h"
#include "UObject/UObjectAnnotation.h"
#include "UObject/ConstructorHelpers.h"
#include "EngineGlobals.h"
#include "Materials/MaterialInterface.h"
#include "Engine/Engine.h"
#include "Engine/Font.h"
#include "MaterialShared.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionMaterialFunctionCall.h"
#include "Materials/MaterialExpressionMaterialAttributeLayers.h"
#include "Materials/MaterialFunctionInterface.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialFunctionMaterialLayer.h"
#include "Materials/MaterialFunctionMaterialLayerBlend.h"
#include "Materials/MaterialFunctionInstance.h"
#include "Materials/Material.h"
#include "Engine/Texture2D.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/Texture2DDynamic.h"
#include "Engine/TextureCube.h"
#include "Engine/TextureRenderTargetCube.h"
#include "Styling/CoreStyle.h"
#include "EditorSupportDelegates.h"
#include "MaterialCompiler.h"
#if WITH_EDITOR
#include "MaterialGraph/MaterialGraphNode_Comment.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#endif //WITH_EDITOR
#include "Materials/MaterialInstanceConstant.h"
#define LOCTEXT_NAMESPACE "MyMaterialExpression"
///
// UMaterialExpressionCustom
///
UMyCustomNode::UMyCustomNode(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
FText NAME_Custom;
FConstructorStatics()
: NAME_Custom(LOCTEXT("MyCustom", "MyCustom"))
{
}
};
static FConstructorStatics ConstructorStatics;
Description = TEXT("MyCustom");
FHLSLFunctions InitDefualt;
InitDefualt.FunctionName = TEXT("This is main function,you need wirte you main function code here");
InitDefualt.FunctionCodes = TEXT("return half3(1,1,1);");
HLSLFunctions.Add(InitDefualt);
#if WITH_EDITORONLY_DATA
MenuCategories.Add(ConstructorStatics.NAME_Custom);
#endif
OutputType = CMOT_Float3;
Inputs.Add(FCustomInput());
Inputs[0].InputName = TEXT("");
bCollapsed = false;
}
#if WITH_EDITOR
int32 UMyCustomNode::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
TArray<int32> CompiledInputs;
for (int32 i = 0; i < Inputs.Num(); i++)
{
// skip over unnamed inputs
if (Inputs[i].InputName.IsNone())
{
CompiledInputs.Add(INDEX_NONE);
}
else
{
if (!Inputs[i].Input.GetTracedInput().Expression)
{
return Compiler->Errorf(TEXT("Custom material %s missing input %d (%s)"), *Description, i + 1, *Inputs[i].InputName.ToString());
}
int32 InputCode = Inputs[i].Input.Compile(Compiler);
if (InputCode < 0)
{
return InputCode;
}
CompiledInputs.Add(InputCode);
}
}
return Compiler->MyCustomExpression(this, CompiledInputs);
}
void UMyCustomNode::GetCaption(TArray<FString>& OutCaptions) const
{
OutCaptions.Add(Description);
}
#endif // WITH_EDITOR
const TArray<FExpressionInput*> UMyCustomNode::GetInputs()
{
TArray<FExpressionInput*> Result;
for (int32 i = 0; i < Inputs.Num(); i++)
{
Result.Add(&Inputs[i].Input);
}
return Result;
}
FExpressionInput* UMyCustomNode::GetInput(int32 InputIndex)
{
if (InputIndex < Inputs.Num())
{
return &Inputs[InputIndex].Input;
}
return NULL;
}
FName UMyCustomNode::GetInputName(int32 InputIndex) const
{
if (InputIndex < Inputs.Num())
{
return Inputs[InputIndex].InputName;
}
return NAME_None;
}
#if WITH_EDITOR
void UMyCustomNode::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// strip any spaces from input name
UProperty* PropertyThatChanged = PropertyChangedEvent.Property;
if (PropertyThatChanged && PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(FCustomInput, InputName))
{
for (FCustomInput& Input : Inputs)
{
FString InputName = Input.InputName.ToString();
if (InputName.ReplaceInline(TEXT(" "), TEXT("")) > 0)
{
Input.InputName = *InputName;
}
}
}
if (PropertyChangedEvent.MemberProperty && GraphNode)
{
const FName PropertyName = PropertyChangedEvent.MemberProperty->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMyCustomNode, Inputs))
{
GraphNode->ReconstructNode();
}
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
uint32 UMyCustomNode::GetOutputType(int32 OutputIndex)
{
switch (OutputType)
{
case CMOT_Float1:
return MCT_Float;
case CMOT_Float2:
return MCT_Float2;
case CMOT_Float3:
return MCT_Float3;
case CMOT_Float4:
return MCT_Float4;
default:
return MCT_Unknown;
}
}
#endif // WITH_EDITOR
void UMyCustomNode::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FRenderingObjectVersion::GUID);
// Make a copy of the current code before we change it
const FString PreFixUp = HLSLFunctions[0].FunctionCodes;
bool bDidUpdate = false;
if (Ar.UE4Ver() < VER_UE4_INSTANCED_STEREO_UNIFORM_UPDATE)
{
// Look for WorldPosition rename
if (HLSLFunctions[0].FunctionCodes.ReplaceInline(TEXT("Parameters.WorldPosition"), TEXT("Parameters.AbsoluteWorldPosition"), ESearchCase::CaseSensitive) > 0)
{
bDidUpdate = true;
}
}
// Fix up uniform references that were moved from View to Frame as part of the instanced stereo implementation
else if (Ar.UE4Ver() < VER_UE4_INSTANCED_STEREO_UNIFORM_REFACTOR)
{
// Uniform members that were moved from View to Frame
static const FString UniformMembers[] = {
FString(TEXT("FieldOfViewWideAngles")),
FString(TEXT("PrevFieldOfViewWideAngles")),
FString(TEXT("ViewRectMin")),
FString(TEXT("ViewSizeAndInvSize")),
FString(TEXT("BufferSizeAndInvSize")),
FString(TEXT("ExposureScale")),
FString(TEXT("DiffuseOverrideParameter")),
FString(TEXT("SpecularOverrideParameter")),
FString(TEXT("NormalOverrideParameter")),
FString(TEXT("RoughnessOverrideParameter")),
FString(TEXT("PrevFrameGameTime")),
FString(TEXT("PrevFrameRealTime")),
FString(TEXT("OutOfBoundsMask")),
FString(TEXT("WorldCameraMovementSinceLastFrame")),
FString(TEXT("CullingSign")),
FString(TEXT("NearPlane")),
FString(TEXT("AdaptiveTessellationFactor")),
FString(TEXT("GameTime")),
FString(TEXT("RealTime")),
FString(TEXT("Random")),
FString(TEXT("FrameNumber")),
FString(TEXT("CameraCut")),
FString(TEXT("UseLightmaps")),
FString(TEXT("UnlitViewmodeMask")),
FString(TEXT("DirectionalLightColor")),
FString(TEXT("DirectionalLightDirection")),
FString(TEXT("DirectionalLightShadowTransition")),
FString(TEXT("DirectionalLightShadowSize")),
FString(TEXT("DirectionalLightScreenToShadow")),
FString(TEXT("DirectionalLightShadowDistances")),
FString(TEXT("UpperSkyColor")),
FString(TEXT("LowerSkyColor")),
FString(TEXT("TranslucencyLightingVolumeMin")),
FString(TEXT("TranslucencyLightingVolumeInvSize")),
FString(TEXT("TemporalAAParams")),
FString(TEXT("CircleDOFParams")),
FString(TEXT("DepthOfFieldFocalDistance")),
FString(TEXT("DepthOfFieldScale")),
FString(TEXT("DepthOfFieldFocalLength")),
FString(TEXT("DepthOfFieldFocalRegion")),
FString(TEXT("DepthOfFieldNearTransitionRegion")),
FString(TEXT("DepthOfFieldFarTransitionRegion")),
FString(TEXT("MotionBlurNormalizedToPixel")),
FString(TEXT("GeneralPurposeTweak")),
FString(TEXT("DemosaicVposOffset")),
FString(TEXT("IndirectLightingColorScale")),
FString(TEXT("HDR32bppEncodingMode")),
FString(TEXT("AtmosphericFogSunDirection")),
FString(TEXT("AtmosphericFogSunPower")),
FString(TEXT("AtmosphericFogPower")),
FString(TEXT("AtmosphericFogDensityScale")),
FString(TEXT("AtmosphericFogDensityOffset")),
FString(TEXT("AtmosphericFogGroundOffset")),
FString(TEXT("AtmosphericFogDistanceScale")),
FString(TEXT("AtmosphericFogAltitudeScale")),
FString(TEXT("AtmosphericFogHeightScaleRayleigh")),
FString(TEXT("AtmosphericFogStartDistance")),
FString(TEXT("AtmosphericFogDistanceOffset")),
FString(TEXT("AtmosphericFogSunDiscScale")),
FString(TEXT("AtmosphericFogRenderMask")),
FString(TEXT("AtmosphericFogInscatterAltitudeSampleNum")),
FString(TEXT("AtmosphericFogSunColor")),
FString(TEXT("AmbientCubemapTint")),
FString(TEXT("AmbientCubemapIntensity")),
FString(TEXT("RenderTargetSize")),
FString(TEXT("SkyLightParameters")),
FString(TEXT("SceneFString(TEXTureMinMax")),
FString(TEXT("SkyLightColor")),
FString(TEXT("SkyIrradianceEnvironmentMap")),
FString(TEXT("MobilePreviewMode")),
FString(TEXT("HMDEyePaddingOffset")),
FString(TEXT("DirectionalLightShadowFString(TEXTure")),
FString(TEXT("SamplerState")),
};
const FString ViewUniformName(TEXT("View."));
const FString FrameUniformName(TEXT("Frame."));
for (const FString& Member : UniformMembers)
{
const FString SearchString = FrameUniformName + Member;
const FString ReplaceString = ViewUniformName + Member;
if (HLSLFunctions[0].FunctionCodes.ReplaceInline(*SearchString, *ReplaceString, ESearchCase::CaseSensitive) > 0)
{
bDidUpdate = true;
}
}
}
if (Ar.CustomVer(FRenderingObjectVersion::GUID) < FRenderingObjectVersion::RemovedRenderTargetSize)
{
if (HLSLFunctions[0].FunctionCodes.ReplaceInline(TEXT("View.RenderTargetSize"), TEXT("View.BufferSizeAndInvSize.xy"), ESearchCase::CaseSensitive) > 0)
{
bDidUpdate = true;
}
}
// If we made changes, copy the original into the description just in case
if (bDidUpdate)
{
Desc += TEXT("\n*** Original source before expression upgrade ***\n");
Desc += PreFixUp;
UE_LOG(LogMaterial, Log, TEXT("Uniform references updated for Mycustom material expression %s."), *Description);
}
}
做完这两件事情后,打开HLSLMaterialTranslator.h
在virtual int32 CustomExpression( class UMaterialExpressionCustom* Custom, TArray<int32>& CompiledInputs ) override函数后面添加如下代码:
virtual int32 MyCustomExpression(class UMyCustomNode* Custom, TArray<int32>& CompiledInputs) override
{
int32 ResultIdx = INDEX_NONE;
FString OutputTypeString;
EMaterialValueType OutputType;
switch (Custom->OutputType)
{
case CMOT_Float2:
OutputType = MCT_Float2;
OutputTypeString = TEXT("MaterialFloat2");
break;
case CMOT_Float3:
OutputType = MCT_Float3;
OutputTypeString = TEXT("MaterialFloat3");
break;
case CMOT_Float4:
OutputType = MCT_Float4;
OutputTypeString = TEXT("MaterialFloat4");
break;
default:
OutputType = MCT_Float;
OutputTypeString = TEXT("MaterialFloat");
break;
}
// Declare implementation function
FString InputParamDecl;
check(Custom->Inputs.Num() == CompiledInputs.Num());
for (int32 i = 0; i < Custom->Inputs.Num(); i++)
{
// skip over unnamed inputs
if (Custom->Inputs[i].InputName.IsNone())
{
continue;
}
InputParamDecl += TEXT(",");
const FString InputNameStr = Custom->Inputs[i].InputName.ToString();
switch (GetParameterType(CompiledInputs[i]))
{
case MCT_Float:
case MCT_Float1:
InputParamDecl += TEXT("MaterialFloat ");
InputParamDecl += InputNameStr;
break;
case MCT_Float2:
InputParamDecl += TEXT("MaterialFloat2 ");
InputParamDecl += InputNameStr;
break;
case MCT_Float3:
InputParamDecl += TEXT("MaterialFloat3 ");
InputParamDecl += InputNameStr;
break;
case MCT_Float4:
InputParamDecl += TEXT("MaterialFloat4 ");
InputParamDecl += InputNameStr;
break;
case MCT_Texture2D:
InputParamDecl += TEXT("Texture2D ");
InputParamDecl += InputNameStr;
InputParamDecl += TEXT(", SamplerState ");
InputParamDecl += InputNameStr;
InputParamDecl += TEXT("Sampler ");
break;
case MCT_TextureCube:
InputParamDecl += TEXT("TextureCube ");
InputParamDecl += InputNameStr;
InputParamDecl += TEXT(", SamplerState ");
InputParamDecl += InputNameStr;
InputParamDecl += TEXT("Sampler ");
break;
default:
return Errorf(TEXT("Bad type %s for %s input %s"), DescribeType(GetParameterType(CompiledInputs[i])), *Custom->Description, *InputNameStr);
break;
}
}
int32 CustomExpressionIndex = CustomExpressionImplementations.Num();
//------------------------先把需要调用的函数加上去,第一个函数默认为Main函数-----------------------------------------
for (int32 i = 1; i < Custom->HLSLFunctions.Num(); i++)
{
FString Code = Custom->HLSLFunctions[i].FunctionCodes;
FString FunctionName = Custom->HLSLFunctions[i].FunctionName;
if (!Code.Contains(TEXT("return")))
{
Code = FString(TEXT("return ")) + Code + TEXT(";");
}
Code.ReplaceInline(TEXT("\n"), TEXT("\r\n"), ESearchCase::CaseSensitive);
FString ParametersType = ShaderFrequency == SF_Vertex ? TEXT("Vertex") : (ShaderFrequency == SF_Domain ? TEXT("Tessellation") : TEXT("Pixel"));
FString ImplementationCode = FString::Printf(TEXT("%s\r\n{\r\n%s\r\n}\r\n"),*FunctionName, *Code);
CustomExpressionImplementations.Add(ImplementationCode);
}
FString Code = Custom->HLSLFunctions[0].FunctionCodes;
if (!Code.Contains(TEXT("return")))
{
Code = FString(TEXT("return ")) + Code + TEXT(";");
}
Code.ReplaceInline(TEXT("\n"), TEXT("\r\n"), ESearchCase::CaseSensitive);
FString ParametersType = ShaderFrequency == SF_Vertex ? TEXT("Vertex") : (ShaderFrequency == SF_Domain ? TEXT("Tessellation") : TEXT("Pixel"));
FString ImplementationCode = FString::Printf(TEXT("%s CustomExpression%d(FMaterial%sParameters Parameters%s)\r\n{\r\n%s\r\n}\r\n"), *OutputTypeString, CustomExpressionIndex, *ParametersType, *InputParamDecl, *Code);
CustomExpressionImplementations.Add(ImplementationCode);
//-------------------------------------------------------------------------------------------
// Add call to implementation function
FString CodeChunk = FString::Printf(TEXT("CustomExpression%d(Parameters"), CustomExpressionIndex);
for (int32 i = 0; i < CompiledInputs.Num(); i++)
{
// skip over unnamed inputs
if (Custom->Inputs[i].InputName.IsNone())
{
continue;
}
FString ParamCode = GetParameterCode(CompiledInputs[i]);
EMaterialValueType ParamType = GetParameterType(CompiledInputs[i]);
CodeChunk += TEXT(",");
CodeChunk += *ParamCode;
if (ParamType == MCT_Texture2D || ParamType == MCT_TextureCube)
{
CodeChunk += TEXT(",");
CodeChunk += *ParamCode;
CodeChunk += TEXT("Sampler");
}
}
CodeChunk += TEXT(")");
ResultIdx = AddCodeChunk(
OutputType,
*CodeChunk
);
return ResultIdx;
}
加完代码后在HLSLTranslator里加上include文件
完成之后打开MaterialCompiler.h z做如下两个修改:
Build一下之后就可以啦!!!