// Copyright Epic Games, Inc. All Rights Reserved. #include "Item/MetaHumanGroomPipeline.h" #include "MetaHumanDefaultPipelineLog.h" #include "MetaHumanItemEditorPipeline.h" #include "Algo/Find.h" #include "GroomAsset.h" #include "GroomBindingAsset.h" #include "GroomComponent.h" #include "Logging/StructuredLog.h" #include "Materials/MaterialInstanceDynamic.h" #include "Engine/Texture.h" UMetaHumanGroomPipeline::UMetaHumanGroomPipeline() { // Initialize the specification { Specification = CreateDefaultSubobject("Specification"); Specification->BuildOutputStruct = FMetaHumanGroomPipelineBuildOutput::StaticStruct(); Specification->AssemblyInputStruct = FMetaHumanGroomPipelineAssemblyInput::StaticStruct(); Specification->AssemblyOutputStruct = FMetaHumanGroomPipelineAssemblyOutput::StaticStruct(); } } #if WITH_EDITOR void UMetaHumanGroomPipeline::SetDefaultEditorPipeline() { EditorPipeline = nullptr; const TSubclassOf EditorPipelineClass = GetEditorPipelineClass(); if (EditorPipelineClass) { EditorPipeline = NewObject(this, EditorPipelineClass); } } const UMetaHumanItemEditorPipeline* UMetaHumanGroomPipeline::GetEditorPipeline() const { // If there's no editor pipeline instance, we can use the Class Default Object, because // pipelines are stateless and won't be modified when used. // // This is unfortunately a slow path, as it involves looking the class up by name. We could // cache this if it becomes a performance issue. if (!EditorPipeline) { const TSubclassOf EditorPipelineClass = GetEditorPipelineClass(); if (EditorPipelineClass) { return EditorPipelineClass.GetDefaultObject(); } } return EditorPipeline; } TSubclassOf UMetaHumanGroomPipeline::GetEditorPipelineClass() const { const TSoftClassPtr SoftEditorPipelineClass(FSoftObjectPath(TEXT("/Script/MetaHumanDefaultEditorPipeline.MetaHumanGroomEditorPipeline"))); return SoftEditorPipelineClass.Get(); } #endif void UMetaHumanGroomPipeline::AssembleItem( const FMetaHumanPaletteItemPath& BaseItemPath, const TArray& SlotSelections, const FMetaHumanPaletteBuiltData& ItemBuiltData, const FInstancedStruct& AssemblyInput, TNotNull OuterForGeneratedObjects, const FOnAssemblyComplete& OnComplete) const { const FInstancedStruct& BuildOutput = ItemBuiltData.ItemBuiltData[BaseItemPath].BuildOutput; if (!BuildOutput.GetPtr()) { UE_LOGFMT(LogMetaHumanDefaultPipeline, Error, "Build output not provided to Groom pipeline during assembly"); OnComplete.ExecuteIfBound(FMetaHumanAssemblyOutput()); return; } if (!AssemblyInput.GetPtr()) { UE_LOGFMT(LogMetaHumanDefaultPipeline, Error, "Assembly input not provided to Groom pipeline during assembly"); OnComplete.ExecuteIfBound(FMetaHumanAssemblyOutput()); return; } const FMetaHumanGroomPipelineBuildOutput& GroomBuildOutput = BuildOutput.Get(); const FMetaHumanGroomPipelineAssemblyInput& GroomAssemblyInput = AssemblyInput.Get(); const TObjectPtr* GroomBindingPtr = GroomBuildOutput.Bindings.FindByPredicate( [TargetMesh = GroomAssemblyInput.TargetMesh](const TObjectPtr& Binding) { return Binding && TargetMesh == Binding->GetTargetSkeletalMesh(); }); if (!GroomBindingPtr) { UE_LOGFMT(LogMetaHumanDefaultPipeline, Error, "The requested skeletal mesh ({Mesh}) was not found in the Groom pipeline's build output", GetPathNameSafe(GroomAssemblyInput.TargetMesh)); OnComplete.ExecuteIfBound(FMetaHumanAssemblyOutput()); return; } FMetaHumanAssemblyOutput AssemblyOutput; FMetaHumanInstanceParameterOutput InstanceParameterOutput; FMetaHumanGroomPipelineAssemblyOutput& GroomAssemblyOutput = AssemblyOutput.PipelineAssemblyOutput.InitializeAs(); GroomAssemblyOutput.Binding = *GroomBindingPtr; // TODO: Could initialise this only if there are parameters FMetaHumanGroomPipelineParameterContext& ParameterContext = InstanceParameterOutput.ParameterContext.InitializeAs(); if (GroomAssemblyOutput.Binding) { if (const UGroomAsset* Groom = GroomAssemblyOutput.Binding->GetGroom()) { const TArray& HairGroupMaterials = Groom->GetHairGroupsMaterials(); for (int32 SlotIndex = 0; SlotIndex < HairGroupMaterials.Num(); ++SlotIndex) { const FHairGroupsMaterial& GroupMaterial = HairGroupMaterials[SlotIndex]; ParameterContext.AvailableSlots.Add(GroupMaterial.SlotName); if (GroomAssemblyOutput.OverrideMaterials.Contains(GroupMaterial.SlotName)) { // A slot with the same name has already been processed. // // We can only support one slot for each slot name. continue; } TObjectPtr AssemblyMaterial = GroupMaterial.Material; const TObjectPtr* PipelineMaterialOverride = OverrideMaterials.Find(GroupMaterial.SlotName); if (PipelineMaterialOverride != nullptr) { AssemblyMaterial = *PipelineMaterialOverride; } if (!AssemblyMaterial) { // No material is assigned to this slot continue; } bool bNewMaterial = false; if (!AssemblyMaterial->IsA()) { bNewMaterial = true; AssemblyMaterial = UMaterialInstanceDynamic::Create(AssemblyMaterial, nullptr); } UMaterialInstanceDynamic* AssemblyMaterialDynamic = CastChecked(AssemblyMaterial); OverrideInitialMaterialValues(AssemblyMaterialDynamic, GroupMaterial.SlotName, SlotIndex); const TArray MaterialParamsForThisSlot = RuntimeMaterialParameters.FilterByPredicate( [SlotName = GroupMaterial.SlotName, SlotIndex](const FMetaHumanMaterialParameter& Parameter) { switch (Parameter.SlotTarget) { case EMetaHumanRuntimeMaterialParameterSlotTarget::SlotNames: return Parameter.SlotNames.Contains(SlotName); case EMetaHumanRuntimeMaterialParameterSlotTarget::SlotIndices: return Parameter.SlotIndices.Contains(SlotIndex); default: return false; } }); const bool bSuccessful = UE::MetaHuman::MaterialUtils::ParametersToPropertyBag( AssemblyMaterialDynamic, MaterialParamsForThisSlot, InstanceParameterOutput.Parameters); if (!bSuccessful) { continue; } if (bNewMaterial) { AssemblyOutput.Metadata.Emplace(AssemblyMaterial, TEXT("Grooms"), AssemblyMaterial->GetName()); AssemblyMaterial->Rename(nullptr, OuterForGeneratedObjects); } ParameterContext.MaterialSlotToMaterialInstance.Add(GroupMaterial.SlotName, AssemblyMaterialDynamic); if (AssemblyMaterial != GroupMaterial.Material) { GroomAssemblyOutput.OverrideMaterials.Add(GroupMaterial.SlotName, AssemblyMaterial); } } } } if (InstanceParameterOutput.Parameters.IsValid()) { AssemblyOutput.InstanceParameters.Add(BaseItemPath, MoveTemp(InstanceParameterOutput)); } OnComplete.ExecuteIfBound(MoveTemp(AssemblyOutput)); } void UMetaHumanGroomPipeline::SetInstanceParameters(const FInstancedStruct& ParameterContext, const FInstancedPropertyBag& Parameters) const { if (!ParameterContext.GetPtr()) { // Can't do anything without context return; } const FMetaHumanGroomPipelineParameterContext& GroomParameterContext = ParameterContext.Get(); UE::MetaHuman::MaterialUtils::SetInstanceParameters( RuntimeMaterialParameters, GroomParameterContext.MaterialSlotToMaterialInstance, GroomParameterContext.AvailableSlots, Parameters); } TNotNull UMetaHumanGroomPipeline::GetSpecification() const { return Specification; } void UMetaHumanGroomPipeline::ApplyGroomAssemblyOutputToGroomComponent(const FMetaHumanGroomPipelineAssemblyOutput& GroomAssemblyOutput, UGroomComponent* GroomComponent) { const bool bTemporaryEnableStrands = GroomComponent->bUseCards; if (bTemporaryEnableStrands) { GroomComponent->SetUseCards(false); } GroomComponent->SetGroomAsset(GroomAssemblyOutput.Binding ? GroomAssemblyOutput.Binding->GetGroom() : nullptr, GroomAssemblyOutput.Binding); GroomComponent->EmptyOverrideMaterials(); for (const TPair>& OverrideMaterial : GroomAssemblyOutput.OverrideMaterials) { const int32 MaterialIndex = GroomComponent->GetMaterialIndex(OverrideMaterial.Key); if (MaterialIndex != INDEX_NONE) { GroomComponent->SetMaterial(MaterialIndex, OverrideMaterial.Value); } } if (bTemporaryEnableStrands) { GroomComponent->GetWorld()->SendAllEndOfFrameUpdates(); GroomComponent->SetUseCards(true); } }