// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "HAL/FileManager.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "Misc/PackageName.h" #include "Materials/MaterialInterface.h" #include "MaterialExpressionIO.h" #include "Materials/Material.h" #include "Factories/MaterialFactoryNew.h" #include "Engine/Texture.h" #include "Factories/TextureFactory.h" #include "Engine/Texture2D.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Materials/MaterialExpressionTextureCoordinate.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialInstanceConstant.h" #include "Factories/MaterialInstanceConstantFactoryNew.h" #include "FbxImporter.h" #include "ObjectTools.h" #include "PackageTools.h" #include "AssetRegistry/AssetRegistryModule.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "Misc/FbxErrors.h" #include "AssetRegistry/ARFilter.h" #include "Factories/MaterialImportHelpers.h" #include "MaterialEditingLibrary.h" #include "Engine/RendererSettings.h" DEFINE_LOG_CATEGORY_STATIC(LogFbxMaterialImport, Log, All); #define LOCTEXT_NAMESPACE "FbxMaterialImport" using namespace UnFbx; UTexture* UnFbx::FFbxImporter::ImportTexture(FbxFileTexture* FbxTexture, bool bSetupAsNormalMap) { if (!FbxTexture || !CanImportClass(UTexture2D::StaticClass())) { return nullptr; } // create an unreal texture asset UTexture* UnrealTexture = NULL; FString AbsoluteFilename = UTF8_TO_TCHAR(FbxTexture->GetFileName()); FString Extension = FPaths::GetExtension(AbsoluteFilename).ToLower(); // name the texture with file name FString TextureName; if (FString* UniqueName = FbxTextureToUniqueNameMap.Find(FbxTexture)) { TextureName = *UniqueName; } else { TextureName = FPaths::GetBaseFilename(AbsoluteFilename); TextureName = ObjectTools::SanitizeObjectName(TextureName); } // set where to place the textures FString BasePackageName = FPackageName::GetLongPackagePath(Parent->GetOutermost()->GetName()) / TextureName; BasePackageName = UPackageTools::SanitizePackageName(BasePackageName); UTexture* ExistingTexture = NULL; UPackage* TexturePackage = NULL; // First check if the asset already exists. { FString ObjectPath = BasePackageName + TEXT(".") + TextureName; ExistingTexture = LoadObject(NULL, *ObjectPath, nullptr, LOAD_Quiet | LOAD_NoWarn); } if (!ExistingTexture) { const FString Suffix(TEXT("")); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); FString FinalPackageName; AssetToolsModule.Get().CreateUniqueAssetName(BasePackageName, Suffix, FinalPackageName, TextureName); TexturePackage = CreatePackage( *FinalPackageName); } else { TexturePackage = ExistingTexture->GetOutermost(); } FString FinalFilePath; if (IFileManager::Get().FileExists(*AbsoluteFilename)) { // try opening from absolute path FinalFilePath = AbsoluteFilename; } else if (IFileManager::Get().FileExists(*(FileBasePath / UTF8_TO_TCHAR(FbxTexture->GetRelativeFileName())))) { // try fbx file base path + relative path FinalFilePath = FileBasePath / UTF8_TO_TCHAR(FbxTexture->GetRelativeFileName()); } else if (IFileManager::Get().FileExists(*(FileBasePath / AbsoluteFilename))) { // Some fbx files dont store the actual absolute filename as absolute and it is actually relative. Try to get it relative to the FBX file we are importing FinalFilePath = FileBasePath / AbsoluteFilename; } else { UE_LOG(LogFbxMaterialImport, Display, TEXT("Unable to find Texture file %s"), *AbsoluteFilename); } TArray DataBinary; if (!FinalFilePath.IsEmpty()) { FFileHelper::LoadFileToArray(DataBinary, *FinalFilePath); } if (DataBinary.Num()>0) { UE_LOG(LogFbxMaterialImport, Verbose, TEXT("Loading texture file %s"),*FinalFilePath); const uint8* PtrTexture = DataBinary.GetData(); auto TextureFact = NewObject(); TextureFact->AddToRoot(); // save texture settings if texture exist TextureFact->SuppressImportOverwriteDialog(); const TCHAR* TextureType = *Extension; const bool bTextureAssetAlreadyExists = FindObject(TexturePackage, *TextureName) != nullptr; // Unless the normal map setting is used during import, // the user has to manually hit "reimport" then "recompress now" button if ( bSetupAsNormalMap ) { if (!ExistingTexture) { TextureFact->LODGroup = TEXTUREGROUP_WorldNormalMap; TextureFact->CompressionSettings = TC_Normalmap; TextureFact->bFlipNormalMapGreenChannel = ImportOptions->bInvertNormalMap; } else { UE_LOG(LogFbxMaterialImport, Warning, TEXT("Manual texture reimport and recompression may be needed for %s"), *TextureName); } } UnrealTexture = (UTexture*)TextureFact->FactoryCreateBinary( UTexture2D::StaticClass(), TexturePackage, *TextureName, RF_Standalone|RF_Public, NULL, TextureType, PtrTexture, PtrTexture+DataBinary.Num(), GWarn ); if ( UnrealTexture != NULL ) { if ( !bTextureAssetAlreadyExists ) { //This asset did not override any other asset during its creation, so it is considered as newly created. CreatedObjects.Add(UnrealTexture); } //Make sure the AssetImportData point on the texture file and not on the fbx files since the factory point on the fbx file UnrealTexture->AssetImportData->Update(IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FinalFilePath)); // Notify the asset registry FAssetRegistryModule::AssetCreated(UnrealTexture); // Set the dirty flag so this package will get saved later TexturePackage->SetDirtyFlag(true); } TextureFact->RemoveFromRoot(); } return UnrealTexture; } void UnFbx::FFbxImporter::ImportTexturesFromNode(FbxNode* Node) { FbxProperty Property; int32 NbMat = Node->GetMaterialCount(); // visit all materials int32 MaterialIndex; for (MaterialIndex = 0; MaterialIndex < NbMat; MaterialIndex++) { FbxSurfaceMaterial *Material = Node->GetMaterial(MaterialIndex); //go through all the possible textures if(Material) { int32 TextureIndex; FBXSDK_FOR_EACH_TEXTURE(TextureIndex) { Property = Material->FindProperty(FbxLayerElement::sTextureChannelNames[TextureIndex]); if( Property.IsValid() ) { FbxTexture * lTexture= NULL; //Here we have to check if it's layered textures, or just textures: int32 LayeredTextureCount = Property.GetSrcObjectCount(); FbxString PropertyName = Property.GetName(); if(LayeredTextureCount > 0) { for(int32 LayerIndex=0; LayerIndex(LayerIndex); int32 NbTextures = lLayeredTexture->GetSrcObjectCount(); for(int32 TexIndex =0; TexIndexGetSrcObject(TexIndex); if(Texture) { ImportTexture(Texture, PropertyName == FbxSurfaceMaterial::sNormalMap || PropertyName == FbxSurfaceMaterial::sBump); } } } } else { //no layered texture simply get on the property int32 NbTextures = Property.GetSrcObjectCount(); for(int32 TexIndex =0; TexIndex(TexIndex); if(Texture) { ImportTexture(Texture, PropertyName == FbxSurfaceMaterial::sNormalMap || PropertyName == FbxSurfaceMaterial::sBump); } } } } } }//end if(Material) }// end for MaterialIndex } //------------------------------------------------------------------------- // //------------------------------------------------------------------------- //Enable debug log of fbx material properties, this will log all material properties that are in the FBX file #define DEBUG_LOG_FBX_MATERIAL_PROPERTIES 0 #if DEBUG_LOG_FBX_MATERIAL_PROPERTIES void LogPropertyAndChild(FbxSurfaceMaterial& FbxMaterial, const FbxProperty &Property) { FbxString PropertyName = Property.GetHierarchicalName(); UE_LOG(LogFbxMaterialImport, Display, TEXT("Property Name [%s]"), UTF8_TO_TCHAR(PropertyName.Buffer())); int32 TextureCount = Property.GetSrcObjectCount(); if (TextureCount > 0) { for (int32 TextureIndex = 0; TextureIndex < TextureCount; ++TextureIndex) { FbxFileTexture* TextureObj = Property.GetSrcObject(TextureIndex); if (TextureObj != nullptr) { UE_LOG(LogFbxMaterialImport, Display, TEXT("Texture Path [%s]"), UTF8_TO_TCHAR(TextureObj->GetFileName())); } } } const FbxProperty &NextProperty = FbxMaterial.GetNextProperty(Property); if (NextProperty.IsValid()) { LogPropertyAndChild(FbxMaterial, NextProperty); } } #endif bool UnFbx::FFbxImporter::CreateAndLinkExpressionForMaterialProperty( const FbxSurfaceMaterial& FbxMaterial, UMaterial* UnrealMaterial, const char* MaterialProperty , FExpressionInput& MaterialInput, bool bSetupAsNormalMap, TArray& UVSet, const FVector2D& Location) { bool bCreated = false; FbxProperty FbxProperty = FbxMaterial.FindProperty( MaterialProperty ); if( FbxProperty.IsValid() ) { int32 UnsupportedTextureCount = FbxProperty.GetSrcObjectCount(); UnsupportedTextureCount += FbxProperty.GetSrcObjectCount(); if (UnsupportedTextureCount>0) { UE_LOG(LogFbxMaterialImport, Warning,TEXT("Layered or procedural Textures are not supported (material %s)"),UTF8_TO_TCHAR(FbxMaterial.GetName())); } else { int32 TextureCount = FbxProperty.GetSrcObjectCount(); if (TextureCount>0) { const bool bEnableVirtualTextureOpacityMask = GetDefault()->bEnableVirtualTextureOpacityMask; for(int32 TextureIndex =0; TextureIndex(TextureIndex); // create an unreal texture asset UTexture* UnrealTexture = ImportTexture(FbxTexture, bSetupAsNormalMap); if (UnrealTexture) { float ScaleU = FbxTexture->GetScaleU(); float ScaleV = FbxTexture->GetScaleV(); bool bIsVirtualTexture = UnrealTexture->VirtualTextureStreaming; if (bIsVirtualTexture && MaterialProperty == FbxSurfaceMaterial::sTransparencyFactor && !bEnableVirtualTextureOpacityMask) { //Virtual textures are not supported in the OpacityMask slot, convert any textures back to a regular texture. //TODO, add a tracking of the materials created during the import so we can refresh here them if the TextureFactory actually found a existing asset instead of creating a new one. if (UTexture2D* UnrealTexture2D = Cast(UnrealTexture)) { TArray TexturesToConvert = { UnrealTexture2D }; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); AssetToolsModule.Get().ConvertVirtualTextures(TexturesToConvert, true); } else { UnrealTexture->VirtualTextureStreaming = false; } bIsVirtualTexture = false; FString TextureName = UnrealTexture->GetName(); UE_LOG(LogFbxMaterialImport, Warning, TEXT("Texture %s could not be imported as a Virtual Texture because the project renderer settings disable virtual textures in OpacityMask property (material %s)."), *TextureName, UTF8_TO_TCHAR(FbxMaterial.GetName())); } // and link it to the material UMaterialExpressionTextureSample* UnrealTextureExpression = NewObject(UnrealMaterial); UnrealMaterial->GetExpressionCollection().AddExpression( UnrealTextureExpression ); MaterialInput.Expression = UnrealTextureExpression; UnrealTextureExpression->Texture = UnrealTexture; UnrealTextureExpression->SamplerType = bSetupAsNormalMap ? (bIsVirtualTexture ? SAMPLERTYPE_VirtualNormal : SAMPLERTYPE_Normal) : (bIsVirtualTexture ? SAMPLERTYPE_VirtualColor : SAMPLERTYPE_Color); UnrealTextureExpression->MaterialExpressionEditorX = FMath::TruncToInt(Location.X); UnrealTextureExpression->MaterialExpressionEditorY = FMath::TruncToInt(Location.Y); // add/find UVSet and set it to the texture FbxString UVSetName = FbxTexture->UVSet.Get(); FString LocalUVSetName = UTF8_TO_TCHAR(UVSetName.Buffer()); if (LocalUVSetName.IsEmpty()) { LocalUVSetName = TEXT("UVmap_0"); } int32 SetIndex = UVSet.Find(LocalUVSetName); if( (SetIndex != 0 && SetIndex != INDEX_NONE) || ScaleU != 1.0f || ScaleV != 1.0f ) { // Create a texture coord node for the texture sample UMaterialExpressionTextureCoordinate* MyCoordExpression = NewObject(UnrealMaterial); UnrealMaterial->GetExpressionCollection().AddExpression(MyCoordExpression); MyCoordExpression->CoordinateIndex = (SetIndex >= 0) ? SetIndex : 0; MyCoordExpression->UTiling = ScaleU; MyCoordExpression->VTiling = ScaleV; UnrealTextureExpression->Coordinates.Expression = MyCoordExpression; MyCoordExpression->MaterialExpressionEditorX = FMath::TruncToInt(Location.X-175); MyCoordExpression->MaterialExpressionEditorY = FMath::TruncToInt(Location.Y); } bCreated = true; } } } if (MaterialInput.Expression) { TArray Outputs = MaterialInput.Expression->GetOutputs(); FExpressionOutput* Output = Outputs.GetData(); MaterialInput.Mask = Output->Mask; MaterialInput.MaskR = Output->MaskR; MaterialInput.MaskG = Output->MaskG; MaterialInput.MaskB = Output->MaskB; MaterialInput.MaskA = Output->MaskA; } } } return bCreated; } //------------------------------------------------------------------------- // //------------------------------------------------------------------------- void UnFbx::FFbxImporter::FixupMaterial( const FbxSurfaceMaterial& FbxMaterial, UMaterial* UnrealMaterial ) { UMaterialEditorOnlyData* UnrealMaterialEditorOnly = UnrealMaterial->GetEditorOnlyData(); // add a basic diffuse color if no texture is linked to diffuse if (UnrealMaterialEditorOnly->BaseColor.Expression == NULL) { FbxDouble3 DiffuseColor; UMaterialExpressionVectorParameter* MyColorExpression = NewObject(UnrealMaterial); UnrealMaterial->GetExpressionCollection().AddExpression( MyColorExpression ); UnrealMaterialEditorOnly->BaseColor.Expression = MyColorExpression; bool bFoundDiffuseColor = true; if( FbxMaterial.GetClassId().Is(FbxSurfacePhong::ClassId) ) { DiffuseColor = ((FbxSurfacePhong&)(FbxMaterial)).Diffuse.Get(); } else if( FbxMaterial.GetClassId().Is(FbxSurfaceLambert::ClassId) ) { DiffuseColor = ((FbxSurfaceLambert&)(FbxMaterial)).Diffuse.Get(); } else { bFoundDiffuseColor = false; } if( bFoundDiffuseColor ) { MyColorExpression->DefaultValue.R = (float)(DiffuseColor[0]); MyColorExpression->DefaultValue.G = (float)(DiffuseColor[1]); MyColorExpression->DefaultValue.B = (float)(DiffuseColor[2]); } else { // use random color because there may be multiple materials, then they can be different MyColorExpression->DefaultValue.R = 0.5f+(0.5f*FMath::Rand()) / static_cast(RAND_MAX); MyColorExpression->DefaultValue.G = 0.5f+(0.5f*FMath::Rand()) / static_cast(RAND_MAX); MyColorExpression->DefaultValue.B = 0.5f+(0.5f*FMath::Rand()) / static_cast(RAND_MAX); } TArray Outputs = UnrealMaterialEditorOnly->BaseColor.Expression->GetOutputs(); FExpressionOutput* Output = Outputs.GetData(); UnrealMaterialEditorOnly->BaseColor.Mask = Output->Mask; UnrealMaterialEditorOnly->BaseColor.MaskR = Output->MaskR; UnrealMaterialEditorOnly->BaseColor.MaskG = Output->MaskG; UnrealMaterialEditorOnly->BaseColor.MaskB = Output->MaskB; UnrealMaterialEditorOnly->BaseColor.MaskA = Output->MaskA; } } //------------------------------------------------------------------------- // //------------------------------------------------------------------------- FString UnFbx::FFbxImporter::GetMaterialFullName(const FbxSurfaceMaterial& FbxMaterial) const { FString MaterialFullName = MakeName(FbxMaterial.GetName()); if (MaterialFullName.Len() > 6) { int32 Offset = MaterialFullName.Find(TEXT("_SKIN"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); if (Offset != INDEX_NONE) { // Chop off the material name so we are left with the number in _SKINXX FString SkinXXNumber = MaterialFullName.Right(MaterialFullName.Len() - (Offset + 1)).RightChop(4); if (SkinXXNumber.IsNumeric()) { // remove the '_skinXX' suffix from the material name MaterialFullName.LeftChopInline(MaterialFullName.Len() - Offset, EAllowShrinking::No); } } } else if (MaterialFullName.IsEmpty()) { MaterialFullName = TEXT("UnnamedMaterial"); } MaterialFullName = ObjectTools::SanitizeObjectName(MaterialFullName); return MaterialFullName; } FString UnFbx::FFbxImporter::GetMaterialBasePackageName(const FString& MaterialFullName) const { FString BasePackageName = FPackageName::GetLongPackagePath(Parent->GetOutermost()->GetName()); if (ImportOptions->MaterialBasePath != NAME_None) { BasePackageName = ImportOptions->MaterialBasePath.ToString(); } else { BasePackageName += TEXT("/"); } BasePackageName += MaterialFullName; BasePackageName = UPackageTools::SanitizePackageName(BasePackageName); return BasePackageName; } bool UnFbx::FFbxImporter::LinkMaterialProperty( const FbxSurfaceMaterial& FbxMaterial, UMaterialInstanceConstant* UnrealMaterial, const char* MaterialProperty, FName ParameterValue, bool bSetupAsNormalMap) { bool bCreated = false; FbxProperty FbxProperty = FbxMaterial.FindProperty(MaterialProperty); if (FbxProperty.IsValid()) { int32 LayeredTextureCount = FbxProperty.GetSrcObjectCount(); if (LayeredTextureCount > 0) { UE_LOG(LogFbxMaterialImport, Warning, TEXT("Layered Textures are not supported (material %s)"), UTF8_TO_TCHAR(FbxMaterial.GetName())); } else { int32 TextureCount = FbxProperty.GetSrcObjectCount(); if (TextureCount > 0) { for (int32 TextureIndex = 0; TextureIndex < TextureCount; ++TextureIndex) { FbxFileTexture* FbxTexture = FbxProperty.GetSrcObject(TextureIndex); // create an unreal texture asset UTexture* UnrealTexture = ImportTexture(FbxTexture, bSetupAsNormalMap); if (UnrealTexture) { UnrealMaterial->SetTextureParameterValueEditorOnly(ParameterValue, UnrealTexture); bCreated = true; } } } } } return bCreated; } bool CanUseMaterialWithInstance(const FbxSurfaceMaterial& FbxMaterial, const char* MaterialProperty, FString ParameterValueName, UMaterialInterface *BaseMaterial, TArray& UVSet) { FbxProperty FbxProperty = FbxMaterial.FindProperty(MaterialProperty); if (FbxProperty.IsValid()) { int32 LayeredTextureCount = FbxProperty.GetSrcObjectCount(); if (LayeredTextureCount == 0) { int32 TextureCount = FbxProperty.GetSrcObjectCount(); if (TextureCount == 1) { // If we didnt specify a parameter to go with this property we can't use this as base instance if (ParameterValueName.IsEmpty()) { return false; } if (FbxFileTexture* FbxTexture = FbxProperty.GetSrcObject(0)) { float ScaleU = FbxTexture->GetScaleU(); float ScaleV = FbxTexture->GetScaleV(); FbxString UVSetName = FbxTexture->UVSet.Get(); FString LocalUVSetName = UTF8_TO_TCHAR(UVSetName.Buffer()); int32 SetIndex = UVSet.Find(LocalUVSetName); if ((SetIndex != 0 && SetIndex != INDEX_NONE) || ScaleU != 1.0f || ScaleV != 1.0f) { return false; // no support for custom uv with instanced yet } } } else if (TextureCount > 1) { return false; // no support for multiple textures } } else { return false; // no support for layered textures } } return true; } UMaterialInterface* UnFbx::FFbxImporter::FindExistingMaterialFromFbxMaterial(const FbxSurfaceMaterial& FbxMaterial, EMaterialSearchLocation MaterialSearchLocation) { // Make sure we have a parent if (!ensure(Parent.IsValid())) { return nullptr; } if (UMaterialInterface** OverrideMaterial = ImportOptions->OverrideMaterials.Find(FbxMaterial.GetUniqueID())) { if (!ImportedMaterialData.IsAlreadyImported(FbxMaterial, FName(*(*OverrideMaterial)->GetPathName()))) { // Add the material override to the list of imported materials ImportedMaterialData.AddImportedMaterial(FbxMaterial, **OverrideMaterial); } return *OverrideMaterial; } const FString MaterialFullName = GetMaterialFullName(FbxMaterial); const FString BasePackageName = GetMaterialBasePackageName(MaterialFullName); const FName ObjectPath = *(BasePackageName + TEXT(".") + MaterialFullName); // The material could already exist in the project UMaterialInterface* FoundMaterial = nullptr; if (ImportedMaterialData.IsAlreadyImported(FbxMaterial, ObjectPath)) { FoundMaterial = ImportedMaterialData.GetUnrealMaterial(FbxMaterial); } else { FText Error; FoundMaterial = UMaterialImportHelpers::FindExistingMaterialFromSearchLocation(MaterialFullName, BasePackageName, MaterialSearchLocation, Error); if (!Error.IsEmpty()) { AddTokenizedErrorMessage( FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FbxMaterialImport_MultipleMaterialsFound", "While importing '{0}': {1}"), FText::FromString(Parent->GetOutermost()->GetName()), Error)), FFbxErrors::Generic_LoadingSceneFailed); } if (FoundMaterial) { ImportedMaterialData.AddImportedMaterial(FbxMaterial, *FoundMaterial); } } return FoundMaterial; } UMaterialInterface* UnFbx::FFbxImporter::CreateUnrealMaterial(const FbxSurfaceMaterial& FbxMaterial, TArray& OutUVSets, bool bForSkeletalMesh) { // Make sure we have a parent if ( !ensure(Parent.IsValid()) ) { return nullptr; } // Check if we can use the specified base material to instance from it FBXImportOptions* FbxImportOptions = GetImportOptions(); bool bCanInstance = false; if (FbxImportOptions->BaseMaterial) { // try to use the material as a base for the new material to instance from FbxProperty FbxDiffuseProperty = FbxMaterial.FindProperty(FbxSurfaceMaterial::sDiffuse); if (FbxDiffuseProperty.IsValid()) { bCanInstance = CanUseMaterialWithInstance(FbxMaterial, FbxSurfaceMaterial::sDiffuse, FbxImportOptions->BaseDiffuseTextureName, FbxImportOptions->BaseMaterial, OutUVSets); } else { bCanInstance = !FbxImportOptions->BaseColorName.IsEmpty(); } FbxProperty FbxEmissiveProperty = FbxMaterial.FindProperty(FbxSurfaceMaterial::sEmissive); if (FbxEmissiveProperty.IsValid()) { bCanInstance &= CanUseMaterialWithInstance(FbxMaterial, FbxSurfaceMaterial::sEmissive, FbxImportOptions->BaseEmmisiveTextureName, FbxImportOptions->BaseMaterial, OutUVSets); } else { bCanInstance &= !FbxImportOptions->BaseEmissiveColorName.IsEmpty(); } bCanInstance &= CanUseMaterialWithInstance(FbxMaterial, FbxSurfaceMaterial::sSpecular, FbxImportOptions->BaseSpecularTextureName, FbxImportOptions->BaseMaterial, OutUVSets); bCanInstance &= CanUseMaterialWithInstance(FbxMaterial, FbxSurfaceMaterial::sNormalMap, FbxImportOptions->BaseNormalTextureName, FbxImportOptions->BaseMaterial, OutUVSets); bCanInstance &= CanUseMaterialWithInstance(FbxMaterial, FbxSurfaceMaterial::sTransparentColor, FbxImportOptions->BaseOpacityTextureName, FbxImportOptions->BaseMaterial, OutUVSets); } //Make sure we can import the material class if ( bCanInstance ) { if (!CanImportClass(UMaterialInstanceConstant::StaticClass())) { return nullptr; } } else if ( !CanImportClass(UMaterial::StaticClass()) ) { return nullptr; } const FString BasePackageName = GetMaterialBasePackageName(GetMaterialFullName(FbxMaterial)); const FString Suffix(TEXT("")); FString FinalPackageName; FString FinalMaterialName; FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); AssetToolsModule.Get().CreateUniqueAssetName(BasePackageName, Suffix, FinalPackageName, FinalMaterialName); UPackage* Package = CreatePackage(*FinalPackageName); UMaterialInterface* UnrealMaterialFinal = nullptr; if (bCanInstance) { auto MaterialInstanceFactory = NewObject(); MaterialInstanceFactory->InitialParent = FbxImportOptions->BaseMaterial; UMaterialInstanceConstant* UnrealMaterialConstant = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew(UMaterialInstanceConstant::StaticClass(), Package, *FinalMaterialName, RF_Standalone | RF_Public, NULL, GWarn); if (UnrealMaterialConstant != NULL) { CreatedObjects.Add(UnrealMaterialConstant); UnrealMaterialFinal = UnrealMaterialConstant; // Notify the asset registry FAssetRegistryModule::AssetCreated(UnrealMaterialConstant); // Set the dirty flag so this package will get saved later Package->SetDirtyFlag(true); //UnrealMaterialConstant->SetParentEditorOnly(FbxImportOptions->BaseMaterial); // textures and properties bool bDiffuseTextureCreated = LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sDiffuse, FName(*FbxImportOptions->BaseDiffuseTextureName), false); bool bEmissiveTextureCreated = LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sEmissive, FName(*FbxImportOptions->BaseEmmisiveTextureName), false); bool bOpacityTextureCreated = LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sTransparentColor, FName(*FbxImportOptions->BaseOpacityTextureName), false); LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sSpecular, FName(*FbxImportOptions->BaseSpecularTextureName), false); if (!LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sNormalMap, FName(*FbxImportOptions->BaseNormalTextureName), true)) { LinkMaterialProperty(FbxMaterial, UnrealMaterialConstant, FbxSurfaceMaterial::sBump, FName(*FbxImportOptions->BaseNormalTextureName), true); // no bump in unreal, use as normal map } // If we only have colors and its different from the base material if (!bDiffuseTextureCreated) { FbxDouble3 DiffuseColor; bool OverrideColor = false; if (FbxMaterial.GetClassId().Is(FbxSurfacePhong::ClassId)) { DiffuseColor = ((FbxSurfacePhong&)(FbxMaterial)).Diffuse.Get(); OverrideColor = true; } else if (FbxMaterial.GetClassId().Is(FbxSurfaceLambert::ClassId)) { DiffuseColor = ((FbxSurfaceLambert&)(FbxMaterial)).Diffuse.Get(); OverrideColor = true; } if(OverrideColor) { FLinearColor LinearColor((float)DiffuseColor[0], (float)DiffuseColor[1], (float)DiffuseColor[2]); FLinearColor CurrentLinearColor; if (UnrealMaterialConstant->GetVectorParameterValue(FName(*FbxImportOptions->BaseColorName), CurrentLinearColor)) { //Alpha is not consider for diffuse color LinearColor.A = CurrentLinearColor.A; if (!CurrentLinearColor.Equals(LinearColor)) { UnrealMaterialConstant->SetVectorParameterValueEditorOnly(FName(*FbxImportOptions->BaseColorName), LinearColor); } } } } if (!bEmissiveTextureCreated) { FbxDouble3 EmissiveColor; bool OverrideColor = false; if (FbxMaterial.GetClassId().Is(FbxSurfacePhong::ClassId)) { EmissiveColor = ((FbxSurfacePhong&)(FbxMaterial)).Emissive.Get(); OverrideColor = true; } else if (FbxMaterial.GetClassId().Is(FbxSurfaceLambert::ClassId)) { EmissiveColor = ((FbxSurfaceLambert&)(FbxMaterial)).Emissive.Get(); OverrideColor = true; } if (OverrideColor) { FLinearColor LinearColor((float)EmissiveColor[0], (float)EmissiveColor[1], (float)EmissiveColor[2]); FLinearColor CurrentLinearColor; if (UnrealMaterialConstant->GetVectorParameterValue(FName(*FbxImportOptions->BaseEmissiveColorName), CurrentLinearColor)) { //Alpha is not consider for emissive color LinearColor.A = CurrentLinearColor.A; if (!CurrentLinearColor.Equals(LinearColor)) { UnrealMaterialConstant->SetVectorParameterValueEditorOnly(FName(*FbxImportOptions->BaseEmissiveColorName), LinearColor); } } } } if (bOpacityTextureCreated) { FMaterialInstanceBasePropertyOverrides Overrides; Overrides.bOverride_BlendMode = true; Overrides.BlendMode = BLEND_Translucent; UnrealMaterialConstant->BasePropertyOverrides = Overrides; } } } else { // create an unreal material asset auto MaterialFactory = NewObject(); UMaterial* UnrealMaterial = (UMaterial*)MaterialFactory->FactoryCreateNew( UMaterial::StaticClass(), Package, *FinalMaterialName, RF_Standalone|RF_Public, NULL, GWarn ); if (UnrealMaterial != NULL) { CreatedObjects.Add(UnrealMaterial); UnrealMaterialFinal = UnrealMaterial; // Notify the asset registry FAssetRegistryModule::AssetCreated(UnrealMaterial); if(bForSkeletalMesh) { bool bNeedsRecompile = false; UnrealMaterial->GetMaterial()->SetMaterialUsage(bNeedsRecompile, MATUSAGE_SkeletalMesh); } // Set the dirty flag so this package will get saved later Package->SetDirtyFlag(true); // textures and properties #if DEBUG_LOG_FBX_MATERIAL_PROPERTIES const FbxProperty &FirstProperty = FbxMaterial.GetFirstProperty(); if (FirstProperty.IsValid()) { UE_LOG(LogFbxMaterialImport, Display, TEXT("Creating Material [%s]"), UTF8_TO_TCHAR(FbxMaterial.GetName())); LogPropertyAndChild(FbxMaterial, FirstProperty); UE_LOG(LogFbxMaterialImport, Display, TEXT("-------------------------------")); } #endif UMaterialEditorOnlyData* UnrealMaterialEditorOnly = UnrealMaterial->GetEditorOnlyData(); CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sDiffuse, UnrealMaterialEditorOnly->BaseColor, false, OutUVSets, FVector2D(240, -320)); CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sEmissive, UnrealMaterialEditorOnly->EmissiveColor, false, OutUVSets, FVector2D(240, -64)); CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sSpecular, UnrealMaterialEditorOnly->Specular, false, OutUVSets, FVector2D(240, -128)); CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sSpecularFactor, UnrealMaterialEditorOnly->Roughness, false, OutUVSets, FVector2D(240, -180)); CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sShininess, UnrealMaterialEditorOnly->Metallic, false, OutUVSets, FVector2D(240, -210)); if (!CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sNormalMap, UnrealMaterialEditorOnly->Normal, true, OutUVSets, FVector2D(240, 256))) { CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sBump, UnrealMaterialEditorOnly->Normal, true, OutUVSets, FVector2D(240, 256)); // no bump in unreal, use as normal map } if (CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sTransparentColor, UnrealMaterialEditorOnly->Opacity, false, OutUVSets, FVector2D(200, 256))) { UnrealMaterial->BlendMode = BLEND_Translucent; CreateAndLinkExpressionForMaterialProperty(FbxMaterial, UnrealMaterial, FbxSurfaceMaterial::sTransparencyFactor, UnrealMaterialEditorOnly->OpacityMask, false, OutUVSets, FVector2D(150, 256)); } FixupMaterial(FbxMaterial, UnrealMaterial); // add random diffuse if none exists UMaterialEditingLibrary::LayoutMaterialExpressions(UnrealMaterial); } // compile shaders for PC (from UPrecompileShadersCommandlet::ProcessMaterial // and FMaterialEditor::UpdateOriginalMaterial) } if (UnrealMaterialFinal) { // let the material update itself if necessary UnrealMaterialFinal->PreEditChange(NULL); UnrealMaterialFinal->PostEditChange(); ImportedMaterialData.AddImportedMaterial(FbxMaterial, *UnrealMaterialFinal); } return UnrealMaterialFinal; } void UnFbx::FFbxImporter::FindOrImportMaterialsFromNode(FbxNode* FbxNode, TArray& OutMaterials, TArray& UVSets, bool bForSkeletalMesh) { if (FbxMesh *MeshNode = FbxNode->GetMesh()) { TSet UsedMaterialIndexes; for (int32 ElementMaterialIndex = 0; ElementMaterialIndex < MeshNode->GetElementMaterialCount(); ++ElementMaterialIndex) { FbxGeometryElementMaterial *ElementMaterial = MeshNode->GetElementMaterial(ElementMaterialIndex); switch (ElementMaterial->GetMappingMode()) { case FbxLayerElement::eAllSame: { if (ElementMaterial->GetIndexArray().GetCount() > 0) { UsedMaterialIndexes.Add(ElementMaterial->GetIndexArray()[0]); } } break; case FbxLayerElement::eByPolygon: { for (int32 MaterialIndex = 0; MaterialIndex < ElementMaterial->GetIndexArray().GetCount(); ++MaterialIndex) { UsedMaterialIndexes.Add(ElementMaterial->GetIndexArray()[MaterialIndex]); } } break; } } for (int32 MaterialIndex = 0, MaterialCount = FbxNode->GetMaterialCount(); MaterialIndex < MaterialCount; ++MaterialIndex) { const FbxSurfaceMaterial *FbxMaterial = FbxNode->GetMaterial(MaterialIndex); UMaterialInterface* MaterialImported = nullptr; //Only create the material used by the mesh element material if (FbxMaterial && UsedMaterialIndexes.Contains(MaterialIndex)) { if (UMaterialInterface* ExistingMaterial = FindExistingMaterialFromFbxMaterial(*FbxMaterial, ImportOptions->MaterialSearchLocation)) { MaterialImported = ExistingMaterial; } else if (ImportOptions->bImportMaterials) { //Only create a new material if we are importing them and we could not find an existing one. MaterialImported = CreateUnrealMaterial(*FbxMaterial, UVSets, bForSkeletalMesh); } } //The fbxMaterial is not valid. OutMaterials.Add(MaterialImported); } } else { //Could not import the materials, no mesh found. OutMaterials.AddDefaulted(FbxNode->GetMaterialCount()); } } #undef LOCTEXT_NAMESPACE