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

1049 lines
38 KiB
C++

// 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 <fbxsdk.h>
THIRD_PARTY_INCLUDES_END
namespace UnFbx
{
namespace FbxMaterialExportHelpers
{
UMaterialExpressionCustomOutput* GetCustomOutputByName(const UMaterialInterface* Material, const FString& FunctionName)
{
for (const TObjectPtr<UMaterialExpression>& Expression : Material->GetMaterial()->GetExpressions())
{
UMaterialExpressionCustomOutput* CustomOutput = Cast<UMaterialExpressionCustomOutput>(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<UMaterialExpressionCustomOutput*>(CustomOutput)->GetInput(0);
}
UMaterial* UnderlyingMaterial = const_cast<UMaterial*>(Material->GetMaterial());
return UnderlyingMaterial->GetExpressionInputForProperty(Property.Type);
}
template <typename ExpressionType>
void GetAllInputExpressionsOfType(const UMaterialInterface* Material, const FMaterialPropertyEx& Property, TArray<ExpressionType*>& OutExpressions)
{
const FExpressionInput* Input = GetInputForProperty(Material, Property);
if (Input == nullptr)
{
return;
}
UMaterialExpression* InputExpression = Input->Expression;
if (InputExpression == nullptr)
{
return;
}
TArray<UMaterialExpression*> AllInputExpressions;
InputExpression->GetAllInputExpressions(AllInputExpressions);
for (UMaterialExpression* Expression : AllInputExpressions)
{
if (ExpressionType* ExpressionOfType = Cast<ExpressionType>(Expression))
{
OutExpressions.Add(ExpressionOfType);
}
if (UMaterialFunctionInterface* MaterialFunction = UMaterial::GetExpressionFunctionPointer(Expression))
{
MaterialFunction->GetAllExpressionsOfType<ExpressionType>(OutExpressions);
}
else if (TOptional<UMaterial::FLayersInterfaces> LayersInterfaces = UMaterial::GetExpressionLayers(Expression))
{
for (UMaterialFunctionInterface* Layer : LayersInterfaces->Layers)
{
if (Layer != nullptr)
{
Layer->GetAllExpressionsOfType<ExpressionType>(OutExpressions);
}
}
for (UMaterialFunctionInterface* Blend : LayersInterfaces->Blends)
{
if (Blend != nullptr)
{
Blend->GetAllExpressionsOfType<ExpressionType>(OutExpressions);
}
}
}
}
}
UTexture* GetTextureFromSample(const UMaterialInterface* Material, const UMaterialExpressionTextureSample* SampleExpression)
{
if (const UMaterialExpressionTextureSampleParameter2D* SampleParameter = ExactCast<UMaterialExpressionTextureSampleParameter2D>(SampleExpression))
{
UTexture* ParameterValue = SampleParameter->Texture;
if (!Material->GetTextureParameterValue(SampleParameter->GetParameterName(), ParameterValue))
{
return nullptr;
}
return ParameterValue;
}
if (const UMaterialExpressionTextureSample* Sample = ExactCast<UMaterialExpressionTextureSample>(SampleExpression))
{
UMaterialExpression* ObjectExpression = Sample->TextureObject.Expression;
if (ObjectExpression == nullptr)
{
return Sample->Texture;
}
if (const UMaterialExpressionTextureObjectParameter* ObjectParameter = ExactCast<UMaterialExpressionTextureObjectParameter>(ObjectExpression))
{
UTexture* ParameterValue = ObjectParameter->Texture;
if (!Material->GetTextureParameterValue(ObjectParameter->GetParameterName(), ParameterValue))
{
return nullptr;
}
return ParameterValue;
}
if (const UMaterialExpressionTextureObject* Object = ExactCast<UMaterialExpressionTextureObject>(ObjectExpression))
{
return Object->Texture;
}
return nullptr;
}
if (const UMaterialExpressionTextureObjectParameter* ObjectParameter = ExactCast<UMaterialExpressionTextureObjectParameter>(SampleExpression))
{
UTexture* ParameterValue = ObjectParameter->Texture;
if (!Material->GetTextureParameterValue(ObjectParameter->GetParameterName(), ParameterValue))
{
return nullptr;
}
return ParameterValue;
}
if (const UMaterialExpressionTextureObject* Object = ExactCast<UMaterialExpressionTextureObject>(SampleExpression))
{
return Object->Texture;
}
return nullptr;
}
FIntPoint TryGetMaxTextureSize(const UMaterialInterface* Material, const FMaterialPropertyEx& Property, const FIntPoint& DefaultMaxSize)
{
TArray<UMaterialExpressionTextureSample*> 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<UTexture2D>() || Texture->IsA<UTextureRenderTarget2D>()))
{
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<UTexture2D>(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<UMaterial*>(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<int32>& 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<FColor>& 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<int32> 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<int32> SectionIndices;
SectionIndices.Reserve(Sections.Num());
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
{
if (Sections[SectionIndex].MaterialIndex == MaterialIndex)
{
SectionIndices.Add(SectionIndex);
}
}
return SectionIndices;
}
TArray<int32> GetSectionIndices(const USkeletalMesh* SkeletalMesh, int32 LODIndex, const int32& MaterialIndex)
{
if (SkeletalMesh == nullptr)
{
return {};
}
const FSkeletalMeshLODRenderData& RenderData = SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex];
const TArray<FSkelMeshRenderSection>& Sections = RenderData.RenderSections;
TArray<int32> 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<TObjectPtr<UMaterialExpression>> MaterialExpressions = MaterialInterface->GetMaterial()->GetExpressions();
UMaterialExpressionMaterialFunctionCall* ImportedMaterialFunction = nullptr;
for (const TObjectPtr<UMaterialExpression>& MaterialExpression : MaterialExpressions)
{
if (UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast<UMaterialExpressionMaterialFunctionCall>(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<UMaterialExpressionTextureBase>(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<UMaterialExpressionConstant>(LerpExpr->Alpha.Expression))
{
if (FbxFactorProperty.IsValid()) FbxFactorProperty.Set(ConstExpr1->R);
}
else if (UMaterialExpressionScalarParameter* ScalarParamExpr = Cast<UMaterialExpressionScalarParameter>(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<UMaterialExpressionTextureBase>(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<UMaterialExpressionConstant>(MaterialExpression))
{
FbxDouble3 FbxColor(ConstExpr1->R, ConstExpr1->R, ConstExpr1->R);
FbxProperty.Set(FbxColor);
}
if (UMaterialExpressionConstant3Vector* ConstExpr3 = Cast<UMaterialExpressionConstant3Vector>(MaterialExpression))
{
FbxDouble3 FbxColor(ConstExpr3->Constant.R, ConstExpr3->Constant.G, ConstExpr3->Constant.B);
FbxProperty.Set(FbxColor);
}
if (UMaterialExpressionConstant4Vector* ConstExpr4 = Cast<UMaterialExpressionConstant4Vector>(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<FStaticMaterial> Materials;
GetBrushMesh(Actor, Model, Mesh, Materials);
StaticMesh = CreateStaticMesh(Mesh, Materials, GetTransientPackage(), Actor ? Actor->GetFName() : Model->GetFName());
const IMeshMergeUtilities& MeshMergeUtilities = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("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<IMeshMergeModule>("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<IMeshMergeModule>("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<AActor>(SpawnParams))
{
USkeletalMeshComponent* TempComponent = NewObject<USkeletalMeshComponent>(TempActor, TEXT(""), RF_Transient);
TempComponent->RegisterComponent();
TempComponent->SetSkeletalMesh(const_cast<USkeletalMesh*>(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<int32> 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>();
}
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<int32> 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<FMeshDescription*>(&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<UMaterialInterface*>(Material);
MatSet.PropertySizes.Add(Property, BakeSize);
MatSet.bTangentSpaceNormal = true;
TArray<FMeshData*> MeshSettings;
TArray<FMaterialDataEx*> MatSettings;
MeshSettings.Add(&MeshSet);
MatSettings.Add(&MatSet);
TArray<FBakeOutputEx> BakeOutputs;
IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");
Module.SetLinearBake(true);
Module.BakeMaterials(MatSettings, MeshSettings, BakeOutputs);
const bool bIsLinearBake = Module.IsLinearBake(Property);
Module.SetLinearBake(false);
FBakeOutputEx& BakeOutput = BakeOutputs[0];
TArray<FColor> 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<uint8> OutCompressedData;
IImageWrapperModule& ImageWrapperModule = FModuleManager::Get().LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
const TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(InCompressionFormat);
if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(InRawData, InRawSize, InWidth, InHeight, InRGBFormat, InBitDepth))
{
const TArray64<uint8>& 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<TObjectPtr<UMaterialExpression>> MaterialExpressions = MaterialInterface->GetMaterial()->GetExpressions();
bool MFPhongUsed = false;
for (const TObjectPtr<UMaterialExpression>& MaterialExpression : MaterialExpressions)
{
if (UMaterialExpressionMaterialFunctionCall* FunctionCallExpression = Cast<UMaterialExpressionMaterialFunctionCall>(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<FExpressionInput*> 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<UMaterialInstance>(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));
}
}
}
}
}
}