1928 lines
71 KiB
C++
1928 lines
71 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "StaticMeshBuilder.h"
|
|
|
|
#include "BuildOptimizationHelper.h"
|
|
#include "Components.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "IMeshReductionInterfaces.h"
|
|
#include "IMeshReductionManagerModule.h"
|
|
#include "Logging/StructuredLog.h"
|
|
#include "MeshBuild.h"
|
|
#include "MeshDescriptionHelper.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "StaticMeshAttributes.h"
|
|
#include "StaticMeshOperations.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "Math/Bounds.h"
|
|
#include "NaniteBuilder.h"
|
|
#include "NaniteHelper.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "RenderMath.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogStaticMeshBuilder);
|
|
|
|
void BuildAllBufferOptimizations(
|
|
struct FStaticMeshLODResources& StaticMeshLOD,
|
|
const struct FMeshBuildSettings& LODBuildSettings,
|
|
TArray< uint32 >& IndexBuffer,
|
|
bool bNeeds32BitIndices,
|
|
const FConstMeshBuildVertexView& BuildVertices
|
|
);
|
|
|
|
FStaticMeshBuilder::FStaticMeshBuilder()
|
|
{
|
|
|
|
}
|
|
|
|
static bool UseNativeQuadraticReduction()
|
|
{
|
|
// Are we using our tool, or simplygon? The tool is only changed during editor restarts
|
|
IMeshReduction* ReductionModule = FModuleManager::Get().LoadModuleChecked<IMeshReductionManagerModule>("MeshReductionInterface").GetStaticMeshReductionInterface();
|
|
|
|
FString VersionString = ReductionModule->GetVersionString();
|
|
TArray<FString> SplitVersionString;
|
|
VersionString.ParseIntoArray(SplitVersionString, TEXT("_"), true);
|
|
|
|
bool bUseQuadricSimplier = SplitVersionString[0].Equals("QuadricMeshReduction");
|
|
return bUseQuadricSimplier;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute bounding box and sphere from position buffer
|
|
*/
|
|
static void ComputeBoundsFromPositionBuffer(const FPositionVertexBuffer& UsePositionBuffer, FBoxSphereBounds& BoundsOut)
|
|
{
|
|
// Calculate the bounding box.
|
|
FBounds3f Bounds;
|
|
for (uint32 VertexIndex = 0; VertexIndex < UsePositionBuffer.GetNumVertices(); VertexIndex++)
|
|
{
|
|
Bounds += UsePositionBuffer.VertexPosition(VertexIndex);
|
|
}
|
|
|
|
// Calculate the bounding sphere, using the center of the bounding box as the origin.
|
|
FVector3f Center = Bounds.GetCenter();
|
|
float RadiusSqr = 0.0f;
|
|
for (uint32 VertexIndex = 0; VertexIndex < UsePositionBuffer.GetNumVertices(); VertexIndex++)
|
|
{
|
|
RadiusSqr = FMath::Max( RadiusSqr, ( UsePositionBuffer.VertexPosition(VertexIndex) - Center ).SizeSquared() );
|
|
}
|
|
|
|
BoundsOut.Origin = FVector(Center);
|
|
BoundsOut.BoxExtent = FVector(Bounds.GetExtent());
|
|
BoundsOut.SphereRadius = FMath::Sqrt( RadiusSqr );
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute bounding box and sphere from vertices
|
|
*/
|
|
static void ComputeBoundsFromVertexList(const TArray<FStaticMeshBuildVertex>& Vertices, FBoxSphereBounds& BoundsOut)
|
|
{
|
|
// Calculate the bounding box.
|
|
FBounds3f Bounds;
|
|
for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); VertexIndex++)
|
|
{
|
|
Bounds += Vertices[VertexIndex].Position;
|
|
}
|
|
|
|
// Calculate the bounding sphere, using the center of the bounding box as the origin.
|
|
FVector3f Center = Bounds.GetCenter();
|
|
float RadiusSqr = 0.0f;
|
|
for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); VertexIndex++)
|
|
{
|
|
RadiusSqr = FMath::Max( RadiusSqr, ( Vertices[VertexIndex].Position - Center ).SizeSquared() );
|
|
}
|
|
|
|
BoundsOut.Origin = FVector(Center);
|
|
BoundsOut.BoxExtent = FVector(Bounds.GetExtent());
|
|
BoundsOut.SphereRadius = FMath::Sqrt( RadiusSqr );
|
|
}
|
|
|
|
static void ScaleStaticMeshVertex(
|
|
FVector3f& Position,
|
|
FVector3f& TangentX,
|
|
FVector3f& TangentY,
|
|
FVector3f& TangentZ,
|
|
FVector3f Scale,
|
|
bool bNeedTangents,
|
|
bool bUseLegacyTangentScaling
|
|
)
|
|
{
|
|
Position *= Scale;
|
|
if (bNeedTangents)
|
|
{
|
|
if (bUseLegacyTangentScaling)
|
|
{
|
|
// Apply incorrect inverse scale to tangents to match an old bug, for legacy assets only
|
|
TangentX /= Scale;
|
|
TangentY /= Scale;
|
|
}
|
|
else
|
|
{
|
|
// Tangents should transform by directly applying the same scale as the geometry; it's only the normal that needs an inverse scale
|
|
TangentX *= Scale;
|
|
TangentY *= Scale;
|
|
}
|
|
TangentX.Normalize();
|
|
TangentY.Normalize();
|
|
}
|
|
else
|
|
{
|
|
TangentX = FVector3f(1.0f, 0.0f, 0.0f);
|
|
TangentY = FVector3f(0.0f, 1.0f, 0.0f);
|
|
}
|
|
TangentZ /= Scale;
|
|
TangentZ.Normalize();
|
|
}
|
|
|
|
struct FStaticMeshNaniteBuildContext
|
|
{
|
|
FMeshNaniteSettings Settings;
|
|
UStaticMesh* StaticMesh = nullptr;
|
|
const ITargetPlatform* TargetPlatform = nullptr;
|
|
const FStaticMeshSourceModel* SourceModel = nullptr;
|
|
Nanite::IBuilderModule* Builder = nullptr;
|
|
|
|
bool bIsAssembly : 1 = false;
|
|
bool bIsAssemblyPart : 1 = false;
|
|
bool bHiResSourceModel : 1 = false;
|
|
|
|
bool IsValid() const { return StaticMesh != nullptr; }
|
|
};
|
|
|
|
static bool PrepareNaniteStaticMeshBuild(
|
|
FStaticMeshNaniteBuildContext& OutContext,
|
|
UStaticMesh* StaticMesh,
|
|
const ITargetPlatform* TargetPlatform,
|
|
FStaticMeshNaniteBuildContext* ParentContext = nullptr)
|
|
{
|
|
check(ParentContext == nullptr || ParentContext->IsValid());
|
|
|
|
if (ParentContext == nullptr && !StaticMesh->IsNaniteEnabled())
|
|
{
|
|
// We don't need to build Nanite for this static mesh
|
|
return false;
|
|
}
|
|
|
|
#if NANITE_ASSEMBLY_DATA
|
|
const bool bIsAssembly = StaticMesh->NaniteSettings.NaniteAssemblyData.IsValid();
|
|
#else
|
|
const bool bIsAssembly = false;
|
|
#endif
|
|
const bool bTargetSupportsNanite = DoesTargetPlatformSupportNanite(TargetPlatform);
|
|
FStaticMeshSourceModel& LOD0SourceModel = StaticMesh->GetSourceModel(0);
|
|
FStaticMeshSourceModel& HiResSourceModel = StaticMesh->GetHiResSourceModel();
|
|
const bool bHasHiResSourceModel = HiResSourceModel.IsMeshDescriptionValid();
|
|
|
|
if (!bTargetSupportsNanite && bHasHiResSourceModel)
|
|
{
|
|
// If the target we're building for doesn't support Nanite and we have a hi-res source model, then we don't need to build
|
|
// Nanite, since LOD0 will remain unsimplified.
|
|
// NOTE: This is an optimization for non-Nanite build times and is only valid because we know the DDC key for static mesh
|
|
// cache will be different for meshes with hi-res source data between Nanite and non-Nanite platforms. Otherwise, this
|
|
// would have the potential to cause non-Nanite platforms to cache static mesh data without Nanite resources that Nanite
|
|
// platforms would subsequently load from the DDC.
|
|
return false;
|
|
}
|
|
|
|
const FMeshDescription* LOD0MeshDescription = LOD0SourceModel.GetOrCacheMeshDescription();
|
|
if (LOD0MeshDescription == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Invalid mesh description during Nanite build [%s]."), *StaticMesh->GetFullName());
|
|
return false;
|
|
}
|
|
if (LOD0MeshDescription->IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Empty mesh description during Nanite build [%s]."), *StaticMesh->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
// Only do Nanite build for the hi-res source model if we have one, the target platform supports Nanite, AND the mesh description
|
|
// is well-formed. In all other cases, we will build Nanite from LOD0. This will replace the output VertexBuffers/etc with
|
|
// the fractional Nanite cut to be stored as LOD0 RenderData.
|
|
// NOTE: We also want to use LOD0 for targets that do not support Nanite (even if a hi-res source model was provided)
|
|
// so that it generates the fallback, in which case the Nanite bulk will be stripped
|
|
bool bUseHiResSourceModel = false;
|
|
if (bTargetSupportsNanite && bHasHiResSourceModel)
|
|
{
|
|
if (const FMeshDescription* HiResMeshDescription = HiResSourceModel.GetOrCacheMeshDescription())
|
|
{
|
|
if (HiResMeshDescription->IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Display,
|
|
TEXT("Invalid hi-res mesh description during Nanite build [%s]. The hi-res mesh is empty. ")
|
|
TEXT("This is not supported and LOD 0 will be used as a fallback to build Nanite data."),
|
|
*StaticMesh->GetFullName());
|
|
}
|
|
else
|
|
{
|
|
// Make sure hi-res mesh data has the same amount of sections. If not, rendering bugs and issues will show
|
|
// up because the Nanite render must use the LOD 0 sections.
|
|
const int32 NumLOD0PolyGroups = LOD0MeshDescription->PolygonGroups().Num();
|
|
if (HiResMeshDescription->PolygonGroups().Num() > NumLOD0PolyGroups)
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Display,
|
|
TEXT("Invalid hi-res mesh description during Nanite build [%s]. ")
|
|
TEXT("The number of sections from the hires mesh is higher than LOD 0 section count. ")
|
|
TEXT("This is not supported and LOD 0 will be used as a fallback to build Nanite data."),
|
|
*StaticMesh->GetFullName());
|
|
}
|
|
else
|
|
{
|
|
if (HiResMeshDescription->PolygonGroups().Num() < NumLOD0PolyGroups)
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Display,
|
|
TEXT("Nanite hi-res mesh description for [%s] has fewer sections than lod 0. ")
|
|
TEXT("Verify you have the proper material id result when Nanite is turned on."),
|
|
*StaticMesh->GetFullName());
|
|
}
|
|
bUseHiResSourceModel = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OutContext.Settings = StaticMesh->NaniteSettings;
|
|
Nanite::CorrectFallbackSettings(OutContext.Settings, LOD0MeshDescription->Triangles().Num(), bIsAssembly, /* bIsRayTracing */ false);
|
|
|
|
const bool bIsAssemblyPart = ParentContext != nullptr;
|
|
if (bIsAssemblyPart)
|
|
{
|
|
// For now, inherit these from the parent settings
|
|
// TODO: Nanite-Assemblies - These overrides have to be considered if/when we cache the part intermediates in DDC.
|
|
OutContext.Settings.bPreserveArea = ParentContext->Settings.bPreserveArea;
|
|
OutContext.Settings.bExplicitTangents = ParentContext->Settings.bExplicitTangents;
|
|
}
|
|
|
|
OutContext.StaticMesh = StaticMesh;
|
|
OutContext.SourceModel = bUseHiResSourceModel ? &HiResSourceModel : &LOD0SourceModel;
|
|
OutContext.TargetPlatform = TargetPlatform;
|
|
OutContext.Builder = bIsAssemblyPart ? ParentContext->Builder : &Nanite::IBuilderModule::Get();
|
|
OutContext.bIsAssembly = bIsAssembly;
|
|
OutContext.bIsAssemblyPart = bIsAssemblyPart;
|
|
OutContext.bHiResSourceModel = bUseHiResSourceModel;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool InitNaniteBuildInput(
|
|
FStaticMeshNaniteBuildContext& Context,
|
|
Nanite::IBuilderModule::FInputMeshData& OutData,
|
|
FBoxSphereBounds& OutVertexBounds)
|
|
{
|
|
FMeshDescription MeshDescription;
|
|
if (!Context.SourceModel->CloneMeshDescription(MeshDescription))
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error,
|
|
TEXT("Failed to clone mesh description during Nanite build [%s]."),
|
|
*Context.StaticMesh->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
if (MeshDescription.IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error,
|
|
TEXT("Cannot build an empty mesh description during Nanite build [%s]."),
|
|
*Context.StaticMesh->GetFullName());
|
|
return false;
|
|
}
|
|
|
|
FMeshBuildSettings& BuildSettings = Context.StaticMesh->GetSourceModel(0).BuildSettings;
|
|
|
|
// Only build tangents if they are explicitly enabled or we're going to be injecting this vertex data directly into
|
|
// LOD0 of a generated fallback
|
|
const bool bFallbackUsesInputMeshData =
|
|
!Context.bIsAssemblyPart &&
|
|
!Context.bHiResSourceModel &&
|
|
Context.Settings.FallbackPercentTriangles == 1.0f &&
|
|
Context.Settings.FallbackRelativeError == 0.0f;
|
|
const bool bNeedTangents = Context.Settings.bExplicitTangents || bFallbackUsesInputMeshData;
|
|
|
|
// compute tangents, lightmap UVs, etc
|
|
FMeshDescriptionHelper MeshDescriptionHelper(&BuildSettings);
|
|
MeshDescriptionHelper.SetupRenderMeshDescription(Context.StaticMesh, MeshDescription, true, bNeedTangents);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Prepare the PerSectionIndices array so we can optimize the index buffer for the GPU
|
|
TArray<TArray<uint32>> PerSectionIndices;
|
|
PerSectionIndices.AddDefaulted(MeshDescription.PolygonGroups().Num());
|
|
|
|
FStaticMeshSectionArray StaticMeshSections;
|
|
StaticMeshSections.Empty(MeshDescription.PolygonGroups().Num());
|
|
|
|
// We only need this to de-duplicate vertices inside of BuildVertexBuffer
|
|
// (And only if there are overlapping corners in the mesh description).
|
|
TArray<int32> RemapVerts;
|
|
|
|
// Nanite does not need the wedge map returned (mainly used by non-Nanite mesh painting).
|
|
const bool bNeedWedgeMap = false;
|
|
TArray<int32> WedgeMap;
|
|
|
|
// Build the vertex and index buffer
|
|
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
|
|
Context.StaticMesh,
|
|
MeshDescription,
|
|
BuildSettings,
|
|
WedgeMap,
|
|
StaticMeshSections,
|
|
PerSectionIndices,
|
|
OutData.Vertices,
|
|
MeshDescriptionHelper.GetOverlappingCorners(),
|
|
RemapVerts,
|
|
OutVertexBounds,
|
|
bNeedTangents,
|
|
bNeedWedgeMap);
|
|
|
|
// Concatenate the per-section index buffers.
|
|
bool bNeeds32BitIndices = false;
|
|
UE::Private::StaticMeshBuilder::BuildCombinedSectionIndices(
|
|
PerSectionIndices,
|
|
StaticMeshSections,
|
|
OutData.TriangleIndices,
|
|
bNeeds32BitIndices);
|
|
|
|
OutData.Sections = Nanite::BuildMeshSections(StaticMeshSections);
|
|
|
|
// Nanite build requires the section material indices to have already been resolved from the SectionInfoMap
|
|
// as the indices are baked into the FMaterialTriangles.
|
|
for (int32 SectionIndex = 0; SectionIndex < OutData.Sections.Num(); SectionIndex++)
|
|
{
|
|
OutData.Sections[SectionIndex].MaterialIndex = Context.StaticMesh->GetSectionInfoMap().Get(0, SectionIndex).MaterialIndex;
|
|
}
|
|
|
|
OutData.VertexBounds.Min = FVector3f(OutVertexBounds.Origin - OutVertexBounds.BoxExtent);
|
|
OutData.VertexBounds.Max = FVector3f(OutVertexBounds.Origin + OutVertexBounds.BoxExtent);
|
|
|
|
TVertexInstanceAttributesRef<FVector2f const> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
OutData.NumTexCoords = VertexInstanceUVs.IsValid() ? VertexInstanceUVs.GetNumChannels() : 0;
|
|
|
|
const uint32 TriangleCount = OutData.TriangleIndices.Num() / 3;
|
|
OutData.TriangleCounts.Add(TriangleCount);
|
|
|
|
if (!Context.Builder->BuildMaterialIndices(OutData.Sections, TriangleCount, OutData.MaterialIndices))
|
|
{
|
|
UE_LOGFMT_NSLOC(LogStaticMesh, Warning, "StaticMesh", "NaniteBuildStaticMeshError", "Failed to build Nanite from static mesh. See previous line(s) for details.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if NANITE_ASSEMBLY_DATA
|
|
|
|
static Nanite::FAssemblyPartResourceRef BuildNaniteAssemblyPart(FStaticMeshNaniteBuildContext& ParentContext, UStaticMesh* PartMesh)
|
|
{
|
|
FStaticMeshNaniteBuildContext ChildContext;
|
|
if (!PrepareNaniteStaticMeshBuild(ChildContext, PartMesh, ParentContext.TargetPlatform, &ParentContext))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
Nanite::IBuilderModule::FInputMeshData InputMeshData;
|
|
FBoxSphereBounds VertexBounds;
|
|
if (!InitNaniteBuildInput(ChildContext, InputMeshData, VertexBounds))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return ChildContext.Builder->BuildAssemblyPart(
|
|
InputMeshData,
|
|
ChildContext.Settings
|
|
);
|
|
}
|
|
|
|
#endif // NANITE_ASSEMBLY_DATA
|
|
|
|
static bool InitNaniteAssemblyData(
|
|
FStaticMeshNaniteBuildContext& Context,
|
|
Nanite::FInputAssemblyData& OutData)
|
|
{
|
|
#if NANITE_ASSEMBLY_DATA
|
|
if (!Context.StaticMesh->HasCachedNaniteAssemblyReferences())
|
|
{
|
|
UE_LOGFMT_NSLOC(LogStaticMesh, Warning, "StaticMesh", "NaniteBuildAssemblyUncachedError",
|
|
"Failed to build Nanite Assembly static mesh {MeshPath}. The referenced static meshes were not cached before build.",
|
|
("MeshPath", Context.StaticMesh->GetPathName()));
|
|
return false;
|
|
}
|
|
|
|
// Get the assembly references that should have been resolved before build
|
|
const FNaniteAssemblyData& AssemblyData = Context.Settings.NaniteAssemblyData;
|
|
const TArray<TObjectPtr<UStaticMesh>>& PartReferences = Context.StaticMesh->GetCachedNaniteAssemblyReferences();
|
|
check(PartReferences.Num() == AssemblyData.Parts.Num());
|
|
|
|
TMap<UStaticMesh*, Nanite::FAssemblyPartResourceRef> ResourceLookup;
|
|
ResourceLookup.Reserve(PartReferences.Num());
|
|
|
|
for (int32 PartIndex = 0; PartIndex < AssemblyData.Parts.Num(); ++PartIndex)
|
|
{
|
|
Nanite::FAssemblyPartResourceRef Resource;
|
|
|
|
if (UStaticMesh* PartMesh = PartReferences[PartIndex])
|
|
{
|
|
if (Nanite::FAssemblyPartResourceRef* ExistingResource = ResourceLookup.Find(PartMesh))
|
|
{
|
|
Resource = *ExistingResource;
|
|
}
|
|
else
|
|
{
|
|
Resource = BuildNaniteAssemblyPart(Context, PartMesh);
|
|
ResourceLookup.Add(PartMesh, Resource);
|
|
}
|
|
}
|
|
|
|
if (!Resource.IsValid())
|
|
{
|
|
UE_LOGFMT_NSLOC(LogStaticMesh, Warning, "StaticMesh", "NaniteBuildAssemblyPartError",
|
|
"Failed to build Nanite assembly part from static mesh ({PartPath}). See previous line(s) for details.",
|
|
("PartPath", AssemblyData.Parts[PartIndex].MeshObjectPath.GetAssetName()));
|
|
return false;
|
|
}
|
|
|
|
auto& OutPart = OutData.Parts.AddDefaulted_GetRef();
|
|
OutPart.Resource = Resource;
|
|
|
|
// Apply the material remap
|
|
const TArray<int32>& MaterialRemap = AssemblyData.Parts[PartIndex].MaterialRemap;
|
|
for (int32 i = 0; i < Nanite::MaxSectionArraySize; ++i)
|
|
{
|
|
if (MaterialRemap.Num() == 0)
|
|
{
|
|
// No remaps = match indices
|
|
OutPart.MaterialRemap[i] = i;
|
|
}
|
|
else if (MaterialRemap.IsValidIndex(i))
|
|
{
|
|
OutPart.MaterialRemap[i] = MaterialRemap[i];
|
|
}
|
|
else
|
|
{
|
|
// Index is unrepresented in the remap (may not be a valid index). Fallback on a valid material index
|
|
OutPart.MaterialRemap[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
OutData.Nodes = AssemblyData.Nodes;
|
|
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static void BuildNaniteFallbackMeshDescription(
|
|
FStaticMeshNaniteBuildContext& Context,
|
|
const Nanite::IBuilderModule::FOutputMeshData& InMeshData,
|
|
FMeshDescription& OutMesh
|
|
)
|
|
{
|
|
OutMesh.Empty();
|
|
|
|
// Lod zero was built with scaling build settings, we have to remove the scaling from the data since the other LODs build will also apply the scaling.
|
|
const FVector3f InverseBuildScale = FVector3f(FVector(1.0) / Context.SourceModel->BuildSettings.BuildScale3D);
|
|
const bool bBuildScaleActive = !InverseBuildScale.Equals(FVector3f(1.0f), UE_SMALL_NUMBER);
|
|
const bool bUseLegacyTangentScaling = Context.StaticMesh->GetLegacyTangentScaling();
|
|
|
|
FStaticMeshAttributes Attributes(OutMesh);
|
|
Attributes.Register();
|
|
|
|
const int32 NumVertices = InMeshData.Vertices.Position.Num();
|
|
const int32 NumUVChannels = InMeshData.Vertices.UVs.Num();
|
|
const int32 NumTriangles = InMeshData.TriangleIndices.Num() / 3;
|
|
const int32 NumPolyGroups = InMeshData.Sections.Num();
|
|
|
|
OutMesh.ReserveNewVertices(NumVertices);
|
|
OutMesh.ReserveNewVertexInstances(NumVertices);
|
|
OutMesh.ReserveNewTriangles(NumTriangles);
|
|
OutMesh.ReserveNewPolygonGroups(NumPolyGroups);
|
|
|
|
OutMesh.SetNumUVChannels(NumUVChannels);
|
|
OutMesh.VertexInstanceAttributes().SetAttributeChannelCount(MeshAttribute::VertexInstance::TextureCoordinate, NumUVChannels);
|
|
for (int32 UVChannelIndex = 0; UVChannelIndex < NumUVChannels; ++UVChannelIndex)
|
|
{
|
|
OutMesh.ReserveNewUVs(NumVertices, UVChannelIndex);
|
|
}
|
|
|
|
TVertexAttributesRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
|
|
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
|
|
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
|
|
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
|
|
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
|
|
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
|
|
TPolygonGroupAttributesRef<FName> PolygonGroupMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
|
|
|
|
for (int32 InVertIndex = 0; InVertIndex < NumVertices; ++InVertIndex)
|
|
{
|
|
const FVertexID VertexID(InVertIndex);
|
|
const FVertexInstanceID VertexInstanceID(InVertIndex);
|
|
|
|
// TODO: Deduplicate vertex positions?
|
|
OutMesh.CreateVertexWithID(VertexID);
|
|
OutMesh.CreateVertexInstanceWithID(VertexInstanceID, VertexID);
|
|
|
|
FVector3f Position = InMeshData.Vertices.Position[InVertIndex];
|
|
FVector3f TangentX = InMeshData.Vertices.TangentX[InVertIndex];
|
|
FVector3f TangentY = InMeshData.Vertices.TangentY[InVertIndex];
|
|
FVector3f TangentZ = InMeshData.Vertices.TangentZ[InVertIndex];
|
|
|
|
if (bBuildScaleActive)
|
|
{
|
|
ScaleStaticMeshVertex(
|
|
Position,
|
|
TangentX,
|
|
TangentY,
|
|
TangentZ,
|
|
InverseBuildScale,
|
|
true, // bNeedTangents
|
|
bUseLegacyTangentScaling
|
|
);
|
|
}
|
|
|
|
const float BinormalSign = GetBasisDeterminantSign(FVector(TangentX), FVector(TangentY), FVector(TangentZ));
|
|
const FColor Color = InMeshData.Vertices.Color.IsValidIndex(InVertIndex) ?
|
|
InMeshData.Vertices.Color[InVertIndex] : FColor::White;
|
|
|
|
VertexPositions.Set(VertexID, Position);
|
|
VertexInstanceNormals.Set(VertexInstanceID, TangentZ);
|
|
VertexInstanceTangents.Set(VertexInstanceID, TangentX);
|
|
VertexInstanceBinormalSigns.Set(VertexInstanceID, BinormalSign);
|
|
VertexInstanceColors.Set(VertexInstanceID, FVector4f(FLinearColor(Color)));
|
|
|
|
for (int32 UVChannelIndex = 0; UVChannelIndex < NumUVChannels; ++UVChannelIndex)
|
|
{
|
|
const FVector2f UV = InMeshData.Vertices.UVs[UVChannelIndex][InVertIndex];
|
|
VertexInstanceUVs.Set(VertexInstanceID, UVChannelIndex, UV);
|
|
}
|
|
}
|
|
|
|
const TArray<FStaticMaterial>& StaticMaterials = Context.StaticMesh->GetStaticMaterials();
|
|
for (const Nanite::FMeshDataSection& Section : InMeshData.Sections)
|
|
{
|
|
const FPolygonGroupID PolygonGroupID = OutMesh.CreatePolygonGroup();
|
|
const FName MaterialSlotName = StaticMaterials.IsValidIndex(Section.MaterialIndex) ?
|
|
StaticMaterials[Section.MaterialIndex].ImportedMaterialSlotName : NAME_None;
|
|
PolygonGroupMaterialSlotNames.Set(PolygonGroupID, MaterialSlotName);
|
|
|
|
for (uint32 TriIndex = 0; TriIndex < Section.NumTriangles; ++TriIndex)
|
|
{
|
|
const FVertexInstanceID TriVertInstanceIDs[] = {
|
|
FVertexInstanceID(InMeshData.TriangleIndices[Section.FirstIndex + TriIndex * 3 + 0]),
|
|
FVertexInstanceID(InMeshData.TriangleIndices[Section.FirstIndex + TriIndex * 3 + 1]),
|
|
FVertexInstanceID(InMeshData.TriangleIndices[Section.FirstIndex + TriIndex * 3 + 2])
|
|
};
|
|
|
|
OutMesh.CreateTriangle(PolygonGroupID, MakeConstArrayView(TriVertInstanceIDs, 3));
|
|
}
|
|
}
|
|
}
|
|
|
|
struct FRayTracingFallbackBuildContext
|
|
{
|
|
TArray<float, TInlineAllocator<2>> PercentTriangles;
|
|
TArray<FMeshDescription> MeshDescriptions;
|
|
Nanite::FRayTracingFallbackBuildSettings Settings;
|
|
|
|
int32 NumFallbackLODs() const
|
|
{
|
|
return MeshDescriptions.Num();
|
|
}
|
|
};
|
|
|
|
static bool BuildNanite(
|
|
FStaticMeshNaniteBuildContext& Context,
|
|
FStaticMeshLODResources& LOD0Resources,
|
|
FMeshDescription& LOD0MeshDescription,
|
|
Nanite::FResources& NaniteResources,
|
|
FRayTracingFallbackBuildContext& RayTracingFallbackBuildContext
|
|
)
|
|
{
|
|
if (!ensure(Context.IsValid()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE( FStaticMeshBuilder::BuildNanite );
|
|
|
|
// If applicable, recursively gather and build assembly references, and form their final hierarchy
|
|
Nanite::FInputAssemblyData InputAssemblyData;
|
|
if (Context.bIsAssembly && !InitNaniteAssemblyData(Context, InputAssemblyData))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Build new vertex buffers
|
|
Nanite::IBuilderModule::FInputMeshData InputMeshData;
|
|
if (!InitNaniteBuildInput(Context, InputMeshData, LOD0Resources.SourceMeshBounds))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We don't need to generate a fallback when using a high res source model. Regular static mesh build will handle it
|
|
const bool bGenerateFallback = !Context.bHiResSourceModel;
|
|
const bool bGenerateRayTracingFallback = RayTracingFallbackBuildContext.NumFallbackLODs() > 0;
|
|
Nanite::IBuilderModule::FOutputMeshData FallbackMeshData;
|
|
Nanite::IBuilderModule::FOutputMeshData RayTracingFallbackMeshData;
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!Context.Builder->Build(
|
|
NaniteResources,
|
|
InputMeshData,
|
|
bGenerateFallback ? &FallbackMeshData : nullptr,
|
|
bGenerateRayTracingFallback ? &RayTracingFallbackMeshData : nullptr,
|
|
bGenerateRayTracingFallback ? &RayTracingFallbackBuildContext.Settings : nullptr,
|
|
Context.Settings,
|
|
&InputAssemblyData
|
|
))
|
|
{
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UE_LOGFMT_NSLOC(LogStaticMesh, Warning, "StaticMesh", "NaniteHiResBuildError", "Failed to build Nanite for HiRes static mesh. See previous line(s) for details.");
|
|
return false;
|
|
}
|
|
|
|
const FMeshBuildSettings& BuildSettings = Context.StaticMesh->GetSourceModel(0).BuildSettings;
|
|
|
|
auto HasValidSections = [](Nanite::IBuilderModule::FOutputMeshData& MeshData)
|
|
{
|
|
bool bHasValidSections = false;
|
|
for (const Nanite::FMeshDataSection& Section : MeshData.Sections)
|
|
{
|
|
if (Section.NumTriangles > 0)
|
|
{
|
|
bHasValidSections = true;
|
|
break;
|
|
}
|
|
}
|
|
return bHasValidSections;
|
|
};
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Copy over the output data to the static mesh LOD data
|
|
// Certain output LODs might be empty if the builder decided it wasn't needed (then remove these LODs again)
|
|
// TODO: Is this ever the case with LOD 0 though?
|
|
if (bGenerateFallback)
|
|
{
|
|
// If there are valid sections then copy over data to the LODResource
|
|
if (HasValidSections(FallbackMeshData))
|
|
{
|
|
bool bNeeds32BitIndices = false;
|
|
for (Nanite::FMeshDataSection& Section : FallbackMeshData.Sections)
|
|
{
|
|
bNeeds32BitIndices |= Section.MaxVertexIndex > MAX_uint16;
|
|
}
|
|
|
|
LOD0Resources.Sections = Nanite::BuildStaticMeshSections(FallbackMeshData.Sections);
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::BufferInit);
|
|
|
|
FStaticMeshVertexBufferFlags StaticMeshVertexBufferFlags;
|
|
StaticMeshVertexBufferFlags.bNeedsCPUAccess = true;
|
|
StaticMeshVertexBufferFlags.bUseBackwardsCompatibleF16TruncUVs = BuildSettings.bUseBackwardsCompatibleF16TruncUVs;
|
|
|
|
const FConstMeshBuildVertexView OutputMeshVertices = MakeConstMeshBuildVertexView(FallbackMeshData.Vertices);
|
|
LOD0Resources.VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(BuildSettings.bUseHighPrecisionTangentBasis);
|
|
LOD0Resources.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(BuildSettings.bUseFullPrecisionUVs);
|
|
LOD0Resources.VertexBuffers.StaticMeshVertexBuffer.Init(OutputMeshVertices, StaticMeshVertexBufferFlags);
|
|
LOD0Resources.VertexBuffers.PositionVertexBuffer.Init(OutputMeshVertices);
|
|
LOD0Resources.VertexBuffers.ColorVertexBuffer.Init(OutputMeshVertices);
|
|
|
|
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
|
|
LOD0Resources.IndexBuffer.SetIndices(FallbackMeshData.TriangleIndices, IndexBufferStride);
|
|
|
|
BuildAllBufferOptimizations(LOD0Resources, BuildSettings, FallbackMeshData.TriangleIndices, bNeeds32BitIndices, OutputMeshVertices);
|
|
|
|
// Fill out the mesh description for non-Nanite build/reduction
|
|
BuildNaniteFallbackMeshDescription(Context, FallbackMeshData, LOD0MeshDescription);
|
|
}
|
|
else
|
|
{
|
|
// Initialize the mesh description as empty
|
|
FStaticMeshAttributes(LOD0MeshDescription).Register();
|
|
}
|
|
}
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bGenerateRayTracingFallback)
|
|
{
|
|
if (HasValidSections(RayTracingFallbackMeshData))
|
|
{
|
|
// Fill out the mesh description for ray tracing fallback build/reduction
|
|
BuildNaniteFallbackMeshDescription(Context, RayTracingFallbackMeshData, RayTracingFallbackBuildContext.MeshDescriptions[0]);
|
|
}
|
|
else
|
|
{
|
|
// Initialize the mesh description as empty
|
|
FStaticMeshAttributes(RayTracingFallbackBuildContext.MeshDescriptions[0]).Register();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PrepareRayTracingFallbackBuild(
|
|
const FStaticMeshNaniteBuildContext& NaniteBuildContext,
|
|
FRayTracingFallbackBuildContext& OutBuildContext)
|
|
{
|
|
const UStaticMesh* StaticMesh = NaniteBuildContext.StaticMesh;
|
|
const ITargetPlatform* TargetPlatform = NaniteBuildContext.TargetPlatform;
|
|
check(StaticMesh && TargetPlatform);
|
|
|
|
if (StaticMesh->bSupportRayTracing && TargetPlatform->UsesRayTracing())
|
|
{
|
|
static const TConsoleVariableData<bool>* CVarRayTracingProxies = IConsoleManager::Get().FindTConsoleVariableDataBool(TEXT("r.StaticMesh.RayTracingProxies"));
|
|
|
|
const FMeshRayTracingProxySettings& Settings = StaticMesh->RayTracingProxySettings;
|
|
const bool bNeedsRayTracingProxy =
|
|
(CVarRayTracingProxies && CVarRayTracingProxies->GetValueOnAnyThread())
|
|
&& (Settings.bEnabled || ShouldGenerateRayTracingProxiesByDefault());
|
|
|
|
if (bNeedsRayTracingProxy)
|
|
{
|
|
// LOD0 is generated by Nanite build and is already simplified so no need to reduce
|
|
OutBuildContext.PercentTriangles.Add(1.f);
|
|
OutBuildContext.PercentTriangles.Add(Settings.LOD1PercentTriangles);
|
|
OutBuildContext.MeshDescriptions.SetNum(OutBuildContext.PercentTriangles.Num());
|
|
|
|
FMeshNaniteSettings NaniteSettings;
|
|
NaniteSettings.FallbackTarget = Settings.FallbackTarget;
|
|
NaniteSettings.FallbackPercentTriangles = Settings.FallbackPercentTriangles;
|
|
NaniteSettings.FallbackRelativeError = Settings.FallbackRelativeError;
|
|
const FMeshDescription* InputMeshDescription = NaniteBuildContext.SourceModel->GetCachedMeshDescription();
|
|
check(InputMeshDescription);
|
|
|
|
Nanite::CorrectFallbackSettings(NaniteSettings, InputMeshDescription->Triangles().Num(), NaniteBuildContext.bIsAssembly, /* bIsRayTracing */ true);
|
|
|
|
OutBuildContext.Settings.FallbackPercentTriangles = NaniteSettings.FallbackPercentTriangles;
|
|
OutBuildContext.Settings.FallbackRelativeError = NaniteSettings.FallbackRelativeError;
|
|
|
|
// limit FoliageOverOcclusionBias since setting it to 1.0 removes all triangles and causes issues at runtime code paths.
|
|
OutBuildContext.Settings.FoliageOverOcclusionBias = FMath::Min(Settings.FoliageOverOcclusionBias, 0.9f);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FStaticMeshBuilder::Build(FStaticMeshRenderData& StaticMeshRenderData, const FStaticMeshBuildParameters& BuildParameters)
|
|
{
|
|
if (BuildParameters.TargetPlatform == nullptr)
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Provided FStaticMeshBuildParameters must have a valid TargetPlatform."));
|
|
return false;
|
|
}
|
|
|
|
UStaticMesh* StaticMesh = BuildParameters.StaticMesh;
|
|
const FStaticMeshLODGroup& LODGroup = BuildParameters.LODGroup;
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!StaticMesh->IsMeshDescriptionValid(0))
|
|
{
|
|
//Warn the user that there is no mesh description data
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Cannot find a valid mesh description to build the asset."));
|
|
return false;
|
|
}
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (StaticMeshRenderData.LODResources.Num() > 0)
|
|
{
|
|
//At this point the render data is suppose to be empty
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Cannot build static mesh render data twice [%s]."), *StaticMesh->GetFullName());
|
|
|
|
//Crash in debug
|
|
checkSlow(StaticMeshRenderData.LODResources.Num() == 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build);
|
|
|
|
const int32 NumSourceModels = StaticMesh->GetNumSourceModels();
|
|
StaticMeshRenderData.AllocateLODResources(NumSourceModels);
|
|
|
|
FStaticMeshNaniteBuildContext NaniteBuildContext;
|
|
const bool bBuildNanite = PrepareNaniteStaticMeshBuild(NaniteBuildContext, StaticMesh, BuildParameters.TargetPlatform);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FRayTracingFallbackBuildContext RayTracingFallbackBuildContext;
|
|
bool bBuildRayTracingFallback = false;
|
|
if (bBuildNanite)
|
|
{
|
|
bBuildRayTracingFallback = PrepareRayTracingFallbackBuild(NaniteBuildContext, RayTracingFallbackBuildContext);
|
|
}
|
|
|
|
int32 NumTasks = NumSourceModels;
|
|
NumTasks += NaniteBuildContext.bHiResSourceModel ? 1 : 0;
|
|
NumTasks += RayTracingFallbackBuildContext.NumFallbackLODs();
|
|
FScopedSlowTask SlowTask(NumTasks, NSLOCTEXT("StaticMeshEditor", "StaticMeshBuilderBuild", "Building static mesh render data."));
|
|
SlowTask.MakeDialog();
|
|
|
|
FBoxSphereBounds::Builder MeshBoundsBuilder;
|
|
|
|
const FMeshSectionInfoMap BeforeBuildSectionInfoMap = StaticMesh->GetSectionInfoMap();
|
|
const FMeshSectionInfoMap BeforeBuildOriginalSectionInfoMap = StaticMesh->GetOriginalSectionInfoMap();
|
|
|
|
TArray<FMeshDescription> MeshDescriptions;
|
|
MeshDescriptions.SetNum(NumSourceModels);
|
|
|
|
int32 NaniteBuiltLevels = 0;
|
|
|
|
if (bBuildNanite)
|
|
{
|
|
SlowTask.EnterProgressFrame( 1 );
|
|
|
|
Nanite::FResources& NaniteResources = *StaticMeshRenderData.NaniteResourcesPtr.Get();
|
|
bool bBuildSuccess = BuildNanite(
|
|
NaniteBuildContext,
|
|
StaticMeshRenderData.LODResources[0],
|
|
MeshDescriptions[0],
|
|
NaniteResources,
|
|
RayTracingFallbackBuildContext);
|
|
|
|
if (bBuildSuccess)
|
|
{
|
|
FBoxSphereBounds NaniteBounds;
|
|
NaniteBounds.Origin = FVector(NaniteResources.MeshBounds.Origin);
|
|
NaniteBounds.BoxExtent = FVector(NaniteResources.MeshBounds.BoxExtent);
|
|
NaniteBounds.SphereRadius = NaniteResources.MeshBounds.SphereRadius;
|
|
MeshBoundsBuilder += NaniteBounds;
|
|
|
|
if (!NaniteBuildContext.bHiResSourceModel)
|
|
{
|
|
// We don't need to build LOD 0 below if the Nanite build generated it
|
|
++NaniteBuiltLevels;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TFunction<void(const FMeshDescription&, const FMeshDescription&)> CheckReduction = [&](const FMeshDescription& InitMesh, const FMeshDescription& ReducedMesh)
|
|
{
|
|
FBox BBoxInitMesh = InitMesh.ComputeBoundingBox();
|
|
double BBoxInitMeshSize = (BBoxInitMesh.Max - BBoxInitMesh.Min).Length();
|
|
|
|
FBox BBoxReducedMesh = ReducedMesh.ComputeBoundingBox();
|
|
double BBoxReducedMeshSize = (BBoxReducedMesh.Max - BBoxReducedMesh.Min).Length();
|
|
|
|
constexpr double ThresholdForAbnormalGrowthOfBBox = UE_DOUBLE_SQRT_3; // the reduced mesh must stay in the bounding sphere
|
|
if (BBoxReducedMeshSize > BBoxInitMeshSize * ThresholdForAbnormalGrowthOfBBox)
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Warning, TEXT("The generation of LOD could have generated spikes on the mesh for %s"), *StaticMesh->GetName());
|
|
}
|
|
};
|
|
|
|
// Build non-Nanite render data for each LOD
|
|
for (int32 LodIndex = NaniteBuiltLevels; LodIndex < NumSourceModels; ++LodIndex)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build LOD");
|
|
SlowTask.EnterProgressFrame((LodIndex > 0 || !bBuildNanite) ? 1.0f : 0.0f);
|
|
FScopedSlowTask BuildLODSlowTask(3);
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
|
|
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LodIndex);
|
|
|
|
// NOTE: Make a local copy on the stack, as build settings are used to generate the DDC key for static mesh, and
|
|
// the mesh description helper might make changes to validate some settings
|
|
FMeshBuildSettings LODBuildSettings = SrcModel.BuildSettings;
|
|
|
|
float MaxDeviation = 0.0f;
|
|
bool bIsMeshDescriptionValid = StaticMesh->CloneMeshDescription(LodIndex, MeshDescriptions[LodIndex]);
|
|
bIsMeshDescriptionValid &= !MeshDescriptions[LodIndex].IsEmpty();
|
|
FMeshDescriptionHelper MeshDescriptionHelper(&LODBuildSettings);
|
|
|
|
FMeshReductionSettings ReductionSettings = LODGroup.GetSettings(SrcModel.ReductionSettings, LodIndex);
|
|
|
|
// Make sure we do not reduce a non custom LOD by itself
|
|
const int32 BaseReduceLodIndex = FMath::Clamp<int32>(ReductionSettings.BaseLODModel, 0, bIsMeshDescriptionValid ? LodIndex : LodIndex - 1);
|
|
// Use simplifier if a reduction in triangles or verts has been requested.
|
|
bool bUseReduction = StaticMesh->IsReductionActive(LodIndex);
|
|
|
|
if (bIsMeshDescriptionValid)
|
|
{
|
|
MeshDescriptionHelper.SetupRenderMeshDescription(StaticMesh, MeshDescriptions[LodIndex], false, true);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Make sure the cache is good before looking for the active reduction
|
|
if (SrcModel.CacheMeshDescriptionTrianglesCount == MAX_uint32)
|
|
{
|
|
SrcModel.CacheMeshDescriptionTrianglesCount = static_cast<uint32>(MeshDescriptions[LodIndex].Triangles().Num());
|
|
}
|
|
if (SrcModel.CacheMeshDescriptionVerticesCount == MAX_uint32)
|
|
{
|
|
SrcModel.CacheMeshDescriptionVerticesCount = static_cast<uint32>(FStaticMeshOperations::GetUniqueVertexCount(MeshDescriptions[LodIndex], MeshDescriptionHelper.GetOverlappingCorners()));
|
|
}
|
|
//Get back the reduction status once we apply all build settings, vertex count can change depending on the build settings
|
|
bUseReduction = StaticMesh->IsReductionActive(LodIndex);
|
|
}
|
|
else
|
|
{
|
|
if (bUseReduction)
|
|
{
|
|
// Initialize an empty mesh description that the reduce will fill
|
|
FStaticMeshAttributes(MeshDescriptions[LodIndex]).Register();
|
|
}
|
|
else
|
|
{
|
|
//Duplicate the lodindex 0 we have a 100% reduction which is like a duplicate
|
|
MeshDescriptions[LodIndex] = MeshDescriptions[BaseReduceLodIndex];
|
|
//Set the overlapping threshold
|
|
float ComparisonThreshold = StaticMesh->GetSourceModel(BaseReduceLodIndex).BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
|
|
MeshDescriptionHelper.FindOverlappingCorners(MeshDescriptions[LodIndex], ComparisonThreshold);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LodIndex > 0)
|
|
{
|
|
|
|
//Make sure the SectionInfoMap is taken from the Base RawMesh
|
|
int32 SectionNumber = StaticMesh->GetOriginalSectionInfoMap().GetSectionNumber(BaseReduceLodIndex);
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionNumber; ++SectionIndex)
|
|
{
|
|
//Keep the old data if its valid
|
|
bool bHasValidLODInfoMap = StaticMesh->GetSectionInfoMap().IsValidSection(LodIndex, SectionIndex);
|
|
//Section material index have to be remap with the ReductionSettings.BaseLODModel SectionInfoMap to create
|
|
//a valid new section info map for the reduced LOD.
|
|
if (!bHasValidLODInfoMap && StaticMesh->GetSectionInfoMap().IsValidSection(BaseReduceLodIndex, SectionIndex))
|
|
{
|
|
//Copy the BaseLODModel section info to the reduce LODIndex.
|
|
FMeshSectionInfo SectionInfo = StaticMesh->GetSectionInfoMap().Get(BaseReduceLodIndex, SectionIndex);
|
|
FMeshSectionInfo OriginalSectionInfo = StaticMesh->GetOriginalSectionInfoMap().Get(BaseReduceLodIndex, SectionIndex);
|
|
StaticMesh->GetSectionInfoMap().Set(LodIndex, SectionIndex, SectionInfo);
|
|
StaticMesh->GetOriginalSectionInfoMap().Set(LodIndex, SectionIndex, OriginalSectionInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LodIndex > 0)
|
|
{
|
|
LODBuildSettings = StaticMesh->GetSourceModel(BaseReduceLodIndex).BuildSettings;
|
|
}
|
|
}
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Reduce LODs
|
|
if (bUseReduction)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build - Reduce LOD");
|
|
|
|
float OverlappingThreshold = LODBuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
|
|
FOverlappingCorners OverlappingCorners;
|
|
FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, MeshDescriptions[BaseReduceLodIndex], OverlappingThreshold);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32 OldSectionInfoMapCount = StaticMesh->GetSectionInfoMap().GetSectionNumber(LodIndex);
|
|
|
|
if (LodIndex == BaseReduceLodIndex)
|
|
{
|
|
//When using LOD 0, we use a copy of the mesh description since reduce do not support inline reducing
|
|
FMeshDescription BaseMeshDescription = MeshDescriptions[BaseReduceLodIndex];
|
|
MeshDescriptionHelper.ReduceLOD(BaseMeshDescription, MeshDescriptions[LodIndex], ReductionSettings, OverlappingCorners, MaxDeviation);
|
|
CheckReduction(BaseMeshDescription, MeshDescriptions[LodIndex]);
|
|
}
|
|
else
|
|
{
|
|
MeshDescriptionHelper.ReduceLOD(MeshDescriptions[BaseReduceLodIndex], MeshDescriptions[LodIndex], ReductionSettings, OverlappingCorners, MaxDeviation);
|
|
CheckReduction(MeshDescriptions[BaseReduceLodIndex], MeshDescriptions[LodIndex]);
|
|
}
|
|
|
|
const TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = MeshDescriptions[LodIndex].PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
const TPolygonGroupAttributesRef<FName> BasePolygonGroupImportedMaterialSlotNames = MeshDescriptions[BaseReduceLodIndex].PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
|
|
// Recompute adjacency information. Since we change the vertices when we reduce
|
|
MeshDescriptionHelper.FindOverlappingCorners(MeshDescriptions[LodIndex], OverlappingThreshold);
|
|
|
|
//Make sure the static mesh SectionInfoMap is up to date with the new reduce LOD
|
|
//We have to remap the material index with the ReductionSettings.BaseLODModel sectionInfoMap
|
|
//Set the new SectionInfoMap for this reduced LOD base on the ReductionSettings.BaseLODModel SectionInfoMap
|
|
TArray<int32> BaseUniqueMaterialIndexes;
|
|
//Find all unique Material in used order
|
|
for (const FPolygonGroupID PolygonGroupID : MeshDescriptions[BaseReduceLodIndex].PolygonGroups().GetElementIDs())
|
|
{
|
|
int32 MaterialIndex = StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(BasePolygonGroupImportedMaterialSlotNames[PolygonGroupID]);
|
|
if (MaterialIndex == INDEX_NONE)
|
|
{
|
|
MaterialIndex = PolygonGroupID.GetValue();
|
|
}
|
|
BaseUniqueMaterialIndexes.AddUnique(MaterialIndex);
|
|
}
|
|
TArray<int32> UniqueMaterialIndex;
|
|
//Find all unique Material in used order
|
|
for (const FPolygonGroupID PolygonGroupID : MeshDescriptions[LodIndex].PolygonGroups().GetElementIDs())
|
|
{
|
|
int32 MaterialIndex = StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(PolygonGroupImportedMaterialSlotNames[PolygonGroupID]);
|
|
if (MaterialIndex == INDEX_NONE)
|
|
{
|
|
MaterialIndex = PolygonGroupID.GetValue();
|
|
}
|
|
UniqueMaterialIndex.AddUnique(MaterialIndex);
|
|
}
|
|
|
|
//If the reduce did not output the same number of section use the base LOD sectionInfoMap
|
|
bool bIsOldMappingInvalid = OldSectionInfoMapCount != MeshDescriptions[LodIndex].PolygonGroups().Num();
|
|
|
|
bool bValidBaseSectionInfoMap = BeforeBuildSectionInfoMap.GetSectionNumber(BaseReduceLodIndex) > 0;
|
|
//All used material represent a different section
|
|
for (int32 SectionIndex = 0; SectionIndex < UniqueMaterialIndex.Num(); ++SectionIndex)
|
|
{
|
|
//Keep the old data
|
|
bool bHasValidLODInfoMap = !bIsOldMappingInvalid && BeforeBuildSectionInfoMap.IsValidSection(LodIndex, SectionIndex);
|
|
//Section material index have to be remap with the ReductionSettings.BaseLODModel SectionInfoMap to create
|
|
//a valid new section info map for the reduced LOD.
|
|
|
|
//Find the base LOD section using this material
|
|
if (!bHasValidLODInfoMap)
|
|
{
|
|
bool bSectionInfoSet = false;
|
|
if (bValidBaseSectionInfoMap)
|
|
{
|
|
for (int32 BaseSectionIndex = 0; BaseSectionIndex < BaseUniqueMaterialIndexes.Num(); ++BaseSectionIndex)
|
|
{
|
|
if (UniqueMaterialIndex[SectionIndex] == BaseUniqueMaterialIndexes[BaseSectionIndex])
|
|
{
|
|
//Copy the base sectionInfoMap
|
|
FMeshSectionInfo SectionInfo = BeforeBuildSectionInfoMap.Get(BaseReduceLodIndex, BaseSectionIndex);
|
|
FMeshSectionInfo OriginalSectionInfo = BeforeBuildOriginalSectionInfoMap.Get(BaseReduceLodIndex, BaseSectionIndex);
|
|
StaticMesh->GetSectionInfoMap().Set(LodIndex, SectionIndex, SectionInfo);
|
|
StaticMesh->GetOriginalSectionInfoMap().Set(LodIndex, BaseSectionIndex, OriginalSectionInfo);
|
|
bSectionInfoSet = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bSectionInfoSet)
|
|
{
|
|
//Just set the default section info in case we did not found any match with the Base Lod
|
|
FMeshSectionInfo SectionInfo;
|
|
SectionInfo.MaterialIndex = SectionIndex;
|
|
StaticMesh->GetSectionInfoMap().Set(LodIndex, SectionIndex, SectionInfo);
|
|
StaticMesh->GetOriginalSectionInfoMap().Set(LodIndex, SectionIndex, SectionInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
const FPolygonGroupArray& PolygonGroups = MeshDescriptions[LodIndex].PolygonGroups();
|
|
|
|
FStaticMeshLODResources& StaticMeshLOD = StaticMeshRenderData.LODResources[LodIndex];
|
|
StaticMeshLOD.MaxDeviation = MaxDeviation;
|
|
|
|
// Build new vertex buffers
|
|
FMeshBuildVertexData BuildVertexData;
|
|
|
|
StaticMeshLOD.Sections.Empty(PolygonGroups.Num());
|
|
TArray<int32> RemapVerts; //Because we will remove MeshVertex that are redundant, we need a remap
|
|
//Render data Wedge map is only set for LOD 0???
|
|
|
|
TArray<int32>& WedgeMap = StaticMeshLOD.WedgeMap;
|
|
WedgeMap.Reset();
|
|
|
|
// Prepare the PerSectionIndices array so we can optimize the index buffer for the GPU
|
|
TArray<TArray<uint32> > PerSectionIndices;
|
|
PerSectionIndices.AddDefaulted(MeshDescriptions[LodIndex].PolygonGroups().Num());
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Build the vertex and index buffer
|
|
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
|
|
StaticMesh,
|
|
MeshDescriptions[LodIndex],
|
|
LODBuildSettings,
|
|
WedgeMap,
|
|
StaticMeshLOD.Sections,
|
|
PerSectionIndices,
|
|
BuildVertexData,
|
|
MeshDescriptionHelper.GetOverlappingCorners(),
|
|
RemapVerts,
|
|
StaticMeshLOD.SourceMeshBounds,
|
|
true /* bNeedTangents */,
|
|
true /* bNeedWedgeMap */
|
|
);
|
|
|
|
MeshBoundsBuilder += StaticMeshLOD.SourceMeshBounds;
|
|
|
|
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = MeshDescriptions[LodIndex].VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);
|
|
const uint32 NumTextureCoord = VertexInstanceUVs.IsValid() ? VertexInstanceUVs.GetNumChannels() : 0;
|
|
|
|
// Only the render data and vertex buffers will be used from now on unless we have more than one source models
|
|
// This will help with memory usage for Nanite Mesh by releasing memory before doing the build
|
|
if (NumSourceModels == 1)
|
|
{
|
|
MeshDescriptions.Empty();
|
|
}
|
|
|
|
// Concatenate the per-section index buffers.
|
|
TArray<uint32> CombinedIndices;
|
|
bool bNeeds32BitIndices = false;
|
|
UE::Private::StaticMeshBuilder::BuildCombinedSectionIndices(PerSectionIndices, StaticMeshLOD.Sections, CombinedIndices, bNeeds32BitIndices);
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::BufferInit);
|
|
|
|
FConstMeshBuildVertexView ConstVertexView = MakeConstMeshBuildVertexView(BuildVertexData);
|
|
|
|
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(LODBuildSettings.bUseHighPrecisionTangentBasis);
|
|
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(LODBuildSettings.bUseFullPrecisionUVs);
|
|
FStaticMeshVertexBufferFlags StaticMeshVertexBufferFlags;
|
|
StaticMeshVertexBufferFlags.bNeedsCPUAccess = true;
|
|
StaticMeshVertexBufferFlags.bUseBackwardsCompatibleF16TruncUVs = LODBuildSettings.bUseBackwardsCompatibleF16TruncUVs;
|
|
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.Init(ConstVertexView, StaticMeshVertexBufferFlags);
|
|
StaticMeshLOD.VertexBuffers.PositionVertexBuffer.Init(ConstVertexView);
|
|
StaticMeshLOD.VertexBuffers.ColorVertexBuffer.Init(ConstVertexView);
|
|
|
|
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
|
|
StaticMeshLOD.IndexBuffer.SetIndices(CombinedIndices, IndexBufferStride);
|
|
|
|
// post-process the index buffer
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
BuildAllBufferOptimizations(StaticMeshLOD, LODBuildSettings, CombinedIndices, bNeeds32BitIndices, ConstVertexView);
|
|
}
|
|
|
|
} //End of LOD for loop
|
|
|
|
// Update the render data bounds
|
|
StaticMeshRenderData.Bounds = MeshBoundsBuilder;
|
|
|
|
if (bBuildRayTracingFallback)
|
|
{
|
|
const int32 NumRayTracingLODs = RayTracingFallbackBuildContext.NumFallbackLODs();
|
|
|
|
checkf(!StaticMeshRenderData.RayTracingProxy, TEXT("RayTracingProxy expected to be null. Was the static mesh ray tracing representation already initialized?"));
|
|
StaticMeshRenderData.RayTracingProxy = new FStaticMeshRayTracingProxy();
|
|
StaticMeshRenderData.RayTracingProxy->bUsingRenderingLODs = false;
|
|
|
|
FStaticMeshRayTracingProxyLODArray& RayTracingLODs = StaticMeshRenderData.RayTracingProxy->LODs;
|
|
RayTracingLODs.Reserve(NumRayTracingLODs);
|
|
|
|
StaticMeshRenderData.RayTracingProxy->LODVertexFactories = new FStaticMeshVertexFactoriesArray;
|
|
FStaticMeshVertexFactoriesArray& RayTracingLODVertexFactories = *StaticMeshRenderData.RayTracingProxy->LODVertexFactories;
|
|
RayTracingLODVertexFactories.Reserve(NumRayTracingLODs);
|
|
|
|
for (int32 LodIndex = 0; LodIndex < NumRayTracingLODs; ++LodIndex)
|
|
{
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build Ray Tracing Proxy");
|
|
SlowTask.EnterProgressFrame(1);
|
|
FScopedSlowTask BuildLODSlowTask(3);
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
|
|
const int32 BaseReduceLodIndex = 0;
|
|
// NOTE: Make a local copy on the stack, as build settings are used to generate the DDC key for static mesh, and
|
|
// the mesh description helper might make changes to validate some settings
|
|
FMeshBuildSettings LODBuildSettings = StaticMesh->GetSourceModel(BaseReduceLodIndex).BuildSettings;
|
|
|
|
FMeshDescriptionHelper MeshDescriptionHelper(&LODBuildSettings);
|
|
|
|
FMeshReductionSettings ReductionSettings;
|
|
ReductionSettings.PercentTriangles = FMath::Max(RayTracingFallbackBuildContext.PercentTriangles[LodIndex], .001);
|
|
ReductionSettings.TerminationCriterion = EStaticMeshReductionTerimationCriterion::Triangles;
|
|
|
|
const bool bUseReduction = ReductionSettings.PercentTriangles < 1.f;
|
|
TArray<FMeshDescription>& RayTracingMeshDescriptions = RayTracingFallbackBuildContext.MeshDescriptions;
|
|
|
|
// Reduce LODs
|
|
if (bUseReduction)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build - Reduce Ray Tracing LOD");
|
|
|
|
FStaticMeshAttributes(RayTracingMeshDescriptions[LodIndex]).Register();
|
|
|
|
const float OverlappingThreshold = LODBuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
|
|
FOverlappingCorners OverlappingCorners;
|
|
FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, RayTracingMeshDescriptions[BaseReduceLodIndex], OverlappingThreshold);
|
|
|
|
float MaxDeviation = 0.f;
|
|
if (LodIndex == BaseReduceLodIndex)
|
|
{
|
|
//When using LOD 0, we use a copy of the mesh description since reduce do not support inline reducing
|
|
FMeshDescription BaseMeshDescription = RayTracingMeshDescriptions[BaseReduceLodIndex];
|
|
MeshDescriptionHelper.ReduceLOD(BaseMeshDescription, RayTracingMeshDescriptions[LodIndex], ReductionSettings, OverlappingCorners, MaxDeviation);
|
|
CheckReduction(BaseMeshDescription, RayTracingMeshDescriptions[LodIndex]);
|
|
}
|
|
else
|
|
{
|
|
MeshDescriptionHelper.ReduceLOD(RayTracingMeshDescriptions[BaseReduceLodIndex], RayTracingMeshDescriptions[LodIndex], ReductionSettings, OverlappingCorners, MaxDeviation);
|
|
CheckReduction(RayTracingMeshDescriptions[BaseReduceLodIndex], RayTracingMeshDescriptions[LodIndex]);
|
|
}
|
|
|
|
// Recompute adjacency information. Since we change the vertices when we reduce
|
|
MeshDescriptionHelper.FindOverlappingCorners(RayTracingMeshDescriptions[LodIndex], OverlappingThreshold);
|
|
}
|
|
else
|
|
{
|
|
// Nanite build has already generated LOD0 mesh description
|
|
if (LodIndex > BaseReduceLodIndex)
|
|
{
|
|
RayTracingMeshDescriptions[LodIndex] = RayTracingMeshDescriptions[BaseReduceLodIndex];
|
|
}
|
|
|
|
//Set the overlapping threshold
|
|
const float ComparisonThreshold = LODBuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
|
|
MeshDescriptionHelper.FindOverlappingCorners(RayTracingMeshDescriptions[LodIndex], ComparisonThreshold);
|
|
}
|
|
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
const FPolygonGroupArray& PolygonGroups = RayTracingMeshDescriptions[LodIndex].PolygonGroups();
|
|
|
|
// Build new vertex buffers
|
|
FMeshBuildVertexData BuildVertexData;
|
|
FStaticMeshSectionArray Sections;
|
|
TArray<int32> RemapVerts; //Because we will remove MeshVertex that are redundant, we need a remap
|
|
TArray<int32> WedgeMap;
|
|
TArray<TArray<uint32>> PerSectionIndices;
|
|
FBoxSphereBounds LODBounds;
|
|
|
|
Sections.Empty(PolygonGroups.Num());
|
|
PerSectionIndices.AddDefaulted(PolygonGroups.Num());
|
|
|
|
// Build the vertex and index buffer
|
|
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
|
|
StaticMesh,
|
|
RayTracingMeshDescriptions[LodIndex],
|
|
LODBuildSettings,
|
|
WedgeMap,
|
|
Sections,
|
|
PerSectionIndices,
|
|
BuildVertexData,
|
|
MeshDescriptionHelper.GetOverlappingCorners(),
|
|
RemapVerts,
|
|
LODBounds,
|
|
true /* bNeedTangents */,
|
|
true /* bNeedWedgeMap */
|
|
);
|
|
|
|
if (LodIndex == NumRayTracingLODs - 1)
|
|
{
|
|
RayTracingMeshDescriptions.Empty();
|
|
}
|
|
|
|
// Concatenate the per-section index buffers.
|
|
TArray<uint32> CombinedIndices;
|
|
bool bNeeds32BitIndices = false;
|
|
UE::Private::StaticMeshBuilder::BuildCombinedSectionIndices(PerSectionIndices, Sections, CombinedIndices, bNeeds32BitIndices);
|
|
|
|
bool bHasValidSections = false;
|
|
for (const FStaticMeshSection& Section : Sections)
|
|
{
|
|
if (Section.NumTriangles > 0)
|
|
{
|
|
bHasValidSections = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there are valid sections then copy over data to the RayTracingProxy
|
|
if (bHasValidSections)
|
|
{
|
|
RayTracingLODVertexFactories.Add(FStaticMeshVertexFactories(GMaxRHIFeatureLevel));
|
|
|
|
FStaticMeshRayTracingProxyLOD* RayTracingLOD = new FStaticMeshRayTracingProxyLOD;
|
|
RayTracingLODs.Add(RayTracingLOD);
|
|
RayTracingLOD->Sections = new FStaticMeshSectionArray;
|
|
RayTracingLOD->VertexBuffers = new FStaticMeshVertexBuffers;
|
|
RayTracingLOD->IndexBuffer = new FRawStaticIndexBuffer;
|
|
RayTracingLOD->bOwnsBuffers = true;
|
|
RayTracingLOD->bOwnsRayTracingGeometry = true;
|
|
RayTracingLOD->RayTracingGeometry = new FRayTracingGeometry;
|
|
|
|
RayTracingLOD->Sections->Empty(Sections.Num());
|
|
for (const FStaticMeshSection& Section : Sections)
|
|
{
|
|
RayTracingLOD->Sections->Add(Section);
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::BufferInit);
|
|
|
|
FStaticMeshVertexBufferFlags StaticMeshVertexBufferFlags;
|
|
StaticMeshVertexBufferFlags.bNeedsCPUAccess = true;
|
|
StaticMeshVertexBufferFlags.bUseBackwardsCompatibleF16TruncUVs = LODBuildSettings.bUseBackwardsCompatibleF16TruncUVs;
|
|
|
|
const FConstMeshBuildVertexView OutputMeshVertices = MakeConstMeshBuildVertexView(BuildVertexData);
|
|
RayTracingLOD->VertexBuffers->StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(LODBuildSettings.bUseHighPrecisionTangentBasis);
|
|
RayTracingLOD->VertexBuffers->StaticMeshVertexBuffer.SetUseFullPrecisionUVs(LODBuildSettings.bUseFullPrecisionUVs);
|
|
RayTracingLOD->VertexBuffers->StaticMeshVertexBuffer.Init(OutputMeshVertices, StaticMeshVertexBufferFlags);
|
|
RayTracingLOD->VertexBuffers->PositionVertexBuffer.Init(OutputMeshVertices);
|
|
RayTracingLOD->VertexBuffers->ColorVertexBuffer.Init(OutputMeshVertices);
|
|
|
|
// Why is the 'bNeeds32BitIndices' used from the original index buffer? Is that needed?
|
|
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
|
|
RayTracingLOD->IndexBuffer->SetIndices(CombinedIndices, IndexBufferStride);
|
|
|
|
BuildLODSlowTask.EnterProgressFrame(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (StaticMesh->bSupportRayTracing && BuildParameters.TargetPlatform->UsesRayTracing())
|
|
{
|
|
if (!StaticMeshRenderData.RayTracingProxy)
|
|
{
|
|
StaticMeshRenderData.InitializeRayTracingRepresentationFromRenderingLODs();
|
|
}
|
|
else
|
|
{
|
|
check(!StaticMeshRenderData.RayTracingProxy->bUsingRenderingLODs);
|
|
}
|
|
}
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FStaticMeshBuilder::BuildMeshVertexPositions(
|
|
UStaticMesh* StaticMesh,
|
|
TArray<uint32>& BuiltIndices,
|
|
TArray<FVector3f>& BuiltVertices,
|
|
FStaticMeshSectionArray& Sections)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::BuildMeshVertexPositions);
|
|
|
|
FStaticMeshSourceModel& SourceModel = StaticMesh->IsHiResMeshDescriptionValid() ? StaticMesh->GetHiResSourceModel() : StaticMesh->GetSourceModel(0);
|
|
if (!SourceModel.IsMeshDescriptionValid())
|
|
{
|
|
//Warn the user that there is no mesh description data
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Cannot find a valid mesh description to build the asset."));
|
|
return false;
|
|
}
|
|
|
|
FMeshDescription MeshDescription;
|
|
const bool bIsMeshDescriptionValid = SourceModel.CloneMeshDescription(MeshDescription);
|
|
check(bIsMeshDescriptionValid);
|
|
|
|
if (MeshDescription.IsEmpty())
|
|
{
|
|
UE_LOG(LogStaticMeshBuilder, Error, TEXT("Cannot build the asset from an empty mesh description."));
|
|
return false;
|
|
}
|
|
|
|
FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
|
|
|
|
FMeshDescriptionHelper MeshDescriptionHelper(&BuildSettings);
|
|
MeshDescriptionHelper.SetupRenderMeshDescription(StaticMesh, MeshDescription, false, false);
|
|
|
|
if (UE::Tasks::FCancellationTokenScope::IsCurrentWorkCanceled())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FPolygonGroupArray& PolygonGroups = MeshDescription.PolygonGroups();
|
|
|
|
// Build new vertex buffers
|
|
FMeshBuildVertexData BuildVertexData;
|
|
|
|
Sections.Empty(PolygonGroups.Num());
|
|
|
|
TArray<int32> RemapVerts; //Because we will remove MeshVertex that are redundant, we need a remap
|
|
//Render data Wedge map is only set for LOD 0???
|
|
|
|
TArray<int32> WedgeMap;
|
|
|
|
// Prepare the PerSectionIndices array so we can optimize the index buffer for the GPU
|
|
TArray<TArray<uint32>> PerSectionIndices;
|
|
PerSectionIndices.AddDefaulted(MeshDescription.PolygonGroups().Num());
|
|
|
|
FBoxSphereBounds LODBounds;
|
|
|
|
// Build the vertex and index buffer
|
|
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
|
|
StaticMesh,
|
|
MeshDescription,
|
|
BuildSettings,
|
|
WedgeMap,
|
|
Sections,
|
|
PerSectionIndices,
|
|
BuildVertexData,
|
|
MeshDescriptionHelper.GetOverlappingCorners(),
|
|
RemapVerts,
|
|
LODBounds,
|
|
false /* bNeedTangents */,
|
|
false /* bNeedWedgeMap */
|
|
);
|
|
|
|
BuiltVertices = BuildVertexData.Position;
|
|
|
|
// Release MeshDescription memory since we don't need it anymore
|
|
MeshDescription.Empty();
|
|
|
|
// Concatenate the per-section index buffers.
|
|
bool bNeeds32BitIndices = false;
|
|
UE::Private::StaticMeshBuilder::BuildCombinedSectionIndices(PerSectionIndices, Sections, BuiltIndices, bNeeds32BitIndices);
|
|
|
|
// Apply section remapping
|
|
for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); SectionIndex++)
|
|
{
|
|
Sections[SectionIndex].MaterialIndex = StaticMesh->GetSectionInfoMap().Get(0, SectionIndex).MaterialIndex;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace UE::Private::StaticMeshBuilder
|
|
{
|
|
|
|
struct FPendingVertex
|
|
{
|
|
FVector3f Position;
|
|
FVector3f TangentX;
|
|
FVector3f TangentY;
|
|
FVector3f TangentZ;
|
|
FColor Color;
|
|
TStaticArray<FVector2f, MAX_STATIC_TEXCOORDS> UVs;
|
|
};
|
|
|
|
bool AreVerticesEqual(const FPendingVertex& Vertex, const FMeshBuildVertexData& VertexData, int32 CompareVertex, float ComparisonThreshold)
|
|
{
|
|
if (!Vertex.Position.Equals(VertexData.Position[CompareVertex], ComparisonThreshold))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Test TangentZ first, often X and Y are zero
|
|
if (!NormalsEqual(Vertex.TangentZ, VertexData.TangentZ[CompareVertex]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!NormalsEqual(Vertex.TangentX, VertexData.TangentX[CompareVertex]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!NormalsEqual(Vertex.TangentY, VertexData.TangentY[CompareVertex]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (VertexData.Color.Num() > 0)
|
|
{
|
|
if (Vertex.Color != VertexData.Color[CompareVertex])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// UVs
|
|
for (int32 UVIndex = 0; UVIndex < VertexData.UVs.Num(); UVIndex++)
|
|
{
|
|
if (!UVsEqual(Vertex.UVs[UVIndex], VertexData.UVs[UVIndex][CompareVertex]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void BuildVertexBuffer(
|
|
UStaticMesh* StaticMesh,
|
|
const FMeshDescription& MeshDescription,
|
|
const FMeshBuildSettings& BuildSettings,
|
|
TArray<int32>& OutWedgeMap,
|
|
FStaticMeshSectionArray& OutSections,
|
|
TArray<TArray<uint32>>& OutPerSectionIndices,
|
|
FMeshBuildVertexData& BuildVertexData,
|
|
const FOverlappingCorners& OverlappingCorners,
|
|
TArray<int32>& RemapVerts,
|
|
FBoxSphereBounds& MeshBounds,
|
|
bool bNeedTangents,
|
|
bool bNeedWedgeMap
|
|
)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(BuildVertexBuffer);
|
|
|
|
const int32 NumVertexInstances = MeshDescription.VertexInstances().GetArraySize();
|
|
const bool bCacheOptimize = (NumVertexInstances < 100000 * 3);
|
|
|
|
FBounds3f Bounds;
|
|
bool bBoundsSet = false;
|
|
|
|
FStaticMeshConstAttributes Attributes(MeshDescription);
|
|
|
|
TPolygonGroupAttributesConstRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
|
|
TVertexAttributesConstRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
|
|
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
|
|
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
|
|
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
|
|
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
|
|
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
|
|
|
|
const bool bHasColors = VertexInstanceColors.IsValid();
|
|
bool bValidColors = false;
|
|
const int32 NumTextureCoord = VertexInstanceUVs.IsValid() ? VertexInstanceUVs.GetNumChannels() : 0;
|
|
const FVector3f BuildScale(BuildSettings.BuildScale3D);
|
|
|
|
// set up vertex buffer elements
|
|
BuildVertexData.Position.Reserve(NumVertexInstances);
|
|
BuildVertexData.TangentX.Reserve(NumVertexInstances);
|
|
BuildVertexData.TangentY.Reserve(NumVertexInstances);
|
|
BuildVertexData.TangentZ.Reserve(NumVertexInstances);
|
|
BuildVertexData.UVs.SetNum(NumTextureCoord);
|
|
for (int32 TexCoord = 0; TexCoord < NumTextureCoord; ++TexCoord)
|
|
{
|
|
BuildVertexData.UVs[TexCoord].Reserve(NumVertexInstances);
|
|
}
|
|
|
|
TMap<FPolygonGroupID, int32> PolygonGroupToSectionIndex;
|
|
|
|
for (const FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
|
|
{
|
|
int32& SectionIndex = PolygonGroupToSectionIndex.FindOrAdd(PolygonGroupID);
|
|
SectionIndex = OutSections.Add(FStaticMeshSection());
|
|
FStaticMeshSection& StaticMeshSection = OutSections[SectionIndex];
|
|
StaticMeshSection.MaterialIndex = StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(PolygonGroupImportedMaterialSlotNames[PolygonGroupID]);
|
|
if (StaticMeshSection.MaterialIndex == INDEX_NONE)
|
|
{
|
|
StaticMeshSection.MaterialIndex = PolygonGroupID.GetValue();
|
|
}
|
|
}
|
|
|
|
int32 ReserveIndicesCount = MeshDescription.Triangles().Num() * 3;
|
|
|
|
// Fill the remap array
|
|
{
|
|
RemapVerts.AddZeroed(ReserveIndicesCount);
|
|
for (int32& RemapIndex : RemapVerts)
|
|
{
|
|
RemapIndex = INDEX_NONE;
|
|
}
|
|
}
|
|
|
|
// Initialize the wedge map array tracking correspondence between wedge index and rendering vertex index
|
|
OutWedgeMap.Reset();
|
|
if (bNeedWedgeMap)
|
|
{
|
|
OutWedgeMap.AddZeroed(ReserveIndicesCount);
|
|
}
|
|
|
|
float VertexComparisonThreshold = BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
|
|
|
|
bool bUseLegacyTangentScaling = StaticMesh->GetLegacyTangentScaling();
|
|
|
|
int32 WedgeIndex = 0;
|
|
for (const FTriangleID TriangleID : MeshDescription.Triangles().GetElementIDs())
|
|
{
|
|
const FPolygonGroupID PolygonGroupID = MeshDescription.GetTrianglePolygonGroup(TriangleID);
|
|
const int32 SectionIndex = PolygonGroupToSectionIndex[PolygonGroupID];
|
|
TArray<uint32>& SectionIndices = OutPerSectionIndices[SectionIndex];
|
|
|
|
TArrayView<const FVertexID> VertexIDs = MeshDescription.GetTriangleVertices(TriangleID);
|
|
|
|
FVector3f CornerPositions[3];
|
|
for (int32 TriVert = 0; TriVert < 3; ++TriVert)
|
|
{
|
|
CornerPositions[TriVert] = VertexPositions[VertexIDs[TriVert]];
|
|
}
|
|
FOverlappingThresholds OverlappingThresholds;
|
|
OverlappingThresholds.ThresholdPosition = VertexComparisonThreshold;
|
|
// Don't process degenerate triangles.
|
|
if (PointsEqual(CornerPositions[0], CornerPositions[1], OverlappingThresholds)
|
|
|| PointsEqual(CornerPositions[0], CornerPositions[2], OverlappingThresholds)
|
|
|| PointsEqual(CornerPositions[1], CornerPositions[2], OverlappingThresholds))
|
|
{
|
|
WedgeIndex += 3;
|
|
continue;
|
|
}
|
|
|
|
TArrayView<const FVertexInstanceID> VertexInstanceIDs = MeshDescription.GetTriangleVertexInstances(TriangleID);
|
|
for (int32 TriVert = 0; TriVert < 3; ++TriVert, ++WedgeIndex)
|
|
{
|
|
const FVertexInstanceID VertexInstanceID = VertexInstanceIDs[TriVert];
|
|
const FVector3f& VertexPosition = CornerPositions[TriVert];
|
|
const FVector3f& VertexInstanceNormal = VertexInstanceNormals[VertexInstanceID];
|
|
const FVector3f& VertexInstanceTangent = VertexInstanceTangents[VertexInstanceID];
|
|
const float VertexInstanceBinormalSign = VertexInstanceBinormalSigns[VertexInstanceID];
|
|
|
|
FPendingVertex PendingVertex;
|
|
|
|
PendingVertex.Position = VertexPosition;
|
|
PendingVertex.TangentX = VertexInstanceTangent;
|
|
PendingVertex.TangentY = (VertexInstanceNormal ^ VertexInstanceTangent) * VertexInstanceBinormalSign;
|
|
PendingVertex.TangentZ = VertexInstanceNormal;
|
|
|
|
ScaleStaticMeshVertex(
|
|
PendingVertex.Position,
|
|
PendingVertex.TangentX,
|
|
PendingVertex.TangentY,
|
|
PendingVertex.TangentZ,
|
|
BuildScale,
|
|
bNeedTangents,
|
|
bUseLegacyTangentScaling
|
|
);
|
|
|
|
FColor VertexColor = FColor::White;
|
|
if (bHasColors)
|
|
{
|
|
const FVector4f& VertexInstanceColor = VertexInstanceColors[VertexInstanceID];
|
|
const FLinearColor LinearColor(VertexInstanceColor);
|
|
VertexColor = LinearColor.ToFColor(true);
|
|
}
|
|
|
|
PendingVertex.Color = VertexColor;
|
|
|
|
for (int32 UVIndex = 0; UVIndex < NumTextureCoord; ++UVIndex)
|
|
{
|
|
PendingVertex.UVs[UVIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
|
|
}
|
|
|
|
int32 Index = INDEX_NONE;
|
|
|
|
// Never add duplicated vertex instance
|
|
// Use WedgeIndex since OverlappingCorners has been built based on that
|
|
{
|
|
const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(WedgeIndex);
|
|
for (int32 k = 0; k < DupVerts.Num(); k++)
|
|
{
|
|
if (DupVerts[k] >= WedgeIndex)
|
|
{
|
|
break;
|
|
}
|
|
int32 Location = RemapVerts.IsValidIndex(DupVerts[k]) ? RemapVerts[DupVerts[k]] : INDEX_NONE;
|
|
if (Location != INDEX_NONE && AreVerticesEqual(PendingVertex, BuildVertexData, Location, VertexComparisonThreshold))
|
|
{
|
|
Index = Location;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
Index = BuildVertexData.Position.Emplace(PendingVertex.Position);
|
|
|
|
BuildVertexData.TangentX.Emplace(PendingVertex.TangentX);
|
|
BuildVertexData.TangentY.Emplace(PendingVertex.TangentY);
|
|
BuildVertexData.TangentZ.Emplace(PendingVertex.TangentZ);
|
|
|
|
if (bHasColors)
|
|
{
|
|
if (PendingVertex.Color != FColor::White)
|
|
{
|
|
bValidColors = true;
|
|
}
|
|
|
|
if (BuildVertexData.Color.Num() == 0 && bValidColors)
|
|
{
|
|
// First occurrence of a non fully opaque white color means we allocate output space,
|
|
// and then set all previously encountered vertex colors to be opaque white.
|
|
BuildVertexData.Color.Reserve(NumVertexInstances);
|
|
BuildVertexData.Color.SetNumUninitialized(BuildVertexData.Position.Num() - 1);
|
|
for (int32 ColorIndex = 0; ColorIndex < BuildVertexData.Color.Num(); ++ColorIndex)
|
|
{
|
|
BuildVertexData.Color[ColorIndex] = FColor::White;
|
|
}
|
|
}
|
|
|
|
if (bValidColors)
|
|
{
|
|
BuildVertexData.Color.Emplace(PendingVertex.Color);
|
|
}
|
|
}
|
|
|
|
for (int32 UVIndex = 0; UVIndex < NumTextureCoord; ++UVIndex)
|
|
{
|
|
BuildVertexData.UVs[UVIndex].Emplace(VertexInstanceUVs.Get(VertexInstanceID, UVIndex));
|
|
}
|
|
|
|
// We are already processing all vertices, so we may as well compute the bounding box here
|
|
// instead of yet another loop over the vertices at a later point.
|
|
Bounds += PendingVertex.Position;
|
|
bBoundsSet = true;
|
|
}
|
|
|
|
RemapVerts[WedgeIndex] = Index;
|
|
|
|
if (bNeedWedgeMap)
|
|
{
|
|
OutWedgeMap[WedgeIndex] = Index;
|
|
}
|
|
|
|
SectionIndices.Add(Index);
|
|
}
|
|
}
|
|
|
|
if (!bBoundsSet)
|
|
{
|
|
// There were no verts that contribute to bounds, so we'll just set a bounds of 0,0,0 to avoid calculating NaNs for Origin, BoxExtent, and SphereRadius below
|
|
Bounds = FVector3f(0.f, 0.f, 0.f);
|
|
}
|
|
|
|
// Calculate the bounding sphere, using the center of the bounding box as the origin.
|
|
FVector3f Center = Bounds.GetCenter();
|
|
float RadiusSqr = 0.0f;
|
|
for (int32 VertexIndex = 0; VertexIndex < BuildVertexData.Position.Num(); VertexIndex++)
|
|
{
|
|
RadiusSqr = FMath::Max(RadiusSqr, (BuildVertexData.Position[VertexIndex] - Center).SizeSquared());
|
|
}
|
|
|
|
MeshBounds.Origin = FVector(Center);
|
|
MeshBounds.BoxExtent = FVector(Bounds.GetExtent());
|
|
MeshBounds.SphereRadius = FMath::Sqrt(RadiusSqr);
|
|
|
|
// Optimize before setting the buffer
|
|
if (bCacheOptimize)
|
|
{
|
|
BuildOptimizationHelper::CacheOptimizeVertexAndIndexBuffer(BuildVertexData, OutPerSectionIndices, OutWedgeMap);
|
|
//check(OutWedgeMap.Num() == MeshDescription->VertexInstances().Num());
|
|
}
|
|
|
|
RemapVerts.Empty();
|
|
}
|
|
|
|
/**
|
|
* Utility function used inside FStaticMeshBuilder::Build() per-LOD loop to populate
|
|
* the Sections in a FStaticMeshLODResources from PerSectionIndices, as well as
|
|
* concatenate all section indices into CombinedIndicesOut.
|
|
* Returned bNeeds32BitIndicesOut indicates whether max vert index is larger than max int16
|
|
*/
|
|
void BuildCombinedSectionIndices(
|
|
const TArray<TArray<uint32>>& PerSectionIndices,
|
|
FStaticMeshSectionArray& SectionsOut,
|
|
TArray<uint32>& CombinedIndicesOut,
|
|
bool& bNeeds32BitIndicesOut)
|
|
{
|
|
bNeeds32BitIndicesOut = false;
|
|
for (int32 SectionIndex = 0; SectionIndex < SectionsOut.Num(); SectionIndex++)
|
|
{
|
|
FStaticMeshSection& Section = SectionsOut[SectionIndex];
|
|
const TArray<uint32>& SectionIndices = PerSectionIndices[SectionIndex];
|
|
Section.FirstIndex = 0;
|
|
Section.NumTriangles = 0;
|
|
Section.MinVertexIndex = 0;
|
|
Section.MaxVertexIndex = 0;
|
|
|
|
if (SectionIndices.Num())
|
|
{
|
|
Section.FirstIndex = CombinedIndicesOut.Num();
|
|
Section.NumTriangles = SectionIndices.Num() / 3;
|
|
|
|
CombinedIndicesOut.AddUninitialized(SectionIndices.Num());
|
|
uint32* DestPtr = &CombinedIndicesOut[Section.FirstIndex];
|
|
uint32 const* SrcPtr = SectionIndices.GetData();
|
|
|
|
Section.MinVertexIndex = *SrcPtr;
|
|
Section.MaxVertexIndex = *SrcPtr;
|
|
|
|
for (int32 Index = 0; Index < SectionIndices.Num(); Index++)
|
|
{
|
|
uint32 VertIndex = *SrcPtr++;
|
|
|
|
bNeeds32BitIndicesOut |= (VertIndex > MAX_uint16);
|
|
Section.MinVertexIndex = FMath::Min<uint32>(VertIndex, Section.MinVertexIndex);
|
|
Section.MaxVertexIndex = FMath::Max<uint32>(VertIndex, Section.MaxVertexIndex);
|
|
*DestPtr++ = VertIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace UE::Private::StaticMeshBuilder
|
|
|
|
void BuildAllBufferOptimizations(FStaticMeshLODResources& StaticMeshLOD, const FMeshBuildSettings& LODBuildSettings, TArray< uint32 >& IndexBuffer, bool bNeeds32BitIndices, const FConstMeshBuildVertexView& BuildVertices)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(BuildAllBufferOptimizations);
|
|
|
|
if (StaticMeshLOD.AdditionalIndexBuffers == nullptr)
|
|
{
|
|
StaticMeshLOD.AdditionalIndexBuffers = new FAdditionalStaticMeshIndexBuffers();
|
|
}
|
|
|
|
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
|
|
|
|
// Build the reversed index buffer.
|
|
if (LODBuildSettings.bBuildReversedIndexBuffer)
|
|
{
|
|
TArray<uint32> InversedIndices;
|
|
const int32 IndexCount = IndexBuffer.Num();
|
|
InversedIndices.AddUninitialized(IndexCount);
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < StaticMeshLOD.Sections.Num(); ++SectionIndex)
|
|
{
|
|
const FStaticMeshSection& SectionInfo = StaticMeshLOD.Sections[SectionIndex];
|
|
const int32 SectionIndexCount = SectionInfo.NumTriangles * 3;
|
|
|
|
for (int32 i = 0; i < SectionIndexCount; ++i)
|
|
{
|
|
InversedIndices[SectionInfo.FirstIndex + i] = IndexBuffer[SectionInfo.FirstIndex + SectionIndexCount - 1 - i];
|
|
}
|
|
}
|
|
StaticMeshLOD.AdditionalIndexBuffers->ReversedIndexBuffer.SetIndices(InversedIndices, IndexBufferStride);
|
|
}
|
|
|
|
// Build the depth-only index buffer.
|
|
TArray<uint32> DepthOnlyIndices;
|
|
{
|
|
BuildOptimizationHelper::BuildDepthOnlyIndexBuffer(
|
|
DepthOnlyIndices,
|
|
BuildVertices,
|
|
IndexBuffer,
|
|
StaticMeshLOD.Sections
|
|
);
|
|
|
|
if (DepthOnlyIndices.Num() < 50000 * 3)
|
|
{
|
|
BuildOptimizationThirdParty::CacheOptimizeIndexBuffer(DepthOnlyIndices);
|
|
}
|
|
|
|
StaticMeshLOD.DepthOnlyIndexBuffer.SetIndices(DepthOnlyIndices, IndexBufferStride);
|
|
}
|
|
|
|
// Build the inversed depth only index buffer.
|
|
if (LODBuildSettings.bBuildReversedIndexBuffer)
|
|
{
|
|
TArray<uint32> ReversedDepthOnlyIndices;
|
|
const int32 IndexCount = DepthOnlyIndices.Num();
|
|
ReversedDepthOnlyIndices.AddUninitialized(IndexCount);
|
|
for (int32 i = 0; i < IndexCount; ++i)
|
|
{
|
|
ReversedDepthOnlyIndices[i] = DepthOnlyIndices[IndexCount - 1 - i];
|
|
}
|
|
StaticMeshLOD.AdditionalIndexBuffers->ReversedDepthOnlyIndexBuffer.SetIndices(ReversedDepthOnlyIndices, IndexBufferStride);
|
|
}
|
|
|
|
// Build a list of wireframe edges in the static mesh.
|
|
{
|
|
TArray<BuildOptimizationHelper::FMeshEdge> Edges;
|
|
TArray<uint32> WireframeIndices;
|
|
|
|
BuildOptimizationHelper::FMeshEdgeBuilder(IndexBuffer, BuildVertices, Edges).FindEdges();
|
|
WireframeIndices.Empty(2 * Edges.Num());
|
|
for (int32 EdgeIndex = 0; EdgeIndex < Edges.Num(); EdgeIndex++)
|
|
{
|
|
BuildOptimizationHelper::FMeshEdge& Edge = Edges[EdgeIndex];
|
|
WireframeIndices.Add(Edge.Vertices[0]);
|
|
WireframeIndices.Add(Edge.Vertices[1]);
|
|
}
|
|
StaticMeshLOD.AdditionalIndexBuffers->WireframeIndexBuffer.SetIndices(WireframeIndices, IndexBufferStride);
|
|
}
|
|
}
|
|
|