// Copyright Epic Games, Inc. All Rights Reserved. #include "InterchangeglTFPipeline.h" #include "InterchangePipelineHelper.h" #include "InterchangePipelineLog.h" #include "InterchangeManager.h" #include "InterchangeMaterialFactoryNode.h" #include "InterchangeMaterialInstanceNode.h" #include "InterchangeMeshFactoryNode.h" #include "InterchangeShaderGraphNode.h" #include "Nodes/InterchangeSourceNode.h" #include "Gltf/InterchangeGLTFMaterial.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialInstanceDynamic.h" #include "Misc/App.h" const TArray UGLTFPipelineSettings::ExpectedMaterialInstanceIdentifiers = {TEXT("MI_Default_Opaque"), TEXT("MI_Default_Mask"), TEXT("MI_Default_Blend"), TEXT("MI_Unlit_Opaque"), TEXT("MI_Unlit_Mask"), TEXT("MI_Unlit_Blend"), TEXT("MI_ClearCoat_Opaque"), TEXT("MI_ClearCoat_Mask"), TEXT("MI_ClearCoat_Blend"), TEXT("MI_Sheen_Opaque"), TEXT("MI_Sheen_Mask"), TEXT("MI_Sheen_Blend"), TEXT("MI_Transmission"), TEXT("MI_SpecularGlossiness_Opaque"), TEXT("MI_SpecularGlossiness_Mask"), TEXT("MI_SpecularGlossiness_Blend"), TEXT("MI_Default_Opaque_DS"), TEXT("MI_Default_Mask_DS"), TEXT("MI_Default_Blend_DS"), TEXT("MI_Unlit_Opaque_DS"), TEXT("MI_Unlit_Mask_DS"), TEXT("MI_Unlit_Blend_DS"), TEXT("MI_ClearCoat_Opaque_DS"), TEXT("MI_ClearCoat_Mask_DS"), TEXT("MI_ClearCoat_Blend_DS"), TEXT("MI_Sheen_Opaque_DS"), TEXT("MI_Sheen_Mask_DS"), TEXT("MI_Sheen_Blend_DS"), TEXT("MI_Transmission_DS"), TEXT("MI_SpecularGlossiness_Opaque_DS"), TEXT("MI_SpecularGlossiness_Mask_DS"), TEXT("MI_SpecularGlossiness_Blend_DS")}; TArray UGLTFPipelineSettings::ValidateMaterialInstancesAndParameters() const { TArray NotCoveredIdentifiersParameters; //Check if all Material variations are covered: TArray ExpectedIdentifiers = ExpectedMaterialInstanceIdentifiers; TArray IdentifiersUsed; MaterialParents.GetKeys(IdentifiersUsed); for (const FString& Identifier : IdentifiersUsed) { ExpectedIdentifiers.Remove(Identifier); } for (const FString& ExpectedIdentifier : ExpectedIdentifiers) { NotCoveredIdentifiersParameters.Add(TEXT("[") + ExpectedIdentifier + TEXT("]: MaterialInstance not found for Identifier.")); } for (const TPair& MaterialParent : MaterialParents) { TSet ExpectedParameters = GenerateExpectedParametersList(MaterialParent.Key); if (UMaterialInstance* ParentMaterialInstance = Cast(MaterialParent.Value.TryLoad())) { TArray ParameterIds; TArray ScalarParameterInfos; TArray VectorParameterInfos; TArray TextureParameterInfos; ParentMaterialInstance->GetAllScalarParameterInfo(ScalarParameterInfos, ParameterIds); ParentMaterialInstance->GetAllVectorParameterInfo(VectorParameterInfos, ParameterIds); ParentMaterialInstance->GetAllTextureParameterInfo(TextureParameterInfos, ParameterIds); for (const FMaterialParameterInfo& ParameterInfo : ScalarParameterInfos) { ExpectedParameters.Remove(ParameterInfo.Name.ToString()); } for (const FMaterialParameterInfo& ParameterInfo : VectorParameterInfos) { ExpectedParameters.Remove(ParameterInfo.Name.ToString()); } for (const FMaterialParameterInfo& ParameterInfo : TextureParameterInfos) { ExpectedParameters.Remove(ParameterInfo.Name.ToString()); } } for (const FString& ExpectedParameter : ExpectedParameters) { NotCoveredIdentifiersParameters.Add(TEXT("[") + MaterialParent.Key + TEXT("]: Does not cover expected parameter: ") + ExpectedParameter + TEXT(".")); } } return NotCoveredIdentifiersParameters; } TSet UGLTFPipelineSettings::GenerateExpectedParametersList(const FString& Identifier) const { using namespace UE::Interchange::GLTFMaterials; TSet ExpectedParameters; auto AddTextureAndRelated = [&ExpectedParameters](const FString& TextureName) { ExpectedParameters.Add(TextureName); ExpectedParameters.Add(TextureName + Inputs::PostFix::OffsetScale); ExpectedParameters.Add(TextureName + Inputs::PostFix::Rotation); ExpectedParameters.Add(TextureName + Inputs::PostFix::TexCoord); ExpectedParameters.Add(TextureName + Inputs::PostFix::TilingMethod); }; if (Identifier.Contains(TEXT("_Unlit"))) { AddTextureAndRelated(Inputs::BaseColorTexture); ExpectedParameters.Add(Inputs::BaseColorFactor); return ExpectedParameters; } //Generic ones: { AddTextureAndRelated(Inputs::NormalTexture); ExpectedParameters.Add(Inputs::NormalScale); if (!Identifier.Contains(TEXT("Transmission"))) { AddTextureAndRelated(Inputs::EmissiveTexture); ExpectedParameters.Add(Inputs::EmissiveFactor); ExpectedParameters.Add(Inputs::EmissiveStrength); } AddTextureAndRelated(Inputs::OcclusionTexture); ExpectedParameters.Add(Inputs::OcclusionStrength); if (!Identifier.Contains(TEXT("SpecularGlossiness"))) { ExpectedParameters.Add(Inputs::IOR); AddTextureAndRelated(Inputs::SpecularTexture); ExpectedParameters.Add(Inputs::SpecularFactor); } } //Based on ShadingModel: if (Identifier.Contains(TEXT("Default"))) { //MetalRoughness Specific: AddTextureAndRelated(Inputs::BaseColorTexture); ExpectedParameters.Add(Inputs::BaseColorFactor); AddTextureAndRelated(Inputs::MetallicRoughnessTexture); ExpectedParameters.Add(Inputs::MetallicFactor); ExpectedParameters.Add(Inputs::RoughnessFactor); } else if (Identifier.Contains(TEXT("ClearCoat"))) { AddTextureAndRelated(Inputs::ClearCoatTexture); ExpectedParameters.Add(Inputs::ClearCoatFactor); AddTextureAndRelated(Inputs::ClearCoatRoughnessTexture); ExpectedParameters.Add(Inputs::ClearCoatRoughnessFactor); AddTextureAndRelated(Inputs::ClearCoatNormalTexture); ExpectedParameters.Add(Inputs::ClearCoatNormalScale); } else if (Identifier.Contains(TEXT("Sheen"))) { AddTextureAndRelated(Inputs::SheenColorTexture); ExpectedParameters.Add(Inputs::SheenColorFactor); AddTextureAndRelated(Inputs::SheenRoughnessTexture); ExpectedParameters.Add(Inputs::SheenRoughnessFactor); } else if (Identifier.Contains(TEXT("Transmission"))) { AddTextureAndRelated(Inputs::TransmissionTexture); ExpectedParameters.Add(Inputs::TransmissionFactor); } else if (Identifier.Contains(TEXT("SpecularGlossiness"))) { AddTextureAndRelated(Inputs::DiffuseTexture); ExpectedParameters.Add(Inputs::DiffuseFactor); AddTextureAndRelated(Inputs::SpecularGlossinessTexture); ExpectedParameters.Add(Inputs::SpecFactor); ExpectedParameters.Add(Inputs::GlossinessFactor); } return ExpectedParameters; } UInterchangeMaterialInstanceFactoryNode* UGLTFPipelineSettings::BuildMaterialInstance(UInterchangeBaseNodeContainer* NodeContainer, const UInterchangeShaderGraphNode* ShaderGraphNode, const FString& OldFactoryNodeUId) { using namespace UE::Interchange::GLTFMaterials; TArray AttributeKeys; ShaderGraphNode->GetAttributeKeys(AttributeKeys); TMap GltfAttributeKeys; for (const UE::Interchange::FAttributeKey& AttributeKey : AttributeKeys) { if (AttributeKey.ToString().Contains(InterchangeGltfMaterialAttributeIdentifier)) { GltfAttributeKeys.Add(AttributeKey.ToString().Replace(*InterchangeGltfMaterialAttributeIdentifier, TEXT(""), ESearchCase::CaseSensitive), AttributeKey); } } if (GltfAttributeKeys.Num() == 0) { return nullptr; } FString ParentIdentifier; if (!ShaderGraphNode->GetStringAttribute(*(InterchangeGltfMaterialAttributeIdentifier + TEXT("ParentIdentifier")), ParentIdentifier)) { return nullptr; } GltfAttributeKeys.Remove(TEXT("ParentIdentifier")); FString Parent; if (const FSoftObjectPath* ObjectPath = MaterialParents.Find(ParentIdentifier)) { Parent = ObjectPath->GetAssetPathString(); } else { UE_LOG(LogInterchangePipeline, Warning, TEXT("[Interchange] Failed to load MaterialParent for ParentIdentifier: %s"), *ParentIdentifier); return nullptr; } FString MaterialFactoryNodeUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(ShaderGraphNode->GetUniqueID()); FString MaterialFactoryNodeName = ShaderGraphNode->GetDisplayLabel(); UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode = NewObject(NodeContainer); NodeContainer->SetupAndReplaceFactoryNode(MaterialInstanceFactoryNode, MaterialFactoryNodeUid, MaterialFactoryNodeName, EInterchangeNodeContainerType::FactoryData, OldFactoryNodeUId); MaterialInstanceFactoryNode->SetCustomParent(Parent); UInterchangeEditorUtilitiesBase* EditorUtilities = UInterchangeManager::GetInterchangeManager().GetEditorUtilities(); const UClass* MaterialClass = (EditorUtilities && EditorUtilities->IsRuntimeOrPIE()) ? UMaterialInstanceDynamic::StaticClass() : UMaterialInstanceConstant::StaticClass(); MaterialInstanceFactoryNode->SetCustomInstanceClassName(MaterialClass->GetPathName()); for (const TPair& GltfAttributeKey : GltfAttributeKeys) { UE::Interchange::EAttributeTypes AttributeType = ShaderGraphNode->GetAttributeType(GltfAttributeKey.Value); FString InputValueKey = UInterchangeShaderPortsAPI::MakeInputValueKey(GltfAttributeKey.Key); //we are only using 4 attribute types for now: switch (AttributeType) { case UE::Interchange::EAttributeTypes::Bool: { bool Value; if (ShaderGraphNode->GetBooleanAttribute(GltfAttributeKey.Value.Key, Value)) { MaterialInstanceFactoryNode->AddBooleanAttribute(InputValueKey, Value); } } break; case UE::Interchange::EAttributeTypes::Float: { float Value; if (ShaderGraphNode->GetFloatAttribute(GltfAttributeKey.Value.Key, Value)) { MaterialInstanceFactoryNode->AddFloatAttribute(InputValueKey, Value); } } break; case UE::Interchange::EAttributeTypes::LinearColor: { FLinearColor Value; if (ShaderGraphNode->GetLinearColorAttribute(GltfAttributeKey.Value.Key, Value)) { MaterialInstanceFactoryNode->AddLinearColorAttribute(InputValueKey, Value); } } break; case UE::Interchange::EAttributeTypes::String: { FString TextureUid; if (ShaderGraphNode->GetStringAttribute(GltfAttributeKey.Value.Key, TextureUid)) { FString FactoryTextureUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(TextureUid); MaterialInstanceFactoryNode->AddStringAttribute(InputValueKey, FactoryTextureUid); MaterialInstanceFactoryNode->AddFactoryDependencyUid(FactoryTextureUid); } } break; default: break; } } return MaterialInstanceFactoryNode; } UInterchangeGLTFPipeline::UInterchangeGLTFPipeline() : GLTFPipelineSettings(UGLTFPipelineSettings::StaticClass()->GetDefaultObject()) { } void UInterchangeGLTFPipeline::AdjustSettingsForContext(const FInterchangePipelineContextParams& ContextParams) { Super::AdjustSettingsForContext(ContextParams); TArray MaterialInstanceIssues = GLTFPipelineSettings->ValidateMaterialInstancesAndParameters(); for (const FString& MaterialInstanceIssue : MaterialInstanceIssues) { UE_LOG(LogInterchangePipeline, Warning, TEXT("%s"), *MaterialInstanceIssue); } } void UInterchangeGLTFPipeline::ExecutePipeline(UInterchangeBaseNodeContainer* NodeContainer, const TArray& InSourceDatas, const FString& ContentBasePath) { Super::ExecutePipeline(NodeContainer, InSourceDatas, ContentBasePath); if (GLTFPipelineSettings) { TMap MaterialFactoryNodeUidsToShaderGraphNodes; auto FindGLTFShaderGraphNode = [&MaterialFactoryNodeUidsToShaderGraphNodes, &NodeContainer](const FString& NodeUid, UInterchangeFactoryBaseNode* /*Material or MaterialInstance*/ FactoryNode) { TArray TargetNodeUids; FactoryNode->GetTargetNodeUids(TargetNodeUids); for (const FString& TargetNodeUid : TargetNodeUids) { if (const UInterchangeShaderGraphNode* ShaderGraphNode = Cast(NodeContainer->GetNode(TargetNodeUid))) { FString ParentIdentifier; if (ShaderGraphNode->GetStringAttribute(*(InterchangeGltfMaterialAttributeIdentifier + TEXT("ParentIdentifier")), ParentIdentifier)) { MaterialFactoryNodeUidsToShaderGraphNodes.Add(NodeUid, ShaderGraphNode); break; } } } }; NodeContainer->IterateNodesOfType([&MaterialFactoryNodeUidsToShaderGraphNodes, &NodeContainer, &FindGLTFShaderGraphNode](const FString& NodeUid, UInterchangeMaterialFactoryNode* MaterialFactoryNode) { FindGLTFShaderGraphNode(NodeUid, MaterialFactoryNode); }); NodeContainer->IterateNodesOfType([&MaterialFactoryNodeUidsToShaderGraphNodes, &NodeContainer, &FindGLTFShaderGraphNode](const FString& NodeUid, UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode) { FindGLTFShaderGraphNode(NodeUid, MaterialInstanceFactoryNode); }); for (const TPair& ShaderGraphNode : MaterialFactoryNodeUidsToShaderGraphNodes) { UInterchangeMaterialInstanceFactoryNode* MaterialInstanceFactoryNode = GLTFPipelineSettings->BuildMaterialInstance(NodeContainer, ShaderGraphNode.Value, ShaderGraphNode.Key); UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::FindOrCreateUniqueInstance(NodeContainer); UE::Interchange::PipelineHelper::FillSubPathFromSourceNode(MaterialInstanceFactoryNode, SourceNode); } } }