// Copyright Epic Games, Inc. All Rights Reserved. #include "FbxMaterialExportUtilities.h" #include "Exporters/FbxExportOption.h" #include "Editor.h" #include "IMeshMergeUtilities.h" #include "MeshMergeModule.h" #include "Modules/ModuleManager.h" #include "IImageWrapperModule.h" #include "IImageWrapper.h" #include "IMaterialBakingModule.h" #include "Misc/FileHelper.h" #include "EditorFramework/AssetImportData.h" #include "Rendering/SkeletalMeshRenderData.h" #include "Components/BrushComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Components/StaticMeshComponent.h" #include "StaticMeshAttributes.h" #include "StaticMeshComponentLODInfo.h" #include "Engine/MapBuildDataRegistry.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/Brush.h" #include "MaterialPropertyEx.h" #include "MaterialBakingStructures.h" #include "Materials/Material.h" #include "Materials/MaterialInstance.h" #include "Materials/MaterialExpressionCustomOutput.h" #include "Materials/MaterialExpressionMaterialFunctionCall.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Materials/MaterialExpressionTextureObject.h" #include "Materials/MaterialExpressionTextureObjectParameter.h" #include "Materials/MaterialExpressionConstant.h" #include "Materials/MaterialExpressionConstant2Vector.h" #include "Materials/MaterialExpressionConstant3Vector.h" #include "Materials/MaterialExpressionConstant4Vector.h" #include "Materials/MaterialExpressionLinearInterpolate.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Model.h" #include "TextureCompiler.h" #include "InterchangeAssetImportData.h" THIRD_PARTY_INCLUDES_START #include THIRD_PARTY_INCLUDES_END namespace UnFbx { namespace FbxMaterialExportHelpers { UMaterialExpressionCustomOutput* GetCustomOutputByName(const UMaterialInterface* Material, const FString& FunctionName) { for (const TObjectPtr& Expression : Material->GetMaterial()->GetExpressions()) { UMaterialExpressionCustomOutput* CustomOutput = Cast(Expression); if (CustomOutput != nullptr && CustomOutput->GetFunctionName() == FunctionName) { return CustomOutput; } } return nullptr; } const FExpressionInput* GetInputForProperty(const UMaterialInterface* Material, const FMaterialPropertyEx& Property) { if (Property.IsCustomOutput()) { const FString FunctionName = Property.CustomOutput.ToString(); const UMaterialExpressionCustomOutput* CustomOutput = GetCustomOutputByName(Material, FunctionName); if (CustomOutput == nullptr) { return nullptr; } // Assume custom outputs always have a single input (which is true for all supported custom outputs) return const_cast(CustomOutput)->GetInput(0); } UMaterial* UnderlyingMaterial = const_cast(Material->GetMaterial()); return UnderlyingMaterial->GetExpressionInputForProperty(Property.Type); } template void GetAllInputExpressionsOfType(const UMaterialInterface* Material, const FMaterialPropertyEx& Property, TArray& OutExpressions) { const FExpressionInput* Input = GetInputForProperty(Material, Property); if (Input == nullptr) { return; } UMaterialExpression* InputExpression = Input->Expression; if (InputExpression == nullptr) { return; } TArray AllInputExpressions; InputExpression->GetAllInputExpressions(AllInputExpressions); for (UMaterialExpression* Expression : AllInputExpressions) { if (ExpressionType* ExpressionOfType = Cast(Expression)) { OutExpressions.Add(ExpressionOfType); } if (UMaterialFunctionInterface* MaterialFunction = UMaterial::GetExpressionFunctionPointer(Expression)) { MaterialFunction->GetAllExpressionsOfType(OutExpressions); } else if (TOptional LayersInterfaces = UMaterial::GetExpressionLayers(Expression)) { for (UMaterialFunctionInterface* Layer : LayersInterfaces->Layers) { if (Layer != nullptr) { Layer->GetAllExpressionsOfType(OutExpressions); } } for (UMaterialFunctionInterface* Blend : LayersInterfaces->Blends) { if (Blend != nullptr) { Blend->GetAllExpressionsOfType(OutExpressions); } } } } } UTexture* GetTextureFromSample(const UMaterialInterface* Material, const UMaterialExpressionTextureSample* SampleExpression) { if (const UMaterialExpressionTextureSampleParameter2D* SampleParameter = ExactCast(SampleExpression)) { UTexture* ParameterValue = SampleParameter->Texture; if (!Material->GetTextureParameterValue(SampleParameter->GetParameterName(), ParameterValue)) { return nullptr; } return ParameterValue; } if (const UMaterialExpressionTextureSample* Sample = ExactCast(SampleExpression)) { UMaterialExpression* ObjectExpression = Sample->TextureObject.Expression; if (ObjectExpression == nullptr) { return Sample->Texture; } if (const UMaterialExpressionTextureObjectParameter* ObjectParameter = ExactCast(ObjectExpression)) { UTexture* ParameterValue = ObjectParameter->Texture; if (!Material->GetTextureParameterValue(ObjectParameter->GetParameterName(), ParameterValue)) { return nullptr; } return ParameterValue; } if (const UMaterialExpressionTextureObject* Object = ExactCast(ObjectExpression)) { return Object->Texture; } return nullptr; } if (const UMaterialExpressionTextureObjectParameter* ObjectParameter = ExactCast(SampleExpression)) { UTexture* ParameterValue = ObjectParameter->Texture; if (!Material->GetTextureParameterValue(ObjectParameter->GetParameterName(), ParameterValue)) { return nullptr; } return ParameterValue; } if (const UMaterialExpressionTextureObject* Object = ExactCast(SampleExpression)) { return Object->Texture; } return nullptr; } FIntPoint TryGetMaxTextureSize(const UMaterialInterface* Material, const FMaterialPropertyEx& Property, const FIntPoint& DefaultMaxSize) { TArray TextureSamples; GetAllInputExpressionsOfType(Material, Property, TextureSamples); if (TextureSamples.Num() == 0) { return DefaultMaxSize; } FIntPoint MaxSize = { 0, 0 }; for (const UMaterialExpressionTextureSample* TextureSample : TextureSamples) { UTexture* Texture = GetTextureFromSample(Material, TextureSample); if (Texture == nullptr || !(Texture->IsA() || Texture->IsA())) { continue; } //Load Texture { #if WITH_EDITOR FTextureCompilingManager::Get().FinishCompilation({ Texture }); #endif Texture->SetForceMipLevelsToBeResident(30.0f); Texture->WaitForStreaming(); } auto GetMipBias = [](const UTexture* Texture) { if (const UTexture2D* Texture2D = Cast(Texture)) { return Texture2D->GetNumMips() - Texture2D->GetNumMipsAllowed(true); } return Texture->GetCachedLODBias(); }; auto GetInGameSize = [&GetMipBias](const UTexture* Texture) -> FIntPoint { const int32 Width = FMath::CeilToInt(Texture->GetSurfaceWidth()); const int32 Height = FMath::CeilToInt(Texture->GetSurfaceHeight()); const int32 MipBias = GetMipBias(Texture); const int32 InGameWidth = FMath::Max(Width >> MipBias, 1); const int32 InGameHeight = FMath::Max(Height >> MipBias, 1); return { InGameWidth, InGameHeight }; }; FIntPoint TextureSize = GetInGameSize(Texture); MaxSize = MaxSize.ComponentMax(TextureSize); } return (MaxSize.X == 0 || MaxSize.Y == 0) ? DefaultMaxSize : MaxSize; } void AnalyzeMaterialProperty(const UMaterialInterface* InMaterial, const FMaterialPropertyEx& InProperty, FMaterialAnalysisResult& OutAnalysis) { if (GetInputForProperty(InMaterial, InProperty) == nullptr) { OutAnalysis = FMaterialAnalysisResult(); return; } UMaterial* BaseMaterial = const_cast(InMaterial->GetMaterial()); bool bRequiresPrimitiveData = false; if (BaseMaterial) { BaseMaterial->AnalyzeMaterialPropertyEx(InProperty.Type, OutAnalysis); } // Also make sure the analysis takes into account primitive data OutAnalysis.bRequiresVertexData |= bRequiresPrimitiveData; } bool NeedsMeshDataForProperty(const UMaterialInterface* Material, const FMaterialPropertyEx& Property) { if (Material != nullptr) { FMaterialAnalysisResult Analysis; AnalyzeMaterialProperty(Material, Property, Analysis); return Analysis.bRequiresVertexData; } return false; } void GetAllTextureCoordinateIndices(const UMaterialInterface* Material, const FMaterialPropertyEx& Property, TArray& OutTexCoords) { FMaterialAnalysisResult Analysis; AnalyzeMaterialProperty(Material, Property, Analysis); const TBitArray<>& TexCoords = Analysis.TextureCoordinates; for (int32 Index = 0; Index < TexCoords.Num(); Index++) { if (TexCoords[Index]) { OutTexCoords.Add(Index); } } } void TransformColorSpace(TArray& Pixels, bool bFromSRGB, bool bToSRGB) { if (bFromSRGB == bToSRGB) { return; } if (bToSRGB) { for (FColor& Pixel : Pixels) { Pixel = Pixel.ReinterpretAsLinear().ToFColor(true); } } else { for (FColor& Pixel : Pixels) { Pixel = FLinearColor(Pixel).ToFColor(false); } } } TArray GetSectionIndices(const UStaticMesh* StaticMesh, int32 LODIndex, const int32& MaterialIndex) { if (StaticMesh == nullptr) { return {}; } const FStaticMeshLODResources& RenderData = StaticMesh->GetLODForExport(LODIndex); const FStaticMeshSectionArray& Sections = RenderData.Sections; TArray SectionIndices; SectionIndices.Reserve(Sections.Num()); for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex) { if (Sections[SectionIndex].MaterialIndex == MaterialIndex) { SectionIndices.Add(SectionIndex); } } return SectionIndices; } TArray GetSectionIndices(const USkeletalMesh* SkeletalMesh, int32 LODIndex, const int32& MaterialIndex) { if (SkeletalMesh == nullptr) { return {}; } const FSkeletalMeshLODRenderData& RenderData = SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex]; const TArray& Sections = RenderData.RenderSections; TArray SectionIndices; SectionIndices.Reserve(Sections.Num()); for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex) { if (Sections[SectionIndex].MaterialIndex == MaterialIndex) { SectionIndices.Add(SectionIndex); } } return SectionIndices; } namespace InterchangeMaterialProcessHelpers { FString LambertSurfaceMaterialPath = TEXT("/Interchange/Materials/LambertSurfaceMaterial.LambertSurfaceMaterial"); FString PhongSurfaceMaterialPath = TEXT("/Interchange/Materials/PhongSurfaceMaterial.PhongSurfaceMaterial"); FString MF_PhongToMetalRoughnessPathName = TEXT("/Interchange/Functions/MF_PhongToMetalRoughness.MF_PhongToMetalRoughness"); bool HandleMaterialProperty(UMaterialInterface* MaterialInterface, fbxsdk::FbxScene* Scene, fbxsdk::FbxSurfaceMaterial* FbxMaterial, const FString& ParameterPropertyName, const FMaterialPropertyEx& Property, const char* FbxPropertyName, const char* FbxFactorPropertyName, bool bPrimaryPropertyIsMap = true) { fbxsdk::FbxProperty FbxProperty = FbxMaterial->FindProperty(FbxPropertyName); fbxsdk::FbxProperty FbxFactorProperty = FbxMaterial->FindProperty(FbxFactorPropertyName); if (!FbxProperty.IsValid()) { return false; } bool bPropertyProcessed = false; //If Map is present(non-default) then Factor should be acquired from the Weight (and Color is presumed black and does not need to be set on the fbx material (it cannot be set)) //If no Map is present then the factor cannot be deduced, and just the Color should be set(If R = G = B then it should be a float otherwise RGB) //Use Map UTexture* Texture = nullptr; if (bPrimaryPropertyIsMap && MaterialInterface->GetTextureParameterValue(*(ParameterPropertyName + TEXT("Map")), Texture, true)) { FString TextureSourceFullPath = Texture->AssetImportData->GetFirstFilename(); //Create a fbx property FbxFileTexture* lTexture = FbxFileTexture::Create(Scene, "EnvSamplerTex"); lTexture->SetFileName(TCHAR_TO_UTF8(*TextureSourceFullPath)); lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->ConnectDstProperty(FbxProperty); float Weight; if (MaterialInterface->GetScalarParameterValue(*(ParameterPropertyName + TEXT("MapWeight")), Weight, true)) { if (FbxFactorProperty.IsValid()) FbxFactorProperty.Set(Weight); } bPropertyProcessed = true; } else { FLinearColor Color; float Value; if (MaterialInterface->GetVectorParameterValue(*ParameterPropertyName, Color, false)) { FLinearColor DefaultColor; MaterialInterface->GetVectorParameterDefaultValue(*ParameterPropertyName, DefaultColor); if (Color != DefaultColor) { if (Color.R == Color.G && Color.G == Color.B) { if (FbxProperty.IsValid()) FbxProperty.Set(Color.R); } else { FbxDouble3 FbxColor(Color.R, Color.G, Color.B); if (FbxProperty.IsValid()) FbxProperty.Set(FbxColor); } } bPropertyProcessed = true; } else if (MaterialInterface->GetScalarParameterValue(*ParameterPropertyName, Value, false)) { float DefaultValue; MaterialInterface->GetScalarParameterDefaultValue(*ParameterPropertyName, DefaultValue); if (Value != DefaultValue && FbxProperty.IsValid()) { FbxProperty.Set(Value); } bPropertyProcessed = true; } } return bPropertyProcessed; }; UMaterialExpressionMaterialFunctionCall* GetInterchangeMFPongToMetalRoughness(UMaterialInterface* MaterialInterface) { if (!MaterialInterface || !MaterialInterface->GetMaterial()) { return nullptr; } //Acquire the Material's expressions and check if any matches the MF_PhongToMetalRoughness MaterialFunctionCall: TConstArrayView> MaterialExpressions = MaterialInterface->GetMaterial()->GetExpressions(); UMaterialExpressionMaterialFunctionCall* ImportedMaterialFunction = nullptr; for (const TObjectPtr& MaterialExpression : MaterialExpressions) { if (UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast(MaterialExpression)) { if (FunctionCallExpression->MaterialFunction) { FString MF_PathName = FunctionCallExpression->MaterialFunction->GetPathName(); if (MF_PhongToMetalRoughnessPathName == MF_PathName) { ImportedMaterialFunction = FunctionCallExpression; break; } } } } return ImportedMaterialFunction; } void HandleMaterialExpression(UMaterialInterface* MaterialInterface, fbxsdk::FbxScene* Scene, fbxsdk::FbxSurfaceMaterial* FbxMaterial, const char* FbxPropertyName, const char* FbxFactorPropertyName, UMaterialExpression* MaterialExpression) { fbxsdk::FbxProperty FbxProperty = FbxMaterial->FindProperty(FbxPropertyName); fbxsdk::FbxProperty FbxFactorProperty = FbxMaterial->FindProperty(FbxFactorPropertyName); if (!FbxProperty.IsValid()) { return; } //Check for Lerp // Interchange import creates a Lepr Setup with: // A => Color // B => Map // Alpha => Weight (Factor) //If Map is present(non-default) then Factor should be acquired from the Weight (and Color is presumed black and does not need to be set on the fbx material (it cannot be set)) //If no Map is present then the factor cannot be deduced, and just the Color should be set(If R = G = B then it should be a float otherwise RGB) if (UMaterialExpressionLinearInterpolate* LerpExpr = Cast< UMaterialExpressionLinearInterpolate>(MaterialExpression)) { if (UMaterialExpressionTextureBase* TextureBaseExpr = Cast(LerpExpr->B.Expression)) { FString TextureSourceFullPath = TextureBaseExpr->Texture->AssetImportData->GetFirstFilename(); //Create a fbx property FbxFileTexture* lTexture = FbxFileTexture::Create(Scene, "EnvSamplerTex"); lTexture->SetFileName(TCHAR_TO_UTF8(*TextureSourceFullPath)); lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->ConnectDstProperty(FbxProperty); if (UMaterialExpressionConstant* ConstExpr1 = Cast(LerpExpr->Alpha.Expression)) { if (FbxFactorProperty.IsValid()) FbxFactorProperty.Set(ConstExpr1->R); } else if (UMaterialExpressionScalarParameter* ScalarParamExpr = Cast(LerpExpr->Alpha.Expression)) { const FHashedMaterialParameterInfo ParameterInfo(ScalarParamExpr->GetParameterName()); float Value; if (MaterialInterface->GetScalarParameterValue(ParameterInfo, Value)) { if (FbxFactorProperty.IsValid()) FbxFactorProperty.Set(Value); } } } } else { //If Texture/Constant is directly connected //Then Texture and Constant is targetting the FbxProperty. if (UMaterialExpressionTextureBase* TextureBaseExpr = Cast(MaterialExpression)) { FString TextureSourceFullPath = TextureBaseExpr->Texture->AssetImportData->GetFirstFilename(); //Create a fbx property FbxFileTexture* lTexture = FbxFileTexture::Create(Scene, "EnvSamplerTex"); lTexture->SetFileName(TCHAR_TO_UTF8(*TextureSourceFullPath)); lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->ConnectDstProperty(FbxProperty); } else if (UMaterialExpressionConstant* ConstExpr1 = Cast(MaterialExpression)) { FbxDouble3 FbxColor(ConstExpr1->R, ConstExpr1->R, ConstExpr1->R); FbxProperty.Set(FbxColor); } if (UMaterialExpressionConstant3Vector* ConstExpr3 = Cast(MaterialExpression)) { FbxDouble3 FbxColor(ConstExpr3->Constant.R, ConstExpr3->Constant.G, ConstExpr3->Constant.B); FbxProperty.Set(FbxColor); } if (UMaterialExpressionConstant4Vector* ConstExpr4 = Cast(MaterialExpression)) { FbxDouble3 FbxColor(ConstExpr4->Constant.R, ConstExpr4->Constant.G, ConstExpr4->Constant.B); FbxProperty.Set(FbxColor); } } } void HandleExpressionInput(UMaterialInterface* MaterialInterface, fbxsdk::FbxScene* Scene, fbxsdk::FbxSurfaceMaterial* FbxMaterial, const char* FbxPropertyName, const char* FbxFactorPropertyName, FExpressionInput* ExpressionInput) { if (ExpressionInput && ExpressionInput->Expression && ExpressionInput->IsConnected()) { HandleMaterialExpression(MaterialInterface, Scene, FbxMaterial, FbxPropertyName, FbxFactorPropertyName, ExpressionInput->GetTracedInput().Expression); } } } } FFbxMaterialBakingMeshData::FFbxMaterialBakingMeshData() { } FFbxMaterialBakingMeshData::FFbxMaterialBakingMeshData(UModel* Model, ABrush* Actor, int32 InLODIndex) : bHasMeshData(true) , LODIndex(InLODIndex) { FMeshDescription Mesh; FStaticMeshAttributes(Mesh).Register(); TArray Materials; GetBrushMesh(Actor, Model, Mesh, Materials); StaticMesh = CreateStaticMesh(Mesh, Materials, GetTransientPackage(), Actor ? Actor->GetFName() : Model->GetFName()); const IMeshMergeUtilities& MeshMergeUtilities = FModuleManager::Get().LoadModuleChecked("MeshMergeUtilities").GetUtilities(); FStaticMeshAttributes(Description).Register(); if (StaticMeshComponent != nullptr) { //MeshMergeUtilities.RetrieveMeshDescription (for StaticMeshComponent) uses bApplyComponentTransform==true, // however MeshData->Description is only used for DegenerateVertices/Triangles and UV checks, and material baking. MeshMergeUtilities.RetrieveMeshDescription(StaticMeshComponent, LODIndex, Description, true); constexpr int32 LightMapLODIndex = 0; // TODO: why is this zero? if (StaticMeshComponent->LODData.IsValidIndex(LightMapLODIndex)) { const FStaticMeshComponentLODInfo& LODData = StaticMeshComponent->LODData[LightMapLODIndex]; const FMeshMapBuildData* BuildData = StaticMeshComponent->GetMeshMapBuildData(LODData); if (BuildData != nullptr) { LightMap = BuildData->LightMap; LightMapResourceCluster = BuildData->ResourceCluster; } } } else { MeshMergeUtilities.RetrieveMeshDescription(StaticMesh, LODIndex, Description); } LightMapTexCoord = StaticMesh->GetLightMapCoordinateIndex(); const int32 NumTexCoords = StaticMesh->GetLODForExport(LODIndex).VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(); BakeUsingTexCoord = FMath::Min(LightMapTexCoord, NumTexCoords - 1); } FFbxMaterialBakingMeshData::FFbxMaterialBakingMeshData(const UStaticMesh* InStaticMesh, const UStaticMeshComponent* InStaticMeshComponent, int32 InLODIndex) : bHasMeshData(true) , StaticMeshComponent(InStaticMeshComponent) , StaticMesh(InStaticMesh) , LODIndex(InLODIndex) { const IMeshMergeUtilities& MeshMergeUtilities = FModuleManager::Get().LoadModuleChecked("MeshMergeUtilities").GetUtilities(); FStaticMeshAttributes(Description).Register(); if (StaticMeshComponent != nullptr) { //MeshMergeUtilities.RetrieveMeshDescription (for StaticMeshComponent) uses bApplyComponentTransform==true, // however MeshData->Description is only used for DegenerateVertices/Triangles and UV checks, and material baking. MeshMergeUtilities.RetrieveMeshDescription(StaticMeshComponent, LODIndex, Description, true); constexpr int32 LightMapLODIndex = 0; // TODO: why is this zero? if (StaticMeshComponent->LODData.IsValidIndex(LightMapLODIndex)) { const FStaticMeshComponentLODInfo& LODData = StaticMeshComponent->LODData[LightMapLODIndex]; const FMeshMapBuildData* BuildData = StaticMeshComponent->GetMeshMapBuildData(LODData); if (BuildData != nullptr) { LightMap = BuildData->LightMap; LightMapResourceCluster = BuildData->ResourceCluster; } } } else { MeshMergeUtilities.RetrieveMeshDescription(StaticMesh, LODIndex, Description); } LightMapTexCoord = StaticMesh->GetLightMapCoordinateIndex(); const int32 NumTexCoords = StaticMesh->GetLODForExport(LODIndex).VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(); BakeUsingTexCoord = FMath::Min(LightMapTexCoord, NumTexCoords - 1); } FFbxMaterialBakingMeshData::FFbxMaterialBakingMeshData(const USkeletalMesh* InSkeletalMesh, const USkeletalMeshComponent* SkeletalMeshComponent, int32 InLODIndex) : bHasMeshData(true) , SkeletalMesh(InSkeletalMesh) , LODIndex(InLODIndex) { const IMeshMergeUtilities& MeshMergeUtilities = FModuleManager::Get().LoadModuleChecked("MeshMergeUtilities").GetUtilities(); FStaticMeshAttributes(Description).Register(); if (SkeletalMeshComponent != nullptr) { MeshMergeUtilities.RetrieveMeshDescription(SkeletalMeshComponent, LODIndex, Description, true); } else { // NOTE: this is a workaround for the fact that there's no overload for FMeshMergeHelpers::RetrieveMesh // that accepts a USkeletalMesh, only a USkeletalMeshComponent. // Writing a custom utility function that would work on a "standalone" skeletal mesh is problematic // since we would need to implement an equivalent of USkinnedMeshComponent::GetCPUSkinnedVertices too. if (UWorld* World = GEditor->GetEditorWorldContext().World()) { FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; SpawnParams.ObjectFlags |= RF_Transient; SpawnParams.bAllowDuringConstructionScript = true; if (AActor* TempActor = World->SpawnActor(SpawnParams)) { USkeletalMeshComponent* TempComponent = NewObject(TempActor, TEXT(""), RF_Transient); TempComponent->RegisterComponent(); TempComponent->SetSkeletalMesh(const_cast(SkeletalMesh)); MeshMergeUtilities.RetrieveMeshDescription(TempComponent, LODIndex, Description, true); World->DestroyActor(TempActor, false, false); } } } // TODO: don't assume last UV channel is non-overlapping const int32 NumTexCoords = SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].StaticVertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(); BakeUsingTexCoord = NumTexCoords - 1; } TArray FFbxMaterialBakingMeshData::GetSectionIndices(const int32& MaterialIndex) const { if (StaticMesh) { return FbxMaterialExportHelpers::GetSectionIndices(StaticMesh, LODIndex, MaterialIndex); } if (SkeletalMesh) { return FbxMaterialExportHelpers::GetSectionIndices(SkeletalMesh, LODIndex, MaterialIndex); } return TArray(); } int32 FFbxMaterialBakingMeshData::GetUModelStaticMeshMaterialIndex(const UMaterialInterface* MaterialInterface) const { //UModel to StaticMesh generation usaes the MaterialInterface's FName to set the Slot naming: return StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(MaterialInterface->GetFName()); } namespace FFbxMaterialExportUtilities { void BakeMaterialProperty(const UFbxExportOption* FbxExportOptions, fbxsdk::FbxScene* Scene, fbxsdk::FbxSurfaceMaterial* FbxMaterial, const char* FbxPropertyName, const FMaterialPropertyEx& Property, const UMaterialInterface* Material, const int32& MaterialIndex, const FFbxMaterialBakingMeshData& MeshData, const FString& ExportFolderPath) { using namespace FbxMaterialExportHelpers; const FIntPoint DefaultBakeSize = FIntPoint(512, 512); const FBox2f TexCoordBounds = FBox2f{ { 0.0f, 0.0f }, { 1.0f, 1.0f } }; const bool bFillAlpha = true; if (FbxExportOptions->BakeMaterialInputs == EFbxMaterialBakeMode::Disabled) { return; } fbxsdk::FbxProperty FbxColorProperty = FbxMaterial->FindProperty(FbxPropertyName); if (!FbxColorProperty.IsValid()) { return; } FIntPoint BakeSize = FbxExportOptions->DefaultMaterialBakeSize.bAutoDetect ? TryGetMaxTextureSize(Material, Property, DefaultBakeSize) : FbxExportOptions->DefaultMaterialBakeSize.Size; if (BakeSize == FIntPoint(0, 0)) { return; } bool bNeedsMeshData = FbxExportOptions->BakeMaterialInputs == EFbxMaterialBakeMode::UseMeshData && MeshData.bHasMeshData && NeedsMeshDataForProperty(Material, Property); TArray TexCoords; GetAllTextureCoordinateIndices(Material, Property, TexCoords); int32 TexCoordIndex = bNeedsMeshData ? MeshData.BakeUsingTexCoord : (TexCoords.Num() > 0 ? TexCoords[0] : 0); FMeshData MeshSet; MeshSet.TextureCoordinateBox = FBox2D(TexCoordBounds); MeshSet.TextureCoordinateIndex = TexCoordIndex; MeshSet.MaterialIndices = MeshData.GetSectionIndices(MaterialIndex); // NOTE: MaterialIndices is actually section indices if (bNeedsMeshData) { MeshSet.MeshDescription = const_cast(&MeshData.Description); MeshSet.LightMap = MeshData.LightMap; MeshSet.LightMapIndex = MeshData.LightMapTexCoord; MeshSet.LightmapResourceCluster = MeshData.LightMapResourceCluster; MeshSet.PrimitiveData = MeshData.StaticMeshComponent ? FPrimitiveData(MeshData.StaticMeshComponent) : (MeshData.StaticMesh ? FPrimitiveData(MeshData.StaticMesh) : FPrimitiveData(MeshData.SkeletalMesh)); } FMaterialDataEx MatSet; MatSet.Material = const_cast(Material); MatSet.PropertySizes.Add(Property, BakeSize); MatSet.bTangentSpaceNormal = true; TArray MeshSettings; TArray MatSettings; MeshSettings.Add(&MeshSet); MatSettings.Add(&MatSet); TArray BakeOutputs; IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked("MaterialBaking"); Module.SetLinearBake(true); Module.BakeMaterials(MatSettings, MeshSettings, BakeOutputs); const bool bIsLinearBake = Module.IsLinearBake(Property); Module.SetLinearBake(false); FBakeOutputEx& BakeOutput = BakeOutputs[0]; TArray BakedPixels = MoveTemp(BakeOutput.PropertyData.FindChecked(Property)); const FIntPoint BakedSize = BakeOutput.PropertySizes.FindChecked(Property); const float EmissiveScale = BakeOutput.EmissiveScale; if (bFillAlpha) { // NOTE: alpha is 0 by default after baking a property, but we prefer 255 (1.0). // It makes it easier to view the exported textures. for (FColor& Pixel : BakedPixels) { Pixel.A = 255; } } if (Property == MP_EmissiveColor) { bool bFromSRGB = !bIsLinearBake; bool bToSRGB = true; TransformColorSpace(BakedPixels, bFromSRGB, true); } //Save Out: const void* InRawData = BakedPixels.GetData(); const int64 ByteLength = BakedSize.X * BakedSize.Y * sizeof(FColor); int64 InRawSize = ByteLength; int32 InWidth = BakedSize.X; int32 InHeight = BakedSize.Y; ERGBFormat InRGBFormat = ERGBFormat::BGRA; int32 InBitDepth = 8; EImageFormat InCompressionFormat = EImageFormat::PNG; int32 InCompressionQuality = 0; TArray64 OutCompressedData; IImageWrapperModule& ImageWrapperModule = FModuleManager::Get().LoadModuleChecked(TEXT("ImageWrapper")); const TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(InCompressionFormat); if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(InRawData, InRawSize, InWidth, InHeight, InRGBFormat, InBitDepth)) { const TArray64& CompressedData = ImageWrapper->GetCompressed(InCompressionQuality); FString FileName = Material->GetName() + TEXT("_") + Property.ToString() + TEXT("_") + FString::FromInt(MaterialIndex) + TEXT(".png"); FString FilePath = ExportFolderPath + TEXT("/") + FileName; if (FFileHelper::SaveArrayToFile(CompressedData, *FilePath)) { FbxFileTexture* lTexture = FbxFileTexture::Create(Scene, "EnvSamplerTex"); lTexture->SetFileName(TCHAR_TO_UTF8(*FilePath)); //SetRelativeFileName does not seem to work? lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->ConnectDstProperty(FbxColorProperty); } } } bool GetInterchangeShadingModel(const UMaterialInterface* MaterialInterface, bool& bLambert) { using namespace FbxMaterialExportHelpers::InterchangeMaterialProcessHelpers; FString PathName = MaterialInterface->GetMaterial()->GetPathName(); if (MaterialInterface->IsA(UMaterialInstance::StaticClass())) { if (PathName == LambertSurfaceMaterialPath || PathName == PhongSurfaceMaterialPath) { bLambert = PathName == LambertSurfaceMaterialPath; return true; } } else { //Acquire the Material's expressions and check if any matches the MF_PhongToMetalRoughness MaterialFunctionCall: TConstArrayView> MaterialExpressions = MaterialInterface->GetMaterial()->GetExpressions(); bool MFPhongUsed = false; for (const TObjectPtr& MaterialExpression : MaterialExpressions) { if (UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast(MaterialExpression)) { if (FunctionCallExpression->MaterialFunction) { FString MF_PathName = FunctionCallExpression->MaterialFunction->GetPathName(); if (MF_PhongToMetalRoughnessPathName == MF_PathName) { MFPhongUsed = true; break; } } } } bLambert = !MFPhongUsed; return MFPhongUsed || (MaterialInterface->AssetImportData.IsA(UInterchangeAssetImportData::StaticClass())); } return false; } void ProcessInterchangeMaterials(UMaterialInterface* MaterialInterface, fbxsdk::FbxScene* Scene, fbxsdk::FbxSurfaceMaterial* FbxMaterial) { using namespace FbxMaterialExportHelpers::InterchangeMaterialProcessHelpers; FString PathName = MaterialInterface->GetMaterial()->GetPathName(); if (MaterialInterface->IsA(UMaterialInstance::StaticClass())) { if (PathName == LambertSurfaceMaterialPath || PathName == PhongSurfaceMaterialPath) { bool bSpecularToBeBaked = false; //also indicates to bake the BaseColor and Metallic if (PathName == PhongSurfaceMaterialPath) { //SpecularColor //TEXT("SpecularColor"); //TEXT("SpecularColorMap"); //TEXT("SpecularColorMapWeight"); if (!HandleMaterialProperty(MaterialInterface, Scene, FbxMaterial, TEXT("SpecularColor"), MP_Specular, FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor)) { bSpecularToBeBaked = true; } //Roughness //TEXT("Shininess"); //TEXT("ShininessMap"); //TEXT("ShininessMapWeight"); HandleMaterialProperty(MaterialInterface, Scene, FbxMaterial, TEXT("Shininess"), MP_Roughness, FbxSurfaceMaterial::sShininess, "NoFactorForThisProperty", false); } //Diffuse //TEXT("DiffuseColor"); //TEXT("DiffuseColorMap"); //TEXT("DiffuseColorMapWeight"); if (!bSpecularToBeBaked) HandleMaterialProperty(MaterialInterface, Scene, FbxMaterial, TEXT("DiffuseColor"), MP_BaseColor, FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor); //Emissive //TEXT("EmissiveColorMap"); //TEXT("EmissiveColorMapWeight"); //TEXT("EmissiveColor"); HandleMaterialProperty(MaterialInterface, Scene, FbxMaterial, TEXT("EmissiveColor"), MP_EmissiveColor, FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor); //Normal //TEXT("NormalMap"); //TEXT("NormalMapWeight"); HandleMaterialProperty(MaterialInterface, Scene, FbxMaterial, TEXT("Normal"), MP_Normal, FbxSurfaceMaterial::sNormalMap, "NoFactorForThisProperty"); //AmbientOcclusion is NOT AmbientColor, FbxSurfaceMaterail supports AmbientColor! // Meaning Interchange's Lambert and Phong Surface Materials have no support for AmbientColor //TEXT("AmbientOcclusionMap"); //TEXT("AmbientOcclusionMapWeight"); //HandleProperty(TEXT("AmbientOcclusion"), MP_AmbientOcclusion, FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor); } } else //!MaterialInstance (==Material) { //Acquire the Material's expressions and check if any matches the MF_PhongToMetalRoughness MaterialFunctionCall: UMaterialExpressionMaterialFunctionCall* ImportedMF_PhongToMetalRoughness = GetInterchangeMFPongToMetalRoughness(MaterialInterface); if (ImportedMF_PhongToMetalRoughness) { TArrayView InputsView = ImportedMF_PhongToMetalRoughness->GetInputsView(); for (FExpressionInput* Input : InputsView) { if (!Input || !Input->Expression || !Input->IsConnected()) { continue; } FString Name = Input->InputName.ToString(); UMaterialExpression* InputExpression = Input->GetTracedInput().Expression; if (Name == TEXT("AmbientColor")) { //Always set the ambient to zero since we dont have ambient in unreal we want to avoid default value in DCCs ((FbxSurfaceLambert*)FbxMaterial)->Ambient.Set(FbxDouble3(0.0, 0.0, 0.0)); //Overwrite with Interchange values: HandleMaterialExpression(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor, InputExpression); } else if (Name == TEXT("DiffuseColor")) { HandleMaterialExpression(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor, InputExpression); } else if (Name == TEXT("SpecularColor")) { HandleMaterialExpression(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor, InputExpression); } else if (Name == TEXT("Shininess")) { HandleMaterialExpression(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sShininess, "NoFactorForThisProperty", InputExpression); } } } if (MaterialInterface->AssetImportData.IsA(UInterchangeAssetImportData::StaticClass())) { UMaterial* Material = MaterialInterface->GetMaterial(); if (ImportedMF_PhongToMetalRoughness == nullptr) { //AmbientColor // Only supported via MF_PhongToMetalRoughness //Diffuse HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor, Material->GetExpressionInputForProperty(MP_BaseColor)); //Specular HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor, Material->GetExpressionInputForProperty(MP_Specular)); //Shininess // Only supported via MF_PhongToMetalRoughness } //Normal HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sNormalMap, "NoFactorForThisProperty", Material->GetExpressionInputForProperty(MP_Normal)); //Emissive HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor, Material->GetExpressionInputForProperty(MP_EmissiveColor)); UMaterialInstance* MaterialInstance = Cast(MaterialInterface); EBlendMode BlendMode = MaterialInstance != nullptr && MaterialInstance->BasePropertyOverrides.bOverride_BlendMode ? MaterialInstance->BasePropertyOverrides.BlendMode : Material->BlendMode; if (BlendMode == BLEND_Translucent) { //Opacity HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sTransparentColor, "NoFactorForThisProperty", Material->GetExpressionInputForProperty(MP_Opacity)); //OpacityMask HandleExpressionInput(MaterialInterface, Scene, FbxMaterial, FbxSurfaceMaterial::sTransparencyFactor, "NoFactorForThisProperty", Material->GetExpressionInputForProperty(MP_OpacityMask)); } } } } } }