// Copyright Epic Games, Inc. All Rights Reserved. #include "MaterialEditor/PreviewMaterial.h" #include "Modules/ModuleManager.h" #include "MaterialEditor/DEditorParameterValue.h" #include "MaterialEditor/DEditorFontParameterValue.h" #include "MaterialEditor/DEditorMaterialLayersParameterValue.h" #include "MaterialEditor/DEditorRuntimeVirtualTextureParameterValue.h" #include "MaterialEditor/DEditorScalarParameterValue.h" #include "MaterialEditor/DEditorStaticComponentMaskParameterValue.h" #include "MaterialEditor/DEditorStaticSwitchParameterValue.h" #include "MaterialEditor/DEditorTextureParameterValue.h" #include "MaterialEditor/DEditorVectorParameterValue.h" #include "MaterialEditor/DEditorDoubleVectorParameterValue.h" #include "AI/NavigationSystemBase.h" #include "MaterialEditor/MaterialEditorInstanceConstant.h" #include "MaterialEditor/MaterialEditorPreviewParameters.h" #include "MaterialEditor/MaterialEditorMeshComponent.h" #include "MaterialEditorModule.h" #include "MaterialCachedData.h" #include "Materials/MaterialInstance.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialFunctionInstance.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionRuntimeVirtualTextureSampleParameter.h" #include "Materials/MaterialExpressionFontSampleParameter.h" #include "Materials/MaterialExpressionMaterialAttributeLayers.h" #include "Materials/MaterialExpressionStaticBoolParameter.h" #include "Materials/MaterialExpressionStaticComponentMaskParameter.h" #include "Materials/MaterialFunction.h" #include "UObject/UObjectIterator.h" #include "PropertyEditorDelegates.h" #include "IDetailsView.h" #include "MaterialEditingLibrary.h" #include "MaterialPropertyHelpers.h" #include "MaterialStatsCommon.h" #include "MaterialDomain.h" #include "RenderUtils.h" /** * Class for rendering the material on the preview mesh in the Material Editor */ class FPreviewMaterial : public FMaterialResource { public: virtual ~FPreviewMaterial() { } /** * Should the shader for this material with the given platform, shader type and vertex * factory type combination be compiled * * @param Platform The platform currently being compiled for * @param ShaderType Which shader is being compiled * @param VertexFactory Which vertex factory is being compiled (can be NULL) * * @return true if the shader should be compiled */ virtual bool ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const override { // only generate the needed shaders (which should be very restrictive for fast recompiling during editing) // @todo: Add a FindShaderType by fname or something if( Material->IsUIMaterial() ) { if (FCString::Stristr(ShaderType->GetName(), TEXT("TSlateMaterialShaderPS")) || FCString::Stristr(ShaderType->GetName(), TEXT("TSlateMaterialShaderVS"))) { return true; } } if (Material->IsPostProcessMaterial()) { if (FCString::Stristr(ShaderType->GetName(), TEXT("PostProcess"))) { return true; } } { bool bEditorStatsMaterial = Material->bIsMaterialEditorStatsMaterial; // Always allow HitProxy shaders. if (FCString::Stristr(ShaderType->GetName(), TEXT("HitProxy"))) { return true; } // we only need local vertex factory for the preview static mesh if (VertexFactoryType != FindVertexFactoryType(FName(TEXT("FLocalVertexFactory"), FNAME_Find)) && VertexFactoryType != FindVertexFactoryType(FName(TEXT("FNaniteVertexFactory"), FNAME_Find))) { //cache for gpu skinned vertex factory if the material allows it //this way we can have a preview skeletal mesh if (bEditorStatsMaterial || !IsUsedWithSkeletalMesh()) { return false; } if ( VertexFactoryType != FindVertexFactoryType(FName(TEXT("TGPUSkinVertexFactoryDefault"), FNAME_Find)) && VertexFactoryType != FindVertexFactoryType(FName(TEXT("TGPUSkinVertexFactoryUnlimited"), FNAME_Find)) ) { return false; } } // Only allow shaders that are used in the stats. if (bEditorStatsMaterial) { TMap> ShaderTypeNamesAndDescriptions; FMaterialStatsUtils::GetRepresentativeShaderTypesAndDescriptions(ShaderTypeNamesAndDescriptions, this); for (auto DescriptionPair : ShaderTypeNamesAndDescriptions) { auto &DescriptionArray = DescriptionPair.Value; if (DescriptionArray.FindByPredicate([ShaderType = ShaderType](auto& Info) { return Info.ShaderName == ShaderType->GetFName(); })) { return true; } } return false; } // look for any of the needed type bool bShaderTypeMatches = false; // For FMaterialResource::GetRepresentativeInstructionCounts if (FCString::Stristr(ShaderType->GetName(), TEXT("MaterialCHSFNoLightMapPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("MobileDirectionalLight"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("MobileMovableDirectionalLight"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSTDistanceFieldShadowsAndLightMapPolicyHQ"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("Simple"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSFNoLightMapPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("TBasePassCSFNoLightMapPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("CachedPointIndirectLightingPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("PrecomputedVolumetricLightmapLightingPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSFSelfShadowedTranslucencyPolicy"))) { bShaderTypeMatches = true; } // Pick tessellation shader based on material settings else if(FCString::Stristr(ShaderType->GetName(), TEXT("BasePassVSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassHSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassDSFNoLightMapPolicy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("DepthOnly"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("ShadowDepth"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("Distortion"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("MeshDecal"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("TBasePassForForwardShading"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FDebugViewModeVS"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FDebugViewModePS"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FVelocity"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FAnisotropy"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("RayTracingDynamicGeometryConverter"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FLumenCard"))) { bShaderTypeMatches = true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("FLumenFrontLayerTranslucencyGBuffer"))) { bShaderTypeMatches = true; } return bShaderTypeMatches; } } /** * Should shaders compiled for this material be saved to disk? */ virtual bool IsPersistent() const override { return false; } virtual FString GetAssetName() const override { return FString::Printf(TEXT("Preview:%s"), *FMaterialResource::GetAssetName()); } virtual bool IsPreview() const override { return true; } }; /** Implementation of Preview Material functions*/ UPreviewMaterial::UPreviewMaterial(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } FMaterialResource* UPreviewMaterial::AllocateResource() { return new FPreviewMaterial(); } void UMaterialEditorPreviewParameters::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { if (PreviewMaterial && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { FProperty* PropertyThatChanged = PropertyChangedEvent.Property; if (OriginalFunction == nullptr) { bool bLayersParameterChanged = false; // If a material layers parameter changed we need to update it on the source instance // immediately so parameters contained within the new functions can be collected for (FEditorParameterGroup& Group : ParameterGroups) { for (UDEditorParameterValue* Parameter : Group.Parameters) { if (UDEditorMaterialLayersParameterValue* LayersParam = Cast(Parameter)) { UMaterialExpressionMaterialAttributeLayers* LayersNode = nullptr; for(UMaterialExpression* Expression : PreviewMaterial->GetExpressions()) { if(Expression->IsA()) { LayersNode = Cast(Expression); LayersNode->DefaultLayers = LayersParam->ParameterValue; bLayersParameterChanged = true; break; } } } } } if (bLayersParameterChanged) { RegenerateArrays(); } CopyToSourceInstance(); PreviewMaterial->PostEditChangeProperty(PropertyChangedEvent); } else { ApplySourceFunctionChanges(); if (OriginalFunction->PreviewMaterial) { OriginalFunction->PreviewMaterial->PostEditChangeProperty(PropertyChangedEvent); } } } } void UMaterialEditorPreviewParameters::AssignParameterToGroup(UDEditorParameterValue* ParameterValue, const FName& InParameterGroupName) { check(ParameterValue); FName ParameterGroupName = InParameterGroupName; if (ParameterGroupName == TEXT("") || ParameterGroupName == TEXT("None")) { ParameterGroupName = TEXT("None"); } IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked("MaterialEditor"); // Material layers UDEditorMaterialLayersParameterValue* MaterialLayerParam = Cast(ParameterValue); if (ParameterValue->ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter) { if (MaterialLayerParam) { ParameterGroupName = FMaterialPropertyHelpers::LayerParamName; } else { FString AppendedGroupName = GlobalGroupPrefix.ToString(); if (ParameterGroupName != TEXT("None")) { ParameterGroupName.AppendString(AppendedGroupName); ParameterGroupName = FName(*AppendedGroupName); } else { ParameterGroupName = TEXT("Global"); } } } FEditorParameterGroup& CurrentGroup = FMaterialPropertyHelpers::GetParameterGroup(PreviewMaterial, ParameterGroupName, ParameterGroups); CurrentGroup.GroupAssociation = ParameterValue->ParameterInfo.Association; ParameterValue->SetFlags(RF_Transactional); CurrentGroup.Parameters.Add(ParameterValue); } void UMaterialEditorPreviewParameters::RegenerateArrays() { ParameterGroups.Empty(); if (PreviewMaterial) { // Only operate on base materials UMaterial* ParentMaterial = PreviewMaterial; // This can run before UMaterial::PostEditChangeProperty has a chance to run, so explicitly call UpdateCachedExpressionData here PreviewMaterial->UpdateCachedExpressionData(); // Loop through all types of parameters for this material and add them to the parameter arrays. TMap ParameterValues; for (int32 TypeIndex = 0; TypeIndex < NumMaterialParameterTypes; ++TypeIndex) { const EMaterialParameterType ParameterType = (EMaterialParameterType)TypeIndex; ParentMaterial->GetAllParametersOfType(ParameterType, ParameterValues); for (const auto& It : ParameterValues) { UDEditorParameterValue* Parameter = UDEditorParameterValue::Create(this, ParameterType, It.Key, It.Value); Parameter->bOverride = true; AssignParameterToGroup(Parameter, It.Value.Group); } } // Static Material Layers { FMaterialLayersFunctions MaterialLayers; if (ParentMaterial->GetMaterialLayers(MaterialLayers)) { UDEditorMaterialLayersParameterValue* ParameterValue = NewObject(this); ParameterValue->bOverride = true; ParameterValue->ParameterValue = MoveTemp(MaterialLayers); AssignParameterToGroup(ParameterValue, FName()); } } } // sort contents of groups for (int32 ParameterIdx = 0; ParameterIdx < ParameterGroups.Num(); ParameterIdx++) { FEditorParameterGroup & ParamGroup = ParameterGroups[ParameterIdx]; struct FCompareUDEditorParameterValueByParameterName { FORCEINLINE bool operator()(const UDEditorParameterValue& A, const UDEditorParameterValue& B) const { FString AName = A.ParameterInfo.Name.ToString(); FString BName = B.ParameterInfo.Name.ToString(); return A.SortPriority != B.SortPriority ? A.SortPriority < B.SortPriority : AName < BName; } }; ParamGroup.Parameters.Sort(FCompareUDEditorParameterValueByParameterName()); } // sort groups itself pushing defaults to end struct FCompareFEditorParameterGroupByName { FORCEINLINE bool operator()(const FEditorParameterGroup& A, const FEditorParameterGroup& B) const { FString AName = A.GroupName.ToString(); FString BName = B.GroupName.ToString(); if (AName == TEXT("none")) { return false; } if (BName == TEXT("none")) { return false; } return A.GroupSortPriority != B.GroupSortPriority ? A.GroupSortPriority < B.GroupSortPriority : AName < BName; } }; ParameterGroups.Sort(FCompareFEditorParameterGroupByName()); TArray ParameterDefaultGroups; for (int32 ParameterIdx = 0; ParameterIdx < ParameterGroups.Num(); ParameterIdx++) { FEditorParameterGroup & ParamGroup = ParameterGroups[ParameterIdx]; if (ParamGroup.GroupName == TEXT("None")) { ParameterDefaultGroups.Add(ParamGroup); ParameterGroups.RemoveAt(ParameterIdx); break; } } if (ParameterDefaultGroups.Num() > 0) { ParameterGroups.Append(ParameterDefaultGroups); } } TObjectPtr UMaterialEditorPreviewParameters::GetMaterialInterface() { return Cast(PreviewMaterial); } void UMaterialEditorPreviewParameters::CopyToSourceInstance(const bool bForceStaticPermutationUpdate/* = false*/) { if (PreviewMaterial->IsTemplate(RF_ClassDefaultObject) == false && OriginalMaterial != nullptr) { OriginalMaterial->MarkPackageDirty(); // Scalar Parameters for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++) { FEditorParameterGroup & Group = ParameterGroups[GroupIdx]; for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++) { UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx]; if (Parameter) { FMaterialParameterMetadata EditorValue; if (Parameter->GetValue(EditorValue)) { PreviewMaterial->SetParameterValueEditorOnly(Parameter->ParameterInfo.Name, EditorValue); } else if (UDEditorMaterialLayersParameterValue* LayersParameter = Cast(Parameter)) { // find material expression material attribute layer and update it's default/param layers for(UMaterialExpression* Expression : PreviewMaterial->GetExpressions()) { if(Expression->IsA()) { UMaterialExpressionMaterialAttributeLayers* LayersNode = Cast(Expression); LayersNode->DefaultLayers = LayersParameter->ParameterValue; break; } } } } } } } } FName UMaterialEditorPreviewParameters::GlobalGroupPrefix = FName("Global "); void UMaterialEditorPreviewParameters::ApplySourceFunctionChanges() { if (OriginalFunction != nullptr) { CopyToSourceInstance(); OriginalFunction->MarkPackageDirty(); // Scalar Parameters for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++) { FEditorParameterGroup & Group = ParameterGroups[GroupIdx]; for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++) { UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx]; if (Parameter) { FMaterialParameterMetadata EditorValue; if (Parameter->GetValue(EditorValue)) { OriginalFunction->SetParameterValueEditorOnly(Parameter->ParameterInfo.Name, EditorValue); } } } } UMaterialEditingLibrary::UpdateMaterialFunction(OriginalFunction, PreviewMaterial); } } #if WITH_EDITOR void UMaterialEditorPreviewParameters::PostEditUndo() { Super::PostEditUndo(); } #endif FName UMaterialEditorInstanceConstant::GlobalGroupPrefix = FName("Global "); UMaterialEditorInstanceConstant::UMaterialEditorInstanceConstant(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bIsFunctionPreviewMaterial = false; bShowOnlyOverrides = false; // Default to override with nothing on MIC (don't inherit parent setting). bNaniteOverride = true; } void UMaterialEditorInstanceConstant::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { if (SourceInstance) { // Warn our source instance that it is about to be updated. SourceInstance->PreEditChange(PropertyChangedEvent.Property); FProperty* PropertyThatChanged = PropertyChangedEvent.Property; bool bLayersParameterChanged = false; FNavigationLockContext NavUpdateLock(ENavigationLockReason::MaterialUpdate); if(PropertyThatChanged && PropertyThatChanged->GetName()==TEXT("Parent") ) { if(bIsFunctionPreviewMaterial) { bIsFunctionInstanceDirty = true; ApplySourceFunctionChanges(); } else { FMaterialUpdateContext Context; UpdateSourceInstanceParent(); Context.AddMaterialInstance(SourceInstance); // Fully update static parameters before recreating render state for all components SetSourceInstance(SourceInstance); ClearInvalidParameterOverrides(); } } else if (!bIsFunctionPreviewMaterial) { // If a material layers parameter changed we need to update it on the source instance // immediately so parameters contained within the new functions can be collected for (FEditorParameterGroup& Group : ParameterGroups) { for (UDEditorParameterValue* Parameter : Group.Parameters) { if (UDEditorMaterialLayersParameterValue* LayersParam = Cast(Parameter)) { if (SourceInstance->SetMaterialLayers(LayersParam->ParameterValue)) { bLayersParameterChanged = true; } } } } if (bLayersParameterChanged) { RegenerateArrays(); } } else if (bIsFunctionPreviewMaterial) { // If a property changed and we are editing a material function instance // we should dirty the function and apply the parameter changes to the source material // This way you get live previews and updates of the thumbnails. bIsFunctionInstanceDirty = true; ApplySourceFunctionChanges(); } CopyToSourceInstance(bLayersParameterChanged); // Tell our source instance to update itself so the preview updates. SourceInstance->PostEditChangeProperty(PropertyChangedEvent); // Invalidate the streaming data so that it gets rebuilt. SourceInstance->TextureStreamingData.Empty(); } } void UMaterialEditorInstanceConstant::AssignParameterToGroup(UDEditorParameterValue* ParameterValue, const FName& InParameterGroupName) { check(ParameterValue); FName ParameterGroupName = InParameterGroupName; if (ParameterGroupName == TEXT("") || ParameterGroupName == TEXT("None")) { if (bUseOldStyleMICEditorGroups == true) { ParameterGroupName = ParameterValue->GetDefaultGroupName(); } else { ParameterGroupName = TEXT("None"); } IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked("MaterialEditor"); // Material layers if (ParameterValue->ParameterInfo.Association == EMaterialParameterAssociation::GlobalParameter) { FString AppendedGroupName = GlobalGroupPrefix.ToString(); if (ParameterGroupName != TEXT("None")) { ParameterGroupName.AppendString(AppendedGroupName); ParameterGroupName = FName(*AppendedGroupName); } else { ParameterGroupName = TEXT("Global"); } } } FEditorParameterGroup& CurrentGroup = FMaterialPropertyHelpers::GetParameterGroup(Parent->GetMaterial(), ParameterGroupName, ParameterGroups); CurrentGroup.GroupAssociation = ParameterValue->ParameterInfo.Association; ParameterValue->SetFlags(RF_Transactional); CurrentGroup.Parameters.Add(ParameterValue); } TObjectPtr UMaterialEditorInstanceConstant::GetMaterialInterface() { return Cast(SourceInstance); } TObjectPtr UMaterialEditorInstanceConstant::GetParentMaterialInterface() { return SourceInstance ? SourceInstance->Parent : nullptr; } void UMaterialEditorInstanceConstant::RegenerateArrays() { VisibleExpressions.Empty(); ParameterGroups.Empty(); PostProcessOverrides.bIsOverrideable = false; PostProcessOverrides.UserSceneTextureInputs.Empty(); if (Parent) { // Only operate on base materials UMaterial* ParentMaterial = Parent->GetMaterial(); SourceInstance->UpdateParameterNames(); // Update any parameter names that may have changed. SourceInstance->UpdateCachedData(); // Need to get layer info first as other params are collected from layers { FMaterialLayersFunctions MaterialLayers; if (SourceInstance->GetMaterialLayers(MaterialLayers)) { UDEditorMaterialLayersParameterValue& ParameterValue = *(NewObject(this)); ParameterValue.bOverride = true; ParameterValue.ParameterValue = MoveTemp(MaterialLayers); AssignParameterToGroup(&ParameterValue, FName()); } } TMap ParameterValues; for (int32 TypeIndex = 0; TypeIndex < NumMaterialParameterTypes; ++TypeIndex) { const EMaterialParameterType ParameterType = (EMaterialParameterType)TypeIndex; SourceInstance->GetAllParametersOfType(ParameterType, ParameterValues); for (const auto& It : ParameterValues) { UDEditorParameterValue* Parameter = UDEditorParameterValue::Create(this, ParameterType, It.Key, It.Value); AssignParameterToGroup(Parameter, It.Value.Group); } } if (ParentMaterial->MaterialDomain == MD_PostProcess && ParentMaterial->BlendableLocation != BL_ReplacingTonemapper) { PostProcessOverrides.bIsOverrideable = true; UMaterialInterfaceEditorOnlyData* ParentMaterialEditorData = ParentMaterial->GetEditorOnlyData(); if (ParentMaterialEditorData && ParentMaterialEditorData->CachedExpressionData.IsValid()) { const FMaterialCachedExpressionEditorOnlyData* ParentMaterialExpressionData = ParentMaterialEditorData->CachedExpressionData.Get(); for (FName UserSceneTextureInput : ParentMaterialExpressionData->UserSceneTextureInputs) { FName OverrideFound = NAME_None; for (const FUserSceneTextureOverride& Override : SourceInstance->UserSceneTextureOverrides) { if (Override.Key == UserSceneTextureInput) { OverrideFound = Override.Value; break; } } PostProcessOverrides.UserSceneTextureInputs.Add({ UserSceneTextureInput, OverrideFound }); } } } IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); MaterialEditorModule->GetVisibleMaterialParameters(ParentMaterial, SourceInstance, VisibleExpressions); } // sort contents of groups for(int32 ParameterIdx = 0; ParameterIdx < ParameterGroups.Num(); ParameterIdx++) { FEditorParameterGroup & ParamGroup = ParameterGroups[ParameterIdx]; struct FCompareUDEditorParameterValueByParameterName { FORCEINLINE bool operator()(const UDEditorParameterValue& A, const UDEditorParameterValue& B) const { FString AName = A.ParameterInfo.Name.ToString(); FString BName = B.ParameterInfo.Name.ToString(); return A.SortPriority != B.SortPriority ? A.SortPriority < B.SortPriority : AName < BName; } }; ParamGroup.Parameters.Sort( FCompareUDEditorParameterValueByParameterName() ); } // sort groups itself pushing defaults to end struct FCompareFEditorParameterGroupByName { FORCEINLINE bool operator()(const FEditorParameterGroup& A, const FEditorParameterGroup& B) const { FString AName = A.GroupName.ToString(); FString BName = B.GroupName.ToString(); if (AName == TEXT("none")) { return false; } if (BName == TEXT("none")) { return false; } return A.GroupSortPriority != B.GroupSortPriority ? A.GroupSortPriority < B.GroupSortPriority : AName < BName; } }; ParameterGroups.Sort( FCompareFEditorParameterGroupByName() ); TArray ParameterDefaultGroups; for(int32 ParameterIdx=0; ParameterIdx0) { ParameterGroups.Append(ParameterDefaultGroups); } if (DetailsView.IsValid()) { // Tell our source instance to update itself so the preview updates. DetailsView.Pin()->ForceRefresh(); } } #if WITH_EDITOR void UMaterialEditorInstanceConstant::CleanParameterStack(int32 Index, EMaterialParameterAssociation MaterialType) { check(GIsEditor); TArray CleanedGroups; for (FEditorParameterGroup Group : ParameterGroups) { FEditorParameterGroup DuplicatedGroup = FEditorParameterGroup(); DuplicatedGroup.GroupAssociation = Group.GroupAssociation; DuplicatedGroup.GroupName = Group.GroupName; DuplicatedGroup.GroupSortPriority = Group.GroupSortPriority; for (UDEditorParameterValue* Parameter : Group.Parameters) { if (Parameter->ParameterInfo.Association != MaterialType || Parameter->ParameterInfo.Index != Index) { DuplicatedGroup.Parameters.Add(Parameter); } } CleanedGroups.Add(DuplicatedGroup); } ParameterGroups = CleanedGroups; CopyToSourceInstance(true); } void UMaterialEditorInstanceConstant::ResetOverrides(int32 Index, EMaterialParameterAssociation MaterialType) { check(GIsEditor); for (const FEditorParameterGroup& Group : ParameterGroups) { for (UDEditorParameterValue* Parameter : Group.Parameters) { if (Parameter->ParameterInfo.Association == MaterialType && Parameter->ParameterInfo.Index == Index) { const EMaterialParameterType ParameterType = Parameter->GetParameterType(); if (ParameterType != EMaterialParameterType::None) { FMaterialParameterMetadata SourceValue; bool bOverride = false; if (SourceInstance->GetParameterValue(ParameterType, Parameter->ParameterInfo, SourceValue, EMaterialGetParameterValueFlags::CheckInstanceOverrides)) { bOverride = SourceValue.bOverride; } Parameter->bOverride = bOverride; } } } } CopyToSourceInstance(true); } void UMaterialEditorInstanceConstant::ClearInvalidParameterOverrides() { const FMaterialCachedExpressionData* CachedExpressionData = Parent ? &Parent->GetCachedExpressionData() : nullptr; // Look for all Atlas Scalar parameters in each parameter group, then if a parameter has // an override, disable it unless the atlas texture matches that originally set in the parent Material. for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++) { FEditorParameterGroup& Group = ParameterGroups[GroupIdx]; for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++) { UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx]; if (UDEditorScalarParameterValue* ScalarParameter = Cast(Parameter)) { // Ignore parameters without override. if (!Parameter->bOverride) { continue; } // Get parent's parameter atlas texture. If identical to the one the editor parameter is using, // we can keep the override on (as the selected atlas curve will still make sense). FMaterialParameterMetadata Value; if (CachedExpressionData && CachedExpressionData->GetParameterValue(EMaterialParameterType::Scalar, ScalarParameter->ParameterInfo, Value) && Value.ScalarAtlas == ScalarParameter->AtlasData.Atlas) { continue; } // The atlas texture of the newly bound material are different. Disable the override. Parameter->bOverride = false; } } } } #endif void UMaterialEditorInstanceConstant::CopyToSourceInstance(const bool bForceStaticPermutationUpdate) { if (SourceInstance && !SourceInstance->IsTemplate(RF_ClassDefaultObject)) { if (bIsFunctionPreviewMaterial) { bIsFunctionInstanceDirty = true; } else { SourceInstance->MarkPackageDirty(); } { FMaterialInstanceParameterUpdateContext UpdateContext(SourceInstance, EMaterialInstanceClearParameterFlag::All); UpdateContext.SetBasePropertyOverrides(BasePropertyOverrides); UpdateContext.SetForceStaticPermutationUpdate(bForceStaticPermutationUpdate); for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++) { FEditorParameterGroup& Group = ParameterGroups[GroupIdx]; for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++) { UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx]; if (Parameter && Parameter->bOverride) { FMaterialParameterMetadata EditorValue; if (Parameter->GetValue(EditorValue)) { UpdateContext.SetParameterValueEditorOnly(Parameter->ParameterInfo, EditorValue, EMaterialSetParameterValueFlags::SetCurveAtlas); } else if (UDEditorMaterialLayersParameterValue* LayersParameter = Cast(Parameter)) { UpdateContext.SetMaterialLayers(LayersParameter->ParameterValue); } } } } } // Copy phys material back to source instance SourceInstance->PhysMaterial = PhysMaterial; SourceInstance->NaniteOverrideMaterial.bEnableOverride = bNaniteOverride; SourceInstance->NaniteOverrideMaterial.OverrideMaterialEditor = NaniteOverrideMaterial; // Copy the Lightmass settings... SourceInstance->SetOverrideCastShadowAsMasked(LightmassSettings.CastShadowAsMasked.bOverride); SourceInstance->SetCastShadowAsMasked(LightmassSettings.CastShadowAsMasked.ParameterValue); SourceInstance->SetOverrideEmissiveBoost(LightmassSettings.EmissiveBoost.bOverride); SourceInstance->SetEmissiveBoost(LightmassSettings.EmissiveBoost.ParameterValue); SourceInstance->SetOverrideDiffuseBoost(LightmassSettings.DiffuseBoost.bOverride); SourceInstance->SetDiffuseBoost(LightmassSettings.DiffuseBoost.ParameterValue); SourceInstance->SetOverrideExportResolutionScale(LightmassSettings.ExportResolutionScale.bOverride); SourceInstance->SetExportResolutionScale(LightmassSettings.ExportResolutionScale.ParameterValue); // Copy Refraction bias setting FMaterialParameterInfo RefractionInfo(TEXT("RefractionDepthBias")); SourceInstance->SetScalarParameterValueEditorOnly(RefractionInfo, RefractionDepthBias); // Copy UserSceneTextureOverrides SourceInstance->UserSceneTextureOverrides.Empty(); for (FEditorUserSceneTextureOverride& Override : PostProcessOverrides.UserSceneTextureInputs) { if (Override.Value != NAME_None && Override.Value != Override.Key) { SourceInstance->UserSceneTextureOverrides.Add({ Override.Key, Override.Value }); } } if (PostProcessOverrides.UserSceneTextureOutput != NAME_None) { // UserSceneTextureOutput override uses key of NAME_None SourceInstance->UserSceneTextureOverrides.Add({ NAME_None, PostProcessOverrides.UserSceneTextureOutput }); } // Copy other post process overrides (BlendableLocation / BlendablePriority) -- BL_ReplacingTonemapper is disallowed on overrides SourceInstance->bOverrideBlendableLocation = PostProcessOverrides.bOverrideBlendableLocation && PostProcessOverrides.BlendableLocationOverride != BL_ReplacingTonemapper; SourceInstance->bOverrideBlendablePriority = PostProcessOverrides.bOverrideBlendablePriority; if (SourceInstance->bOverrideBlendableLocation) { SourceInstance->BlendableLocationOverride = PostProcessOverrides.BlendableLocationOverride; } else { SourceInstance->BlendableLocationOverride = Parent ? Parent->GetMaterial()->BlendableLocation : TEnumAsByte(BL_SceneColorAfterTonemapping); } if (SourceInstance->bOverrideBlendablePriority) { SourceInstance->BlendablePriorityOverride = PostProcessOverrides.BlendablePriorityOverride; } else { SourceInstance->BlendablePriorityOverride = Parent ? Parent->GetMaterial()->BlendablePriority : 0; } SourceInstance->bOverrideSubsurfaceProfile = bOverrideSubsurfaceProfile; SourceInstance->SubsurfaceProfile = SubsurfaceProfile; SourceInstance->bOverrideSpecularProfile = bOverrideSpecularProfile; SourceInstance->SpecularProfileOverride = SpecularProfile; // Update object references and parameter names. SourceInstance->UpdateParameterNames(); VisibleExpressions.Empty(); // force refresh of visibility of properties if (Parent) { UMaterial* ParentMaterial = Parent->GetMaterial(); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked("MaterialEditor"); MaterialEditorModule->GetVisibleMaterialParameters(ParentMaterial, SourceInstance, VisibleExpressions); } } } void UMaterialEditorInstanceConstant::ApplySourceFunctionChanges() { if (bIsFunctionPreviewMaterial && bIsFunctionInstanceDirty) { CopyToSourceInstance(); // Copy updated function parameter values SourceFunction->ScalarParameterValues = SourceInstance->ScalarParameterValues; SourceFunction->VectorParameterValues = SourceInstance->VectorParameterValues; SourceFunction->DoubleVectorParameterValues = SourceInstance->DoubleVectorParameterValues; SourceFunction->TextureParameterValues = SourceInstance->TextureParameterValues; SourceFunction->TextureCollectionParameterValues = SourceInstance->TextureCollectionParameterValues; SourceFunction->RuntimeVirtualTextureParameterValues = SourceInstance->RuntimeVirtualTextureParameterValues; SourceFunction->SparseVolumeTextureParameterValues = SourceInstance->SparseVolumeTextureParameterValues; SourceFunction->FontParameterValues = SourceInstance->FontParameterValues; SourceFunction->EnumerationParameterValues = SourceInstance->EnumerationParameterValues; SourceFunction->StaticSwitchParameterValues = SourceInstance->GetStaticParameters().StaticSwitchParameters; const FStaticParameterSetEditorOnlyData& StaticParameters = SourceInstance->GetEditorOnlyStaticParameters(); SourceFunction->StaticComponentMaskParameterValues = StaticParameters.StaticComponentMaskParameters; SourceFunction->MarkPackageDirty(); bIsFunctionInstanceDirty = false; UMaterialEditingLibrary::UpdateMaterialFunction(SourceFunction, nullptr); } } void UMaterialEditorInstanceConstant::SetSourceInstance(UMaterialInstanceConstant* MaterialInterface) { check(MaterialInterface); SourceInstance = MaterialInterface; Parent = SourceInstance->Parent; PhysMaterial = SourceInstance->PhysMaterial; bNaniteOverride = SourceInstance->NaniteOverrideMaterial.bEnableOverride; NaniteOverrideMaterial = SourceInstance->NaniteOverrideMaterial.OverrideMaterialEditor; CopyBasePropertiesFromParent(); RegenerateArrays(); //propagate changes to the base material so the instance will be updated if it has a static permutation resource FMaterialInstanceParameterUpdateContext UpdateContext(SourceInstance, EMaterialInstanceClearParameterFlag::Static); for (int32 GroupIdx = 0; GroupIdx < ParameterGroups.Num(); GroupIdx++) { FEditorParameterGroup& Group = ParameterGroups[GroupIdx]; for (int32 ParameterIdx = 0; ParameterIdx < Group.Parameters.Num(); ParameterIdx++) { UDEditorParameterValue* Parameter = Group.Parameters[ParameterIdx]; if (Parameter && Parameter->bOverride) { FMaterialParameterMetadata EditorValue; if (Parameter->GetValue(EditorValue)) { // Only want to update static parameters here if (IsStaticMaterialParameter(EditorValue.Value.Type)) { UpdateContext.SetParameterValueEditorOnly(Parameter->ParameterInfo, EditorValue); } } else if (UDEditorMaterialLayersParameterValue* LayersParameter = Cast(Parameter)) { UpdateContext.SetMaterialLayers(LayersParameter->ParameterValue); } } } } } void UMaterialEditorInstanceConstant::SetSourceFunction(UMaterialFunctionInstance* MaterialFunction) { SourceFunction = MaterialFunction; bIsFunctionPreviewMaterial = !!(SourceFunction); } void UMaterialEditorInstanceConstant::UpdateSourceInstanceParent() { // If the parent was changed to the source instance, set it to NULL if( Parent == SourceInstance ) { Parent = NULL; } SourceInstance->SetParentEditorOnly( Parent ); SourceInstance->PostEditChange(); } void UMaterialEditorInstanceConstant::CopyBasePropertiesFromParent() { BasePropertyOverrides = SourceInstance->BasePropertyOverrides; // Copy the overrides (if not yet overridden), so they match their true values in the UI if (!BasePropertyOverrides.bOverride_OpacityMaskClipValue) { BasePropertyOverrides.OpacityMaskClipValue = SourceInstance->GetOpacityMaskClipValue(); } if (!BasePropertyOverrides.bOverride_BlendMode) { BasePropertyOverrides.BlendMode = SourceInstance->GetBlendMode(); } if (!BasePropertyOverrides.bOverride_ShadingModel) { if (SourceInstance->IsShadingModelFromMaterialExpression()) { BasePropertyOverrides.ShadingModel = MSM_FromMaterialExpression; } else { BasePropertyOverrides.ShadingModel = SourceInstance->GetShadingModels().GetFirstShadingModel(); } } if (!BasePropertyOverrides.bOverride_TwoSided) { BasePropertyOverrides.TwoSided = SourceInstance->IsTwoSided(); } if (!BasePropertyOverrides.bOverride_bIsThinSurface) { BasePropertyOverrides.bIsThinSurface = SourceInstance->IsThinSurface(); } if (!BasePropertyOverrides.bOverride_OutputTranslucentVelocity) { BasePropertyOverrides.bOutputTranslucentVelocity = SourceInstance->IsTranslucencyWritingVelocity(); } if (!BasePropertyOverrides.bOverride_bHasPixelAnimation) { BasePropertyOverrides.bHasPixelAnimation = SourceInstance->HasPixelAnimation(); } if (!BasePropertyOverrides.bOverride_bEnableTessellation) { BasePropertyOverrides.bEnableTessellation = SourceInstance->IsTessellationEnabled(); } if (!BasePropertyOverrides.DitheredLODTransition) { BasePropertyOverrides.DitheredLODTransition = SourceInstance->IsDitheredLODTransition(); } if (!BasePropertyOverrides.bOverride_DisplacementScaling) { BasePropertyOverrides.DisplacementScaling = SourceInstance->GetDisplacementScaling(); } if (!BasePropertyOverrides.bOverride_bEnableDisplacementFade) { BasePropertyOverrides.bEnableDisplacementFade = SourceInstance->IsDisplacementFadeEnabled(); } if (!BasePropertyOverrides.bOverride_DisplacementFadeRange) { BasePropertyOverrides.DisplacementFadeRange = SourceInstance->GetDisplacementFadeRange(); } if (!BasePropertyOverrides.bOverride_MaxWorldPositionOffsetDisplacement) { BasePropertyOverrides.MaxWorldPositionOffsetDisplacement = SourceInstance->GetMaxWorldPositionOffsetDisplacement(); } if (!BasePropertyOverrides.bOverride_CastDynamicShadowAsMasked) { BasePropertyOverrides.bCastDynamicShadowAsMasked = SourceInstance->GetCastDynamicShadowAsMasked(); } if (!BasePropertyOverrides.bOverride_CompatibleWithLumenCardSharing) { BasePropertyOverrides.bCompatibleWithLumenCardSharing = SourceInstance->IsCompatibleWithLumenCardSharing(); } // Copy the Lightmass settings... // The lightmass functions (GetCastShadowAsMasked, etc.) check if the value is overridden and returns the current value if so, otherwise returns the parent value // So we don't need to wrap these in the same "if not overriding" as above LightmassSettings.CastShadowAsMasked.ParameterValue = SourceInstance->GetCastShadowAsMasked(); LightmassSettings.EmissiveBoost.ParameterValue = SourceInstance->GetEmissiveBoost(); LightmassSettings.DiffuseBoost.ParameterValue = SourceInstance->GetDiffuseBoost(); LightmassSettings.ExportResolutionScale.ParameterValue = SourceInstance->GetExportResolutionScale(); //Copy refraction settings SourceInstance->GetRefractionSettings(RefractionDepthBias); bOverrideSubsurfaceProfile = SourceInstance->bOverrideSubsurfaceProfile; // Copy the subsurface profile. GetSubsurfaceProfile_Internal() will return either the overridden profile or one from a parent SubsurfaceProfile = SourceInstance->GetSubsurfaceProfile_Internal(); if (Substrate::IsSubstrateEnabled()) { bOverrideSpecularProfile = SourceInstance->bOverrideSpecularProfile; if (SourceInstance->NumSpecularProfile_Internal() > 0) { SpecularProfile = SourceInstance->GetSpecularProfileOverride_Internal(); } } // Post process blendable location and priority overrides PostProcessOverrides.bOverrideBlendableLocation = SourceInstance->bOverrideBlendableLocation; if (SourceInstance->bOverrideBlendableLocation) { PostProcessOverrides.BlendableLocationOverride = SourceInstance->BlendableLocationOverride; } else { PostProcessOverrides.BlendableLocationOverride = Parent ? Parent->GetMaterial()->BlendableLocation : TEnumAsByte(BL_SceneColorAfterTonemapping); } PostProcessOverrides.bOverrideBlendablePriority = SourceInstance->bOverrideBlendablePriority; if (SourceInstance->bOverrideBlendablePriority) { PostProcessOverrides.BlendablePriorityOverride = SourceInstance->BlendablePriorityOverride; } else { PostProcessOverrides.BlendablePriorityOverride = Parent ? Parent->GetMaterial()->BlendablePriority : 0; } // UserSceneTextureOutput override uses Key == NAME_None. UserSceneTextureInputs are initialized in RegenerateArrays, as those are affected // by graph reachability. for (const FUserSceneTextureOverride& Override : SourceInstance->UserSceneTextureOverrides) { if (Override.Key == NAME_None) { PostProcessOverrides.UserSceneTextureOutput = Override.Value; break; } } } #if WITH_EDITOR void UMaterialEditorInstanceConstant::PostEditUndo() { Super::PostEditUndo(); if (bIsFunctionPreviewMaterial && SourceFunction) { bIsFunctionInstanceDirty = true; ApplySourceFunctionChanges(); } else if (SourceInstance) { SourceInstance->PostEditUndo(); FMaterialUpdateContext Context; UpdateSourceInstanceParent(); Context.AddMaterialInstance(SourceInstance); } } #endif UMaterialEditorMeshComponent::UMaterialEditorMeshComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { }