Files
2025-05-18 13:04:45 +08:00

550 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetaHumanCharacterTextureSynthesis.h"
#include "Editor/EditorEngine.h"
#include "HAL/ConsoleManager.h"
#include "ImageCore.h"
#include "Interfaces/IPluginManager.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Misc/Paths.h"
#include "PixelFormat.h"
#include "Tasks/Task.h"
#include "TextureResource.h"
#include "UObject/NameTypes.h"
#include "ProfilingDebugging/CountersTrace.h"
#include "Logging/StructuredLog.h"
#include "MetaHumanCharacter.h"
#include "MetaHumanCharacterEditorLog.h"
#include "MetaHumanCharacterEditorSettings.h"
#include "MetaHumanFaceTextureSynthesizer.h"
#include "MetaHumanCharacterEditorSubsystem.h"
extern UNREALED_API UEditorEngine* GEditor;
namespace UE::MetaHuman
{
static FAutoConsoleCommand ResetMetaHumanCharacterTextureSynthesis(
TEXT("mh.TextureSynthesis.ResetModel"), TEXT("Reset Texture Synthesis by re-loading the model data"),
FConsoleCommandDelegate::CreateStatic(
[]()
{
if (UMetaHumanCharacterEditorSubsystem* MetaHumanCharacterEditorSubsystem = GEditor->GetEditorSubsystem<UMetaHumanCharacterEditorSubsystem>())
{
MetaHumanCharacterEditorSubsystem->ResetTextureSynthesis();
UE_LOGFMT(LogMetaHumanCharacterEditor, Display, "Texture sythesis reset");
}
else
{
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to reset texture synthesis");
}
}
)
);
// Texture Type to be used if a cached image is not available by the local model
static constexpr EFaceTextureType MapToCompatibleTextureType[] =
{
EFaceTextureType::Basecolor,
EFaceTextureType::Basecolor,
EFaceTextureType::Basecolor,
EFaceTextureType::Basecolor,
EFaceTextureType::Normal,
EFaceTextureType::Normal,
EFaceTextureType::Normal,
EFaceTextureType::Normal,
EFaceTextureType::Cavity,
};
static_assert(UE_ARRAY_COUNT(MapToCompatibleTextureType) == static_cast<int32>(EFaceTextureType::Count));
static UTexture2D* CreateTexture(int32 InSizeX, int32 InSizeY, EFaceTextureType InType, EPixelFormat InPixelFormat)
{
// Sanity check
check(InSizeX >= 0 && (InSizeX == InSizeY));
// Order should match the one in EFaceTextureType
static constexpr TextureCompressionSettings TextureTypeToCompressionSettings[] =
{
TC_Default, // Basecolor
TC_HDR_Compressed, // Animated delta color
TC_HDR_Compressed,
TC_HDR_Compressed,
TC_Normalmap, // Normal
TC_Default, // Animated delta normal
TC_Default,
TC_Default,
TC_Masks // Cavity
};
static constexpr TextureGroup TextureTypeToTextureGroup[] =
{
TEXTUREGROUP_Character, // Basecolor
TEXTUREGROUP_Character,
TEXTUREGROUP_Character,
TEXTUREGROUP_Character,
TEXTUREGROUP_CharacterNormalMap, // Normal
TEXTUREGROUP_CharacterNormalMap,
TEXTUREGROUP_CharacterNormalMap,
TEXTUREGROUP_CharacterNormalMap,
TEXTUREGROUP_CharacterSpecular // Cavity
};
static_assert(UE_ARRAY_COUNT(TextureTypeToCompressionSettings) == static_cast<int32>(EFaceTextureType::Count));
static_assert(UE_ARRAY_COUNT(TextureTypeToTextureGroup) == static_cast<int32>(EFaceTextureType::Count));
const bool bIsAlbedoTexture = InType == EFaceTextureType::Basecolor;
// Create a sensible unique name for the texture to allow easy identification when debugging
const FString TextureName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByValue((int64) InType);
const FString CandidateName = FString::Format(TEXT("T_Face_{0}"), { TextureName });
const FName AssetName = MakeUniqueObjectName(GetTransientPackage(), UTexture2D::StaticClass(), FName{ CandidateName }, EUniqueObjectNameOptions::GloballyUnique);
// Create texture
UTexture2D* Texture = UTexture2D::CreateTransient(InSizeX, InSizeY, InPixelFormat, AssetName);
if (Texture)
{
// Textures properties as expected by the face material
// Set its properties
Texture->CompressionSettings = TextureTypeToCompressionSettings[static_cast<int32>(InType)];
Texture->AlphaCoverageThresholds.W = 1.0f;
// Disable MIPs for albedo
Texture->MipGenSettings = bIsAlbedoTexture ? TMGS_NoMipmaps : TMGS_FromTextureGroup;
// Set texture to the "Character" texture group (rather than the default "World")
Texture->LODGroup = TextureTypeToTextureGroup[static_cast<int32>(InType)];
// Set sRGB for albedo textures
Texture->SRGB = bIsAlbedoTexture;
}
return Texture;
}
static bool CheckMatchingImageAndTextureSize(const FImageView& InImage, TNotNull<const UTexture2D*> InTexture2D)
{
if (const FTexturePlatformData* TexturePlatformData = InTexture2D->GetPlatformData())
{
const FPixelFormatInfo& FormatInfo = GPixelFormats[InTexture2D->GetPixelFormat()];
return InImage.SizeX == TexturePlatformData->Mips[0].SizeX ||
InImage.SizeY == TexturePlatformData->Mips[0].SizeY ||
InImage.GetBytesPerPixel() == FormatInfo.BlockBytes;
}
return false;
}
static void CopySynthesizedDataToTexture2D(TConstArrayView<uint8> InSynthesizedRawData, UTexture2D* InOutTexture2D)
{
check(InOutTexture2D);
check(!InSynthesizedRawData.IsEmpty());
// TODO: Legacy code, revisit to check what still makes sense
// Get Texture2DData
const int32 MipLevel = 0;
FTexture2DMipMap& Mip = InOutTexture2D->GetPlatformData()->Mips[MipLevel];
uint8* Texture2DData = (uint8*)Mip.BulkData.Lock(LOCK_READ_WRITE);
if (!Texture2DData
|| Mip.BulkData.GetBulkDataSize() != InSynthesizedRawData.Num())
{
ensure(false);
return;
}
// Copy the data into the final UTexture2D
FMemory::Memcpy(Texture2DData, InSynthesizedRawData.GetData(), InSynthesizedRawData.Num());
// Unlock source data
Mip.BulkData.Unlock();
// Refresh rendering thread
InOutTexture2D->UpdateResource();
}
static FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams SkinPropertiesToSynthesizerParams(const FMetaHumanCharacterSkinProperties& SkinProperties, int32 MaxHFIndex)
{
return FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams{
.SkinUVFromUI = FVector2f{ SkinProperties.U, SkinProperties.V },
.HighFrequencyIndex = FMath::Clamp(SkinProperties.FaceTextureIndex, 0, MaxHFIndex - 1),
.MapType = FMetaHumanFaceTextureSynthesizer::EMapType::Base
};
}
static TArray<EFaceTextureType> GetSupportedTextureTypes(const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer)
{
// Ensure that EFaceTextureType and FMetaHumanFaceTextureSynthesizer::EMapType are in sync
static_assert(static_cast<int32>(EFaceTextureType::Basecolor) == 0);
static_assert(static_cast<int32>(EFaceTextureType::Normal) == static_cast<int32>(FMetaHumanFaceTextureSynthesizer::EMapType::Animated2) + 1);
const int32 BaseNormalIndex = static_cast<int32>(EFaceTextureType::Normal);
TArray<EFaceTextureType> OutTextureTypes{};
// No supported images when there is no texture synthesis loaded
if (InFaceTextureSynthesizer.IsValid())
{
const TArray<FMetaHumanFaceTextureSynthesizer::EMapType> SupportedAlbedoTypes = InFaceTextureSynthesizer.GetSupportedAlbedoMapTypes();
for (FMetaHumanFaceTextureSynthesizer::EMapType MapType : SupportedAlbedoTypes)
{
const int32 MapIndex = static_cast<int32>(MapType);
OutTextureTypes.Add(static_cast<EFaceTextureType>(MapIndex));
}
const TArray<FMetaHumanFaceTextureSynthesizer::EMapType> SupportedNormalTypes = InFaceTextureSynthesizer.GetSupportedNormalMapTypes();
for (FMetaHumanFaceTextureSynthesizer::EMapType MapType : SupportedNormalTypes)
{
const int32 MapIndex = static_cast<int32>(MapType);
OutTextureTypes.Add(static_cast<EFaceTextureType>(BaseNormalIndex + MapIndex));
}
// Cavity should always be supported
OutTextureTypes.Add(EFaceTextureType::Cavity);
}
return OutTextureTypes;
}
} // namespace UE::MetaHuman
void FMetaHumanCharacterTextureSynthesis::InitFaceTextureSynthesizer(FMetaHumanFaceTextureSynthesizer& OutFaceTextureSynthesizer)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FMetaHumanCharacterTextureSynthesis::InitFaceTextureSynthesizer");
// First try to initialize the face synthesizer with the model path from the plugin Settings
const UMetaHumanCharacterEditorSettings* Settings = GetDefault<UMetaHumanCharacterEditorSettings>();
check(Settings);
const FString TextureSynthesisModelPath = Settings->TextureSynthesisModelDir.Path;
if (!TextureSynthesisModelPath.IsEmpty())
{
// Assume it is a valid model directory
if (FPaths::DirectoryExists(TextureSynthesisModelPath) && OutFaceTextureSynthesizer.Init(TextureSynthesisModelPath, Settings->TextureSynthesisThreadCount))
{
return;
}
else
{
UE_LOGFMT(LogMetaHumanCharacterEditor, Warning, "Failed to initialize texture synthesis model from: {TextureSynthesisModelPath}, will try to load the default models", TextureSynthesisModelPath);
}
}
// Try to load the test model from the Plugin Content
const TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(UE_PLUGIN_NAME);
// Paths to find model data in order of priority
const FString DefaultModelPaths[] =
{
Plugin->GetContentDir() + TEXT("/Optional/TextureSynthesis/TS-1.3-D_UE_res-1024_nchr-153"),
};
bool bIsModelLoaded = false;
for (const FString& ModelPath : DefaultModelPaths)
{
if (OutFaceTextureSynthesizer.Init(ModelPath, Settings->TextureSynthesisThreadCount))
{
bIsModelLoaded = true;
break;
}
}
if (!bIsModelLoaded)
{
UE_LOGFMT(LogMetaHumanCharacterEditor, Warning, "Failed to initialize texture synthesis with default models, skin editing will be disabled");
}
}
void FMetaHumanCharacterTextureSynthesis::InitSynthesizedFaceData(const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer,
const TMap<EFaceTextureType, FMetaHumanCharacterTextureInfo>& InTextureInfo,
TMap<EFaceTextureType, TObjectPtr<UTexture2D>>& OutSynthesizedFaceTextures,
TMap<EFaceTextureType, FImage>& OutSynthesizedFaceImages)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FMetaHumanCharacterTextureSynthesis::InitSynthesizedFaceData");
// Set defaults for when texture synthesis is disabled
const int32 DefaultTextureSizeX = InFaceTextureSynthesizer.IsValid() ? InFaceTextureSynthesizer.GetTextureSizeX() : 128;
const int32 DefaultTextureSizeY = InFaceTextureSynthesizer.IsValid() ? InFaceTextureSynthesizer.GetTextureSizeY() : 128;
const ERawImageFormat::Type DefaultImageFormat = InFaceTextureSynthesizer.IsValid() ? InFaceTextureSynthesizer.GetTextureFormat() : ERawImageFormat::BGRA8;
const EGammaSpace DefaultGammaSpace = InFaceTextureSynthesizer.IsValid() ? InFaceTextureSynthesizer.GetTextureColorSpace() : EGammaSpace::sRGB;
if (OutSynthesizedFaceTextures.IsEmpty())
{
if (InTextureInfo.IsEmpty())
{
FMetaHumanCharacterTextureSynthesis::CreateSynthesizedFaceTextures(DefaultTextureSizeX, OutSynthesizedFaceTextures);
}
else
{
// Synthesized Face Textures need to match the ones expected by the preview material, so always create one for all types
for (EFaceTextureType TextureType : TEnumRange<EFaceTextureType>())
{
// Get a compatible texture type if there is no info for this texture
const EFaceTextureType MatchedTextureType = InTextureInfo.Contains(TextureType) ? TextureType : UE::MetaHuman::MapToCompatibleTextureType[static_cast<int32>(TextureType)];
// Get the texture size
int32 TextureSizeX = DefaultTextureSizeX;
int32 TextureSizeY = DefaultTextureSizeY;
if (const FMetaHumanCharacterTextureInfo* TextureInfo = InTextureInfo.Find(MatchedTextureType))
{
TextureSizeX = TextureInfo->SizeX;
TextureSizeY = TextureInfo->SizeY;
}
else
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureType));
UE_LOGFMT(LogMetaHumanCharacterEditor, Warning, "No compatible texture info for {TextureTypeName}, using the default size", *TextureTypeName);
}
OutSynthesizedFaceTextures.FindOrAdd(TextureType) = CreateFaceTexture(TextureType, TextureSizeX, TextureSizeY);
}
}
}
if (OutSynthesizedFaceImages.IsEmpty())
{
if (InTextureInfo.IsEmpty())
{
// Create cached images for all types of maps that the local model supports
const TArray<EFaceTextureType> SupportedTextureTypes = UE::MetaHuman::GetSupportedTextureTypes(InFaceTextureSynthesizer);
for (EFaceTextureType TextureType : SupportedTextureTypes)
{
FImage& NewSynthesizedFaceTexture = OutSynthesizedFaceImages.Add(TextureType);
NewSynthesizedFaceTexture.Init(DefaultTextureSizeX, DefaultTextureSizeY, DefaultImageFormat, DefaultGammaSpace);
}
}
else
{
for (const TPair<EFaceTextureType, FMetaHumanCharacterTextureInfo>& TextureInfoPair : InTextureInfo)
{
const FMetaHumanCharacterTextureInfo& TextureInfo = TextureInfoPair.Value;
OutSynthesizedFaceImages.Add(TextureInfoPair.Key, TextureInfo.GetBlankImage());
}
}
}
}
UTexture2D* FMetaHumanCharacterTextureSynthesis::CreateFaceTexture(EFaceTextureType InTextureType, int32 InSizeX, int32 InSizeY)
{
return UE::MetaHuman::CreateTexture(InSizeX, InSizeY, InTextureType, PF_B8G8R8A8);
}
void FMetaHumanCharacterTextureSynthesis::CreateSynthesizedFaceTextures(int32 InResolution,
TMap<EFaceTextureType, TObjectPtr<UTexture2D>>& OutSynthesizedFaceTextures)
{
for (EFaceTextureType TextureType : TEnumRange<EFaceTextureType>())
{
OutSynthesizedFaceTextures.Emplace(TextureType, CreateFaceTexture(TextureType, InResolution, InResolution));
}
}
bool FMetaHumanCharacterTextureSynthesis::AreTexturesAndImagesSuitableForSynthesis(
const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer,
const TMap<EFaceTextureType, TObjectPtr<UTexture2D>>& InSynthesizedFaceTextures,
const TMap<EFaceTextureType, FImage>& InSynthesizedFaceImages)
{
if (!InFaceTextureSynthesizer.IsValid())
{
return false;
}
for (EFaceTextureType TextureType : TEnumRange<EFaceTextureType>())
{
const TObjectPtr<UTexture2D>* TexturePtr = InSynthesizedFaceTextures.Find(TextureType);
if (!TexturePtr)
{
// Expected to find a texture of this type
return false;
}
const TObjectPtr<UTexture2D> Texture = *TexturePtr;
if (Texture && (Texture->GetSizeX() != InFaceTextureSynthesizer.GetTextureSizeX()
|| Texture->GetSizeY() != InFaceTextureSynthesizer.GetTextureSizeY()))
{
// TODO: Check texture is of the correct type for completeness
return false;
}
}
for (const TPair<EFaceTextureType, FImage>& Kvp : InSynthesizedFaceImages)
{
const FImage& Image = Kvp.Value;
if (Image.SizeX != InFaceTextureSynthesizer.GetTextureSizeX()
|| Image.SizeY != InFaceTextureSynthesizer.GetTextureSizeY()
|| Image.Format != InFaceTextureSynthesizer.GetTextureFormat()
|| Image.GammaSpace != InFaceTextureSynthesizer.GetTextureColorSpace())
{
return false;
}
}
return true;
}
FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams FMetaHumanCharacterTextureSynthesis::SkinPropertiesToSynthesizerParams(const FMetaHumanCharacterSkinProperties& InSkinProperties, const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer)
{
return UE::MetaHuman::SkinPropertiesToSynthesizerParams(InSkinProperties, InFaceTextureSynthesizer.GetMaxHighFrequencyIndex());
}
bool FMetaHumanCharacterTextureSynthesis::SynthesizeFaceTextures(const FMetaHumanCharacterSkinProperties& InSkinProperties, const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer, TMap<EFaceTextureType, FImage>& OutCachedImages)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FMetaHumanCharacterTextureSynthesis::SynthesizeFaceTextures");
if (!InFaceTextureSynthesizer.IsValid())
{
return false;
}
FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams Params = UE::MetaHuman::SkinPropertiesToSynthesizerParams(InSkinProperties, InFaceTextureSynthesizer.GetMaxHighFrequencyIndex());
// Synthesize albedo maps
for (EFaceTextureType TextureType :
{
EFaceTextureType::Basecolor,
EFaceTextureType::Basecolor_Animated_CM1,
EFaceTextureType::Basecolor_Animated_CM2,
EFaceTextureType::Basecolor_Animated_CM3,
})
{
Params.MapType = static_cast<FMetaHumanFaceTextureSynthesizer::EMapType>(TextureType);
if (OutCachedImages.Contains(TextureType))
{
if (!InFaceTextureSynthesizer.SynthesizeAlbedo(Params, OutCachedImages[TextureType]))
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureType));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to synthesize albedo map: {AlbedoMapTypeName}", *TextureTypeName);
return false;
}
}
}
return true;
}
bool FMetaHumanCharacterTextureSynthesis::SynthesizeFaceAlbedoWithHFMap(EFaceTextureType InTextureType, const FMetaHumanCharacterSkinProperties& InSkinProperties,
const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer, const TStaticArray<TArray<uint8>, 4>& InHFMaps, FImageView OutImage)
{
if (!InFaceTextureSynthesizer.IsValid())
{
return false;
}
const int32 TextureIndex = static_cast<int32>(InTextureType);
if (InTextureType >= EFaceTextureType::Normal)
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureIndex));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Invalid texture type [{TextureTypeName}] passed to SynthesizeFaceAlbedoWithHFMap, only base color types are supported", *TextureTypeName);
return false;
}
FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams Params = UE::MetaHuman::SkinPropertiesToSynthesizerParams(InSkinProperties, InFaceTextureSynthesizer.GetMaxHighFrequencyIndex());
Params.MapType = static_cast<FMetaHumanFaceTextureSynthesizer::EMapType>(TextureIndex);
if (!InFaceTextureSynthesizer.SynthesizeAlbedoWithHF(Params, InHFMaps, OutImage))
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureIndex));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to synthesize albedo map: {TextureTypeName}", *TextureTypeName);
return false;
}
return true;
}
bool FMetaHumanCharacterTextureSynthesis::SelectFaceTextures(const FMetaHumanCharacterSkinProperties& InSkinProperties, const FMetaHumanFaceTextureSynthesizer& InFaceTextureSynthesizer, TMap<EFaceTextureType, FImage>& OutCachedImages)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FMetaHumanCharacterTextureSynthesis::SelectFaceTextures");
if (!InFaceTextureSynthesizer.IsValid())
{
return false;
}
FMetaHumanFaceTextureSynthesizer::FTextureSynthesisParams Params = UE::MetaHuman::SkinPropertiesToSynthesizerParams(InSkinProperties, InFaceTextureSynthesizer.GetMaxHighFrequencyIndex());
const int32 BaseNormalIndex = static_cast<int32>(EFaceTextureType::Normal);
// Select normal maps
for (EFaceTextureType TextureType :
{
EFaceTextureType::Normal,
EFaceTextureType::Normal_Animated_WM1,
EFaceTextureType::Normal_Animated_WM2,
EFaceTextureType::Normal_Animated_WM3,
})
{
Params.MapType = static_cast<FMetaHumanFaceTextureSynthesizer::EMapType>(static_cast<int32>(TextureType) - BaseNormalIndex);
if (OutCachedImages.Contains(TextureType))
{
if (!InFaceTextureSynthesizer.SelectNormal(Params, OutCachedImages[TextureType]))
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureType));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to select normal map: {NormalMapTypeName}", *TextureTypeName);
return false;
}
}
}
// Select the cavity map
if (OutCachedImages.Contains(EFaceTextureType::Cavity))
{
if (!InFaceTextureSynthesizer.SelectCavity(Params.HighFrequencyIndex, OutCachedImages[EFaceTextureType::Cavity]))
{
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to select cavity map");
return false;
}
}
return true;
}
void FMetaHumanCharacterTextureSynthesis::UpdateTexture(TConstArrayView<uint8> InRawData, TNotNull<UTexture2D*> InOutTexture)
{
UE::MetaHuman::CopySynthesizedDataToTexture2D(InRawData, InOutTexture);
}
bool FMetaHumanCharacterTextureSynthesis::UpdateFaceTextures(const TMap<EFaceTextureType, FImage>& InCachedImages, TMap<EFaceTextureType, TObjectPtr<UTexture2D>>& OutSynthesizedFaceTextures)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FMetaHumanCharacterTextureSynthesis::UpdateFaceTextures");
if (OutSynthesizedFaceTextures.Num() != static_cast<int32>(EFaceTextureType::Count))
{
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Invalid synthesized data sizes: cached images {InCachedImages.Num}, textures {OutSynthesizedFaceTextures.Num}", InCachedImages.Num(), OutSynthesizedFaceTextures.Num());
return false;
}
// Iterate through all textures and assign the best available cached image
for (EFaceTextureType TextureType : TEnumRange<EFaceTextureType>())
{
const EFaceTextureType CachedImageTextureType = InCachedImages.Contains(TextureType) ? TextureType : UE::MetaHuman::MapToCompatibleTextureType[static_cast<int32>(TextureType)];
if (!InCachedImages.Contains(CachedImageTextureType))
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureType));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "No compatible cached image for {TextureTypeName}", *TextureTypeName);
return false;
}
check(OutSynthesizedFaceTextures.Contains(TextureType));
if (!UE::MetaHuman::CheckMatchingImageAndTextureSize(InCachedImages[CachedImageTextureType], OutSynthesizedFaceTextures[TextureType]))
{
const FString TextureTypeName = StaticEnum<EFaceTextureType>()->GetAuthoredNameStringByIndex(static_cast<int64>(TextureType));
UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Mismatch between synthesized albedo texture and generated image for {TextureTypeName}", *TextureTypeName);
return false;
}
UpdateTexture(InCachedImages[CachedImageTextureType].RawData, OutSynthesizedFaceTextures[TextureType]);
}
return true;
}