You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Changed fallback settings to be largely error based with new property FallbackRelativeError. Nanite builder will now provide the LOD fallbacks for all autogenerated LOD levels which is far faster than generating them from scratch. #rb graham.wihlidal #preflight 61f9e1fe9e4d23cd93b8d556 #ROBOMERGE-AUTHOR: brian.karis #ROBOMERGE-SOURCE: CL 18820056 in //UE5/Release-5.0/... via CL 18820070 via CL 18822916 #ROBOMERGE-BOT: UE5 (Release-Engine-Test -> Main) (v910-18824042) [CL 18825066 by brian karis in ue5-main branch]
366 lines
13 KiB
C++
366 lines
13 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ProxyGenerationProcessor.h"
|
|
#include "MaterialUtilities.h"
|
|
#include "MeshMergeUtilities.h"
|
|
#include "IMeshMergeExtension.h"
|
|
#include "ProxyMaterialUtilities.h"
|
|
#include "IMeshReductionInterfaces.h"
|
|
#include "IMeshReductionManagerModule.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "Stats/Stats.h"
|
|
#include "Algo/ForEach.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Editor.h"
|
|
#include "MeshMergeHelpers.h"
|
|
#include "ObjectCacheEventSink.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
FProxyGenerationProcessor::FProxyGenerationProcessor(const FMeshMergeUtilities* InOwner)
|
|
: Owner(InOwner)
|
|
{
|
|
#if WITH_EDITOR
|
|
FEditorDelegates::MapChange.AddRaw(this, &FProxyGenerationProcessor::OnMapChange);
|
|
FEditorDelegates::NewCurrentLevel.AddRaw(this, &FProxyGenerationProcessor::OnNewCurrentLevel);
|
|
|
|
IMeshReductionManagerModule& Module = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface");
|
|
IMeshMerging* MeshMerging = Module.GetMeshMergingInterface();
|
|
if (!MeshMerging)
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("No automatic mesh merging module available"));
|
|
}
|
|
else
|
|
{
|
|
MeshMerging->CompleteDelegate.BindRaw(this, &FProxyGenerationProcessor::ProxyGenerationComplete);
|
|
MeshMerging->FailedDelegate.BindRaw(this, &FProxyGenerationProcessor::ProxyGenerationFailed);
|
|
}
|
|
|
|
IMeshMerging* DistributedMeshMerging = Module.GetDistributedMeshMergingInterface();
|
|
if (!DistributedMeshMerging)
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("No distributed automatic mesh merging module available"));
|
|
}
|
|
else
|
|
{
|
|
DistributedMeshMerging->CompleteDelegate.BindRaw(this, &FProxyGenerationProcessor::ProxyGenerationComplete);
|
|
DistributedMeshMerging->FailedDelegate.BindRaw(this, &FProxyGenerationProcessor::ProxyGenerationFailed);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
FProxyGenerationProcessor::~FProxyGenerationProcessor()
|
|
{
|
|
#if WITH_EDITOR
|
|
FEditorDelegates::MapChange.RemoveAll(this);
|
|
FEditorDelegates::NewCurrentLevel.RemoveAll(this);
|
|
#endif // WITH_EDITOR
|
|
}
|
|
|
|
void FProxyGenerationProcessor::AddProxyJob(FGuid InJobGuid, FMergeCompleteData* InCompleteData)
|
|
{
|
|
FScopeLock Lock(&StateLock);
|
|
ProxyMeshJobs.Add(InJobGuid, InCompleteData);
|
|
}
|
|
|
|
bool FProxyGenerationProcessor::Tick(float DeltaTime)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FProxyGenerationProcessor_Tick);
|
|
|
|
FScopeLock Lock(&StateLock);
|
|
for (const auto& Entry : ToProcessJobDataMap)
|
|
{
|
|
FGuid JobGuid = Entry.Key;
|
|
FProxyGenerationData* Data = Entry.Value;
|
|
|
|
// Process the job
|
|
ProcessJob(JobGuid, Data);
|
|
|
|
// Data retrieved so can now remove the job from the map
|
|
ProxyMeshJobs.Remove(JobGuid);
|
|
delete Data->MergeData;
|
|
delete Data;
|
|
}
|
|
|
|
ToProcessJobDataMap.Reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
void FProxyGenerationProcessor::ProxyGenerationComplete(FMeshDescription& OutProxyMesh, struct FFlattenMaterial& OutMaterial, const FGuid OutJobGUID)
|
|
{
|
|
FScopeLock Lock(&StateLock);
|
|
FMergeCompleteData** FindData = ProxyMeshJobs.Find(OutJobGUID);
|
|
if (FindData && *FindData)
|
|
{
|
|
FMergeCompleteData* Data = *FindData;
|
|
|
|
FProxyGenerationData* GenerationData = new FProxyGenerationData();
|
|
GenerationData->Material = OutMaterial;
|
|
GenerationData->RawMesh = OutProxyMesh;
|
|
GenerationData->MergeData = Data;
|
|
|
|
ToProcessJobDataMap.Add(OutJobGUID, GenerationData);
|
|
}
|
|
}
|
|
|
|
void FProxyGenerationProcessor::ProxyGenerationFailed(const FGuid OutJobGUID, const FString& ErrorMessage)
|
|
{
|
|
FScopeLock Lock(&StateLock);
|
|
FMergeCompleteData** FindData = ProxyMeshJobs.Find(OutJobGUID);
|
|
if (FindData && *FindData)
|
|
{
|
|
UE_LOG(LogMeshMerging, Log, TEXT("Failed to generate proxy mesh for cluster %s, %s"), *(*FindData)->ProxyBasePackageName, *ErrorMessage);
|
|
ProxyMeshJobs.Remove(OutJobGUID);
|
|
|
|
TArray<UObject*> OutAssetsToSync;
|
|
(*FindData)->CallbackDelegate.ExecuteIfBound(OutJobGUID, OutAssetsToSync);
|
|
}
|
|
}
|
|
|
|
void FProxyGenerationProcessor::OnMapChange(uint32 MapFlags)
|
|
{
|
|
ClearProcessingData();
|
|
}
|
|
|
|
void FProxyGenerationProcessor::OnNewCurrentLevel()
|
|
{
|
|
ClearProcessingData();
|
|
}
|
|
|
|
void FProxyGenerationProcessor::ClearProcessingData()
|
|
{
|
|
FScopeLock Lock(&StateLock);
|
|
ProxyMeshJobs.Empty();
|
|
ToProcessJobDataMap.Empty();
|
|
}
|
|
|
|
void FProxyGenerationProcessor::ProcessJob(const FGuid& JobGuid, FProxyGenerationData* Data)
|
|
{
|
|
TArray<UObject*> OutAssetsToSync;
|
|
const FString AssetBaseName = FPackageName::GetShortName(Data->MergeData->ProxyBasePackageName);
|
|
const FString AssetBasePath = Data->MergeData->InOuter ? TEXT("") : FPackageName::GetLongPackagePath(Data->MergeData->ProxyBasePackageName) + TEXT("/");
|
|
|
|
UMaterialInstanceConstant* ProxyMaterial = nullptr;
|
|
|
|
if (!Data->RawMesh.IsEmpty())
|
|
{
|
|
// Don't recreate render states with the material update context as we will manually do it through
|
|
// the FStaticMeshComponentRecreateRenderStateContext below
|
|
FMaterialUpdateContext MaterialUpdateContext(FMaterialUpdateContext::EOptions::Default & ~FMaterialUpdateContext::EOptions::RecreateRenderStates);
|
|
|
|
// Retrieve flattened material data
|
|
FFlattenMaterial& FlattenMaterial = Data->Material;
|
|
|
|
// Resize flattened material
|
|
FMaterialUtilities::ResizeFlattenMaterial(FlattenMaterial, Data->MergeData->InProxySettings);
|
|
|
|
// Optimize flattened material
|
|
FMaterialUtilities::OptimizeFlattenMaterial(FlattenMaterial);
|
|
|
|
// Create a new proxy material instance
|
|
ProxyMaterial = ProxyMaterialUtilities::CreateProxyMaterialInstance(Data->MergeData->InOuter, Data->MergeData->InProxySettings.MaterialSettings, Data->MergeData->BaseMaterial, FlattenMaterial, AssetBasePath, AssetBaseName, OutAssetsToSync, &MaterialUpdateContext);
|
|
|
|
for (IMeshMergeExtension* Extension : Owner->MeshMergeExtensions)
|
|
{
|
|
Extension->OnCreatedProxyMaterial(Data->MergeData->StaticMeshComponents, ProxyMaterial);
|
|
}
|
|
|
|
// Set material static lighting usage flag if project has static lighting enabled
|
|
static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
|
|
const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnGameThread() != 0);
|
|
if (bAllowStaticLighting)
|
|
{
|
|
ProxyMaterial->CheckMaterialUsage(MATUSAGE_StaticLighting);
|
|
}
|
|
}
|
|
|
|
// Construct proxy static mesh
|
|
UPackage* MeshPackage = Data->MergeData->InOuter;
|
|
FString MeshAssetName = TEXT("SM_") + AssetBaseName;
|
|
if (MeshPackage == nullptr)
|
|
{
|
|
MeshPackage = CreatePackage( *(AssetBasePath + MeshAssetName));
|
|
MeshPackage->FullyLoad();
|
|
MeshPackage->Modify();
|
|
}
|
|
|
|
UStaticMesh* OldStaticMesh = FindObject<UStaticMesh>(MeshPackage, *MeshAssetName);
|
|
|
|
FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(OldStaticMesh);
|
|
|
|
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(MeshPackage, FName(*MeshAssetName), RF_Public | RF_Standalone);
|
|
StaticMesh->InitResources();
|
|
|
|
FString OutputPath = StaticMesh->GetPathName();
|
|
|
|
// make sure it has a new lighting guid
|
|
StaticMesh->SetLightingGuid();
|
|
|
|
// Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc).
|
|
StaticMesh->SetLightMapResolution(Data->MergeData->InProxySettings.LightMapResolution);
|
|
StaticMesh->SetLightMapCoordinateIndex(1);
|
|
|
|
|
|
FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
|
|
/*Don't allow the engine to recalculate normals*/
|
|
SrcModel.BuildSettings.bRecomputeNormals = false;
|
|
SrcModel.BuildSettings.bRecomputeTangents = false;
|
|
SrcModel.BuildSettings.bComputeWeightedNormals = true;
|
|
SrcModel.BuildSettings.bRemoveDegenerates = true;
|
|
SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
|
|
SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
|
|
SrcModel.BuildSettings.bGenerateLightmapUVs = Data->MergeData->InProxySettings.bGenerateLightmapUVs;
|
|
SrcModel.BuildSettings.bBuildReversedIndexBuffer = false;
|
|
if (!Data->MergeData->InProxySettings.bAllowDistanceField)
|
|
{
|
|
SrcModel.BuildSettings.DistanceFieldResolutionScale = 0.0f;
|
|
}
|
|
|
|
const bool bContainsImposters = Data->MergeData->ImposterComponents.Num() > 0;
|
|
FBox ImposterBounds(EForceInit::ForceInit);
|
|
|
|
TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = FStaticMeshAttributes(Data->RawMesh).GetPolygonGroupMaterialSlotNames();
|
|
|
|
auto RemoveVertexColorAndCommitMeshDescription = [&StaticMesh, &Data, &ProxyMaterial, &PolygonGroupMaterialSlotName]()
|
|
{
|
|
if (!Data->MergeData->InProxySettings.bAllowVertexColors)
|
|
{
|
|
//We cannot remove the vertex color with the mesh description so we assign a white value to all color
|
|
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = FStaticMeshAttributes(Data->RawMesh).GetVertexInstanceColors();
|
|
//set all value to white
|
|
for (const FVertexInstanceID VertexInstanceID : Data->RawMesh.VertexInstances().GetElementIDs())
|
|
{
|
|
VertexInstanceColors[VertexInstanceID] = FVector4f(1.0f, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
//Commit the FMeshDescription to the source model we just created
|
|
int32 SourceModelIndex = StaticMesh->GetNumSourceModels() - 1;
|
|
FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(SourceModelIndex, Data->RawMesh);
|
|
if (ensure(MeshDescription))
|
|
{
|
|
if (ProxyMaterial)
|
|
{
|
|
// Make sure the Proxy material have a valid ImportedMaterialSlotName
|
|
//The proxy material must be add only once and is always the first slot of the HLOD mesh
|
|
FStaticMaterial NewMaterial(ProxyMaterial);
|
|
if (MeshDescription->PolygonGroups().Num() > 0)
|
|
{
|
|
NewMaterial.ImportedMaterialSlotName = PolygonGroupMaterialSlotName[MeshDescription->PolygonGroups().GetFirstValidID()];
|
|
}
|
|
StaticMesh->GetStaticMaterials().Add(NewMaterial);
|
|
}
|
|
|
|
UStaticMesh::FCommitMeshDescriptionParams CommitParams;
|
|
CommitParams.bUseHashAsGuid = true;
|
|
StaticMesh->CommitMeshDescription(SourceModelIndex, CommitParams);
|
|
}
|
|
};
|
|
|
|
if (bContainsImposters)
|
|
{
|
|
TArray<UMaterialInterface*> ImposterMaterials;
|
|
|
|
// Merge imposter meshes to rawmesh
|
|
// The base material index is always one here as we assume we only have one HLOD material
|
|
FMeshMergeHelpers::MergeImpostersToMesh(Data->MergeData->ImposterComponents, Data->RawMesh, FVector::ZeroVector, 1, ImposterMaterials);
|
|
|
|
for (const UStaticMeshComponent* Component : Data->MergeData->ImposterComponents)
|
|
{
|
|
if (Component->GetStaticMesh())
|
|
{
|
|
ImposterBounds += Component->GetStaticMesh()->GetBoundingBox().TransformBy(Component->GetComponentToWorld());
|
|
}
|
|
}
|
|
RemoveVertexColorAndCommitMeshDescription();
|
|
|
|
for (UMaterialInterface* Material : ImposterMaterials)
|
|
{
|
|
//Set the ImportedMaterialSlotName in each imposter material
|
|
FStaticMaterial NewMaterial(Material);
|
|
if (Data->RawMesh.PolygonGroups().Num() > 0)
|
|
{
|
|
NewMaterial.ImportedMaterialSlotName = PolygonGroupMaterialSlotName[Data->RawMesh.PolygonGroups().GetFirstValidID()];
|
|
}
|
|
StaticMesh->GetStaticMaterials().Add(NewMaterial);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveVertexColorAndCommitMeshDescription();
|
|
}
|
|
|
|
// Nanite settings
|
|
StaticMesh->NaniteSettings.bEnabled = Data->MergeData->InProxySettings.bGenerateNaniteEnabledMesh;
|
|
StaticMesh->NaniteSettings.FallbackPercentTriangles = Data->MergeData->InProxySettings.NaniteProxyTrianglePercent * 0.01f;
|
|
StaticMesh->NaniteSettings.PositionPrecision = MIN_int32;
|
|
|
|
//Set the Imported version before calling the build
|
|
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
|
|
|
|
// setup section info map
|
|
TPolygonGroupAttributesConstRef<FName> PolygonGroupImportedMaterialSlotNames = Data->RawMesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
TArray<int32> UniqueMaterialIndices;
|
|
for (const FPolygonGroupID PolygonGroupID : Data->RawMesh.PolygonGroups().GetElementIDs())
|
|
{
|
|
int32 PolygonGroupMaterialIndex = PolygonGroupID.GetValue();
|
|
FName PolygonGroupName = PolygonGroupImportedMaterialSlotNames[PolygonGroupID];
|
|
if (PolygonGroupName != NAME_None)
|
|
{
|
|
for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++MaterialIndex)
|
|
{
|
|
if (StaticMesh->GetStaticMaterials()[MaterialIndex].ImportedMaterialSlotName == PolygonGroupName)
|
|
{
|
|
PolygonGroupMaterialIndex = MaterialIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!StaticMesh->GetStaticMaterials().IsValidIndex(PolygonGroupMaterialIndex))
|
|
{
|
|
PolygonGroupMaterialIndex = 0;
|
|
}
|
|
UniqueMaterialIndices.AddUnique(PolygonGroupMaterialIndex);
|
|
}
|
|
|
|
int32 SectionIndex = 0;
|
|
for (int32 UniqueMaterialIndex : UniqueMaterialIndices)
|
|
{
|
|
FMeshSectionInfo MeshSectionInfo(UniqueMaterialIndex);
|
|
|
|
// enable/disable section collision according to settings
|
|
MeshSectionInfo.bEnableCollision = Data->MergeData->InProxySettings.bCreateCollision;
|
|
|
|
StaticMesh->GetSectionInfoMap().Set(0, SectionIndex, MeshSectionInfo);
|
|
SectionIndex++;
|
|
}
|
|
|
|
StaticMesh->Build();
|
|
|
|
if (ImposterBounds.IsValid)
|
|
{
|
|
const FBox StaticMeshBox = StaticMesh->GetBoundingBox();
|
|
const FBox CombinedBox = StaticMeshBox + ImposterBounds;
|
|
StaticMesh->SetPositiveBoundsExtension((CombinedBox.Max - StaticMeshBox.Max));
|
|
StaticMesh->SetNegativeBoundsExtension((StaticMeshBox.Min - CombinedBox.Min));
|
|
StaticMesh->CalculateExtendedBounds();
|
|
}
|
|
|
|
StaticMesh->PostEditChange();
|
|
|
|
OutAssetsToSync.Add(StaticMesh);
|
|
|
|
if (OldStaticMesh != nullptr)
|
|
{
|
|
Algo::ForEach(RecreateRenderStateContext.GetComponentsUsingMesh(OldStaticMesh), [](UStaticMeshComponent* Component)
|
|
{
|
|
FObjectCacheEventSink::NotifyStaticMeshChanged_Concurrent(Component);
|
|
});
|
|
}
|
|
|
|
// Execute the delegate received from the user
|
|
Data->MergeData->CallbackDelegate.ExecuteIfBound(JobGuid, OutAssetsToSync);
|
|
}
|