Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMaterialImport.cpp
2025-05-18 13:04:45 +08:00

915 lines
34 KiB
C++

// 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<UTexture>(NULL, *ObjectPath, nullptr, LOAD_Quiet | LOAD_NoWarn);
}
if (!ExistingTexture)
{
const FString Suffix(TEXT(""));
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("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<uint8> 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<UTextureFactory>();
TextureFact->AddToRoot();
// save texture settings if texture exist
TextureFact->SuppressImportOverwriteDialog();
const TCHAR* TextureType = *Extension;
const bool bTextureAssetAlreadyExists = FindObject<UTexture>(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<FbxLayeredTexture>();
FbxString PropertyName = Property.GetName();
if(LayeredTextureCount > 0)
{
for(int32 LayerIndex=0; LayerIndex<LayeredTextureCount; ++LayerIndex)
{
FbxLayeredTexture *lLayeredTexture = Property.GetSrcObject<FbxLayeredTexture>(LayerIndex);
int32 NbTextures = lLayeredTexture->GetSrcObjectCount<FbxTexture>();
for(int32 TexIndex =0; TexIndex<NbTextures; ++TexIndex)
{
FbxFileTexture* Texture = lLayeredTexture->GetSrcObject<FbxFileTexture>(TexIndex);
if(Texture)
{
ImportTexture(Texture, PropertyName == FbxSurfaceMaterial::sNormalMap || PropertyName == FbxSurfaceMaterial::sBump);
}
}
}
}
else
{
//no layered texture simply get on the property
int32 NbTextures = Property.GetSrcObjectCount<FbxTexture>();
for(int32 TexIndex =0; TexIndex<NbTextures; ++TexIndex)
{
FbxFileTexture* Texture = Property.GetSrcObject<FbxFileTexture>(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<FbxTexture>();
if (TextureCount > 0)
{
for (int32 TextureIndex = 0; TextureIndex < TextureCount; ++TextureIndex)
{
FbxFileTexture* TextureObj = Property.GetSrcObject<FbxFileTexture>(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<FString>& UVSet,
const FVector2D& Location)
{
bool bCreated = false;
FbxProperty FbxProperty = FbxMaterial.FindProperty( MaterialProperty );
if( FbxProperty.IsValid() )
{
int32 UnsupportedTextureCount = FbxProperty.GetSrcObjectCount<FbxLayeredTexture>();
UnsupportedTextureCount += FbxProperty.GetSrcObjectCount<FbxProceduralTexture>();
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<FbxTexture>();
if (TextureCount>0)
{
const bool bEnableVirtualTextureOpacityMask = GetDefault<URendererSettings>()->bEnableVirtualTextureOpacityMask;
for(int32 TextureIndex =0; TextureIndex<TextureCount; ++TextureIndex)
{
FbxFileTexture* FbxTexture = FbxProperty.GetSrcObject<FbxFileTexture>(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<UTexture2D>(UnrealTexture))
{
TArray<UTexture2D*> TexturesToConvert = { UnrealTexture2D };
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("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<UMaterialExpressionTextureSample>(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<UMaterialExpressionTextureCoordinate>(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<FExpressionOutput> 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<UMaterialExpressionVectorParameter>(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<float>(RAND_MAX);
MyColorExpression->DefaultValue.G = 0.5f+(0.5f*FMath::Rand()) / static_cast<float>(RAND_MAX);
MyColorExpression->DefaultValue.B = 0.5f+(0.5f*FMath::Rand()) / static_cast<float>(RAND_MAX);
}
TArray<FExpressionOutput> 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<FbxLayeredTexture>();
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<FbxTexture>();
if (TextureCount > 0)
{
for (int32 TextureIndex = 0; TextureIndex < TextureCount; ++TextureIndex)
{
FbxFileTexture* FbxTexture = FbxProperty.GetSrcObject<FbxFileTexture>(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<FString>& UVSet) {
FbxProperty FbxProperty = FbxMaterial.FindProperty(MaterialProperty);
if (FbxProperty.IsValid())
{
int32 LayeredTextureCount = FbxProperty.GetSrcObjectCount<FbxLayeredTexture>();
if (LayeredTextureCount == 0)
{
int32 TextureCount = FbxProperty.GetSrcObjectCount<FbxTexture>();
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<FbxFileTexture>(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<FString>& 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<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().CreateUniqueAssetName(BasePackageName, Suffix, FinalPackageName, FinalMaterialName);
UPackage* Package = CreatePackage(*FinalPackageName);
UMaterialInterface* UnrealMaterialFinal = nullptr;
if (bCanInstance)
{
auto MaterialInstanceFactory = NewObject<UMaterialInstanceConstantFactoryNew>();
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<UMaterialFactoryNew>();
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<UMaterialInterface*>& OutMaterials, TArray<FString>& UVSets, bool bForSkeletalMesh)
{
if (FbxMesh *MeshNode = FbxNode->GetMesh())
{
TSet<int32> 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