// Copyright Epic Games, Inc. All Rights Reserved. #include "Materials/MaterialExpressionLandscapeLayerBlend.h" #include "Engine/Engine.h" #include "Engine/Texture.h" #include "EngineGlobals.h" #include "MaterialCompiler.h" #include "DataDrivenShaderPlatformInfo.h" #include "LandscapeUtilsPrivate.h" #include "Materials/MaterialAttributeDefinitionMap.h" #include "Materials/MaterialExpressionShadingModel.h" #if WITH_EDITOR #include "MaterialGraph/MaterialGraphNode.h" #endif #define LOCTEXT_NAMESPACE "Landscape" /////////////////////////////////////////////////////////////////////////////// // UMaterialExpressionLandscapeLayerBlend /////////////////////////////////////////////////////////////////////////////// UMaterialExpressionLandscapeLayerBlend::UMaterialExpressionLandscapeLayerBlend(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { FText NAME_Landscape; FConstructorStatics() : NAME_Landscape(LOCTEXT("Landscape", "Landscape")) { } }; static FConstructorStatics ConstructorStatics; #if WITH_EDITORONLY_DATA MenuCategories.Add(ConstructorStatics.NAME_Landscape); #endif } void UMaterialExpressionLandscapeLayerBlend::Serialize(FStructuredArchive::FRecord Record) { Super::Serialize(Record); FArchive& UnderlyingArchive = Record.GetUnderlyingArchive(); if (UnderlyingArchive.IsLoading() && UnderlyingArchive.UEVer() < VER_UE4_ADD_LB_WEIGHTBLEND) { // convert any LB_AlphaBlend entries to LB_WeightBlend for (FLayerBlendInput& LayerInput : Layers) { if (LayerInput.BlendType == LB_AlphaBlend) { LayerInput.BlendType = LB_WeightBlend; } } } } #if WITH_EDITOR TArrayView UMaterialExpressionLandscapeLayerBlend::GetInputsView() { CachedInputs.Empty(); CachedInputs.Reserve(Layers.Num() * 2); for (int32 LayerIdx = 0; LayerIdx(MCT_Float | MCT_MaterialAttributes); // can accept pretty much anything including MaterialAttributes } if (Layers[LayerIdx].BlendType == LB_HeightBlend) { if (InputIndex == Idx++) { return MCT_Float1; // the height input must be float1 } } } return MCT_Unknown; } bool UMaterialExpressionLandscapeLayerBlend::IsResultMaterialAttributes(int32 OutputIndex) { for (int32 LayerIdx = 0; LayerIdx < Layers.Num(); LayerIdx++) { if (Layers[LayerIdx].LayerInput.Expression && Layers[LayerIdx].LayerInput.Expression->IsResultMaterialAttributes(Layers[LayerIdx].LayerInput.OutputIndex)) { return true; } } return false; } int32 UMaterialExpressionLandscapeLayerBlend::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) { // For renormalization bool bNeedsRenormalize = false; int32 WeightSumCode = Compiler->Constant(0); // Temporary store for each layer's weight TArray WeightCodes; WeightCodes.Empty(Layers.Num()); const bool bTextureArrayEnabled = UE::Landscape::Private::UseWeightmapTextureArray(Compiler->GetShaderPlatform()); for (int32 LayerIdx = 0; LayerIdxConstant(Layer.ConstHeightInput); const int32 DefaultWeightCode = Layer.PreviewWeight > 0.0f ? Compiler->Constant(Layer.PreviewWeight) : INDEX_NONE; const int32 WeightCode = Compiler->StaticTerrainLayerWeight(Layer.LayerName, DefaultWeightCode, bTextureArrayEnabled); if (WeightCode != INDEX_NONE) { switch (Layer.BlendType) { case LB_WeightBlend: { // Store the weight plus accumulate the sum of all weights so far WeightCodes[LayerIdx] = WeightCode; WeightSumCode = Compiler->Add(WeightSumCode, WeightCode); } break; case LB_HeightBlend: { bNeedsRenormalize = true; // Modify weight with height int32 ModifiedWeightCode = Compiler->Clamp( Compiler->Add(Compiler->Lerp(Compiler->Constant(-1.f), Compiler->Constant(1.f), WeightCode), HeightCode), Compiler->Constant(0.0001f), Compiler->Constant(1.f)); // Store the final weight plus accumulate the sum of all weights so far WeightCodes[LayerIdx] = ModifiedWeightCode; WeightSumCode = Compiler->Add(WeightSumCode, ModifiedWeightCode); } break; } } } } int32 InvWeightSumCode = Compiler->Div(Compiler->Constant(1.f), WeightSumCode); // If the output has a ShadingModel type, regular arithmetic nodes (weight/blend) needs to be handled differently int32 OutputCode = INDEX_NONE; bool bIsShadingModel = false; if (FMaterialAttributeDefinitionMap::GetProperty(Compiler->GetMaterialAttribute()) == MP_ShadingModel) { OutputCode = Compiler->ShadingModel(EMaterialShadingModel::MSM_DefaultLit); bIsShadingModel = true; } else { OutputCode = Compiler->Constant(0); } for (int32 LayerIdx = 0; LayerIdxConstant3(static_cast(Layer.ConstLayerInput.X), static_cast(Layer.ConstLayerInput.Y), static_cast(Layer.ConstLayerInput.Z)); if (bIsShadingModel && Compiler->GetType(LayerCode) == EMaterialValueType::MCT_ShadingModel) { OutputCode = CompileShadingModelBlendFunction(Compiler, OutputCode, LayerCode, WeightCodes[LayerIdx]); } else if (bNeedsRenormalize) { // Renormalize the weights as our height modification has made them non-uniform OutputCode = Compiler->Add(OutputCode, Compiler->Mul(LayerCode, Compiler->Mul(InvWeightSumCode, WeightCodes[LayerIdx]))); } else { // No renormalization is necessary, so just add the weights OutputCode = Compiler->Add(OutputCode, Compiler->Mul(LayerCode, WeightCodes[LayerIdx])); } } } // Blend in LB_AlphaBlend layers for (FLayerBlendInput& Layer : Layers) { if (Layer.BlendType == LB_AlphaBlend) { const int32 DefaultWeightCode = Layer.PreviewWeight > 0.0f ? Compiler->Constant(Layer.PreviewWeight) : INDEX_NONE; const int32 WeightCode = Compiler->StaticTerrainLayerWeight(Layer.LayerName, DefaultWeightCode, bTextureArrayEnabled); if (WeightCode != INDEX_NONE) { const int32 LayerCode = Layer.LayerInput.Expression ? Layer.LayerInput.Compile(Compiler) : Compiler->Constant3(static_cast(Layer.ConstLayerInput.X), static_cast(Layer.ConstLayerInput.Y), static_cast(Layer.ConstLayerInput.Z)); // Blend in the layer using the alpha value if (bIsShadingModel && Compiler->GetType(LayerCode) == EMaterialValueType::MCT_ShadingModel) { OutputCode = CompileShadingModelBlendFunction(Compiler, OutputCode, LayerCode, WeightCode); } else { OutputCode = Compiler->Lerp(OutputCode, LayerCode, WeightCode); } } } } if (OutputCode != INDEX_NONE) { // We've definitely passed the reentrant check here so we're good to call IsResultMaterialAttributes(). bool bFoundExpression = false; bool bIsResultMaterialAttributes = false; for (int32 LayerIdx = 0; LayerIdx < Layers.Num(); LayerIdx++) { if (Layers[LayerIdx].HeightInput.Expression) { bool bHeightIsMaterialAttributes = Layers[LayerIdx].HeightInput.Expression->IsResultMaterialAttributes(Layers[LayerIdx].HeightInput.OutputIndex); if (bHeightIsMaterialAttributes) { Compiler->Errorf(TEXT("Height input (%s) does not accept MaterialAttributes"), *(Layers[LayerIdx].LayerName.ToString())); } } if (Layers[LayerIdx].LayerInput.Expression) { bool bLayerIsMaterialAttributes = Layers[LayerIdx].LayerInput.Expression->IsResultMaterialAttributes(Layers[LayerIdx].LayerInput.OutputIndex); if (!bFoundExpression) { bFoundExpression = true; bIsResultMaterialAttributes = bLayerIsMaterialAttributes; } else if (bIsResultMaterialAttributes != bLayerIsMaterialAttributes) { Compiler->Error(TEXT("Cannot mix MaterialAttributes and non MaterialAttributes nodes")); break; } } } } return OutputCode; } #endif // WITH_EDITOR UObject* UMaterialExpressionLandscapeLayerBlend::GetReferencedTexture() const { return GEngine->WeightMapPlaceholderTexture; } UMaterialExpression::ReferencedTextureArray UMaterialExpressionLandscapeLayerBlend::GetReferencedTextures() const { return { GEngine->WeightMapPlaceholderTexture, GEngine->WeightMapArrayPlaceholderTexture }; } #if WITH_EDITOR void UMaterialExpressionLandscapeLayerBlend::GetCaption(TArray& OutCaptions) const { OutCaptions.Add(FString(TEXT("Landscape Layer Blend"))); } void UMaterialExpressionLandscapeLayerBlend::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); // Clear out any height expressions for layers not using height blending for (int32 LayerIdx = 0; LayerIdxGetFName(); if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterialExpressionLandscapeLayerBlend, Layers)) { if (UMaterialGraphNode* MatGraphNode = Cast(GraphNode)) { MatGraphNode->RecreateAndLinkNode(); } } } } void UMaterialExpressionLandscapeLayerBlend::GetLandscapeLayerNames(TArray& OutLayers) const { for (const FLayerBlendInput& Layer : Layers) { OutLayers.AddUnique(Layer.LayerName); } } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE