虚幻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一下之后就可以啦!!!