Files
UnrealEngineUWP/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp
brian karis d1901ae146 Added Nanite static mesh setting: Fallback target.
This is an enum to say which target to reduce to for the fallback. It makes the default "Auto" which gives us more ability to change the default heuristic without needing to change uproperty defaults.

Along with this added r.Nanite.Builder.FallbackTriangleThreshold. Any mesh with source triangle count less than this threshold uses the source triangles directly for the fallback.

#rb graham.wihlidal
#lockdown marc.audy

#preflight 63f0041b1d71bcd26225228c

[CL 24330590 by brian karis in ue5-main branch]
2023-02-20 18:59:18 -05:00

1090 lines
43 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 "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 "Rendering/NaniteResources.h"
DEFINE_LOG_CATEGORY(LogStaticMeshBuilder);
void BuildAllBufferOptimizations(
struct FStaticMeshLODResources& StaticMeshLOD,
const struct FMeshBuildSettings& LODBuildSettings,
TArray< uint32 >& IndexBuffer,
bool bNeeds32BitIndices,
TArray< FStaticMeshBuildVertex >& StaticMeshBuildVertices
);
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 CorrectFallbackSettings( FMeshNaniteSettings& NaniteSettings, int32 NumTris )
{
static const auto CVarFallbackThreshold = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Nanite.Builder.FallbackTriangleThreshold"));
switch( NaniteSettings.FallbackTarget )
{
case ENaniteFallbackTarget::Auto:
NaniteSettings.FallbackPercentTriangles = 1.0f;
NaniteSettings.FallbackRelativeError = NumTris <= CVarFallbackThreshold->GetValueOnAnyThread() ? 0.0f : 1.0f;
break;
case ENaniteFallbackTarget::PercentTriangles:
NaniteSettings.FallbackRelativeError = 0.0f;
break;
case ENaniteFallbackTarget::RelativeError:
NaniteSettings.FallbackPercentTriangles = 1.0f;
break;
}
}
static bool BuildNanite(
UStaticMesh* StaticMesh,
FStaticMeshSourceModel& SourceModel,
FStaticMeshLODResourcesArray& LODResources,
FStaticMeshVertexFactoriesArray& LODVertexFactories,
Nanite::FResources& NaniteResources,
FMeshNaniteSettings& NaniteSettings,
TArrayView<float> PercentTriangles,
FBoxSphereBounds& BoundsOut
)
{
TRACE_CPUPROFILER_EVENT_SCOPE( FStaticMeshBuilder::BuildNanite );
if( !SourceModel.IsMeshDescriptionValid() )
{
return false;
}
FMeshDescription MeshDescription = *SourceModel.GetOrCacheMeshDescription();
FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
FStaticMeshLODResources& StaticMeshLOD = LODResources[0];
CorrectFallbackSettings( NaniteSettings, MeshDescription.Triangles().Num() );
// compute tangents, lightmap UVs, etc
// Until the simplifier supports tangents, only 100% fallback meshes will need them
const bool bNeedTangents = PercentTriangles.Num() > 0 && NaniteSettings.FallbackPercentTriangles == 1.0f && NaniteSettings.FallbackRelativeError == 0.0f;
FMeshDescriptionHelper MeshDescriptionHelper( &BuildSettings );
MeshDescriptionHelper.SetupRenderMeshDescription( StaticMesh, MeshDescription, true, bNeedTangents );
//Build new vertex buffers
TArray< FStaticMeshBuildVertex > StaticMeshBuildVertices;
//Because we will remove MeshVertex that are redundant, we need a remap
//Render data Wedge map is only set for LOD 0???
TArray<int32> RemapVerts;
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( MeshDescription.PolygonGroups().Num() );
StaticMeshLOD.Sections.Empty( MeshDescription.PolygonGroups().Num() );
//Build the vertex and index buffer
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
StaticMesh,
MeshDescription,
BuildSettings,
WedgeMap,
StaticMeshLOD.Sections,
PerSectionIndices,
StaticMeshBuildVertices,
MeshDescriptionHelper.GetOverlappingCorners(),
RemapVerts,
bNeedTangents
);
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = MeshDescription.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
MeshDescription.Empty();
// TODO get bounds from Nanite which computes them anyways!!!!
ComputeBoundsFromVertexList(StaticMeshBuildVertices, BoundsOut);
// Concatenate the per-section index buffers.
TArray<uint32> CombinedIndices;
bool bNeeds32BitIndices = false;
UE::Private::StaticMeshBuilder::BuildCombinedSectionIndices(PerSectionIndices, StaticMeshLOD.Sections, CombinedIndices, bNeeds32BitIndices);
// 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 < StaticMeshLOD.Sections.Num(); SectionIndex++)
{
StaticMeshLOD.Sections[SectionIndex].MaterialIndex = StaticMesh->GetSectionInfoMap().Get(0, SectionIndex).MaterialIndex;
}
// Make sure to not keep the large WedgeMap from the input mesh around.
// No need to calculate a new one for the coarse mesh, because Nanite meshes don't need it yet.
WedgeMap.Empty();
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
// Setup the input data
Nanite::IBuilderModule::FVertexMeshData InputMeshData;
Swap( InputMeshData.Vertices, StaticMeshBuildVertices );
Swap( InputMeshData.TriangleIndices, CombinedIndices );
InputMeshData.Sections = StaticMeshLOD.Sections;
// Request output LODs for each LOD resource
TArray< Nanite::IBuilderModule::FVertexMeshData, TInlineAllocator<4> > OutputLODMeshData;
OutputLODMeshData.SetNum( PercentTriangles.Num() );
for( int32 LodIndex = 0; LodIndex < OutputLODMeshData.Num(); LodIndex++ )
{
OutputLODMeshData[ LodIndex ].PercentTriangles = PercentTriangles[ LodIndex ];
}
if( !NaniteBuilderModule.Build( NaniteResources, InputMeshData, OutputLODMeshData, NumTextureCoord, NaniteSettings ) )
{
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for HiRes static mesh. See previous line(s) for details."));
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)
int ValidLODCount = 0;
for (int32 LodIndex = 0; LodIndex < OutputLODMeshData.Num(); ++LodIndex)
{
Nanite::IBuilderModule::FVertexMeshData& ProxyMeshData = OutputLODMeshData[LodIndex];
bool bHasValidSections = false;
for (FStaticMeshSection& Section : ProxyMeshData.Sections)
{
if (Section.NumTriangles > 0)
{
bHasValidSections = true;
break;
}
}
// Valid valid sections then copy over data to the LODResource
if (bHasValidSections)
{
// Add new LOD resource if not created yet
if (ValidLODCount >= LODResources.Num())
{
LODResources.Add(new FStaticMeshLODResources);
new (LODVertexFactories) FStaticMeshVertexFactories(GMaxRHIFeatureLevel);
}
FStaticMeshLODResources& ProxyLOD = LODResources[ValidLODCount];
ProxyLOD.Sections.Empty(ProxyMeshData.Sections.Num());
for (FStaticMeshSection& Section : ProxyMeshData.Sections)
{
ProxyLOD.Sections.Add(Section);
}
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::BufferInit);
ProxyLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(BuildSettings.bUseHighPrecisionTangentBasis);
ProxyLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(BuildSettings.bUseFullPrecisionUVs);
FStaticMeshVertexBufferFlags StaticMeshVertexBufferFlags;
StaticMeshVertexBufferFlags.bNeedsCPUAccess = true;
StaticMeshVertexBufferFlags.bUseBackwardsCompatibleF16TruncUVs = BuildSettings.bUseBackwardsCompatibleF16TruncUVs;
ProxyLOD.VertexBuffers.StaticMeshVertexBuffer.Init(ProxyMeshData.Vertices, NumTextureCoord, StaticMeshVertexBufferFlags);
ProxyLOD.VertexBuffers.PositionVertexBuffer.Init(ProxyMeshData.Vertices);
ProxyLOD.VertexBuffers.ColorVertexBuffer.Init(ProxyMeshData.Vertices);
// Why is the 'bNeeds32BitIndices' used from the original index buffer? Is that needed?
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
ProxyLOD.IndexBuffer.SetIndices(ProxyMeshData.TriangleIndices, IndexBufferStride);
BuildAllBufferOptimizations(ProxyLOD, BuildSettings, ProxyMeshData.TriangleIndices, bNeeds32BitIndices, ProxyMeshData.Vertices);
ProxyLOD.MaxDeviation = ProxyMeshData.MaxDeviation;
ValidLODCount++;
}
}
return true;
}
bool FStaticMeshBuilder::Build(FStaticMeshRenderData& StaticMeshRenderData, UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup, bool bGenerateCoarseMeshStreamingLODs, bool bTargetSupportsNanite)
{
const bool bNaniteBuildEnabled = StaticMesh->IsNaniteEnabled();
const bool bHaveHiResSourceModel = StaticMesh->IsHiResMeshDescriptionValid();
int32 NumTasks = (bNaniteBuildEnabled && bHaveHiResSourceModel) ? (StaticMesh->GetNumSourceModels() + 1) : (StaticMesh->GetNumSourceModels());
FScopedSlowTask SlowTask(NumTasks, NSLOCTEXT("StaticMeshEditor", "StaticMeshBuilderBuild", "Building static mesh render data."));
SlowTask.MakeDialog();
// The tool can only been switch by restarting the editor
static bool bIsThirdPartyReductiontool = !UseNativeQuadraticReduction();
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 (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);
TArray<FMeshDescription> MeshDescriptions;
MeshDescriptions.SetNum(NumSourceModels);
const FMeshSectionInfoMap BeforeBuildSectionInfoMap = StaticMesh->GetSectionInfoMap();
const FMeshSectionInfoMap BeforeBuildOriginalSectionInfoMap = StaticMesh->GetOriginalSectionInfoMap();
FMeshNaniteSettings NaniteSettings = StaticMesh->NaniteSettings;
bool bNaniteDataBuilt = false; // true once we have finished building Nanite, which can happen in multiple places
int32 NaniteBuiltLevels = 0;
// Bounds of the pre-Nanite mesh
FBoxSphereBounds HiResBounds;
bool bHaveHiResBounds = false;
// Do nanite build for HiRes SourceModel if we have one. In that case we skip the inline nanite build
// below that would happen with LOD0 build
if (bHaveHiResSourceModel && bNaniteBuildEnabled && bTargetSupportsNanite)
{
SlowTask.EnterProgressFrame(1);
bool bBuildSuccess = BuildNanite(
StaticMesh,
StaticMesh->GetHiResSourceModel(),
StaticMeshRenderData.LODResources,
StaticMeshRenderData.LODVertexFactories,
StaticMeshRenderData.NaniteResources,
NaniteSettings,
TArrayView< float >(),
HiResBounds );
if( bBuildSuccess )
{
bHaveHiResBounds = true;
bNaniteDataBuilt = true;
}
}
// If we want Nanite built, and have not already done it, do it based on LOD0 built render data.
// This will replace the output VertexBuffers/etc with the fractional Nanite cut to be stored as LOD0 RenderData.
// NOTE: We still want to do this for targets that do not support Nanite (if no hi-res source model was provided)
// so that it generates the fallback, in which case the Nanite bulk will be stripped
if (bNaniteBuildEnabled && ((bTargetSupportsNanite && !bNaniteDataBuilt) || (!bTargetSupportsNanite && !bHaveHiResSourceModel)))
{
TArray< float, TInlineAllocator<4> > PercentTriangles;
for (int32 LodIndex = 0; LodIndex < NumSourceModels; ++LodIndex)
{
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel( LodIndex );
// As soon as we hit an artist provided LOD stop
if( LodIndex > 0 && SrcModel.IsMeshDescriptionValid() )
break;
FMeshReductionSettings ReductionSettings = LODGroup.GetSettings( SrcModel.ReductionSettings, LodIndex );
PercentTriangles.Add( ReductionSettings.PercentTriangles );
}
SlowTask.EnterProgressFrame( PercentTriangles.Num() );
bool bBuildSuccess = BuildNanite(
StaticMesh,
StaticMesh->GetSourceModel(0),
StaticMeshRenderData.LODResources,
StaticMeshRenderData.LODVertexFactories,
StaticMeshRenderData.NaniteResources,
NaniteSettings,
PercentTriangles,
HiResBounds );
check( bBuildSuccess );
bHaveHiResBounds = true;
bNaniteDataBuilt = true;
NaniteBuiltLevels = PercentTriangles.Num();
if (!bTargetSupportsNanite)
{
// Strip the Nanite bulk for the target platform
StaticMeshRenderData.NaniteResources = Nanite::FResources();
}
}
// Build render data for each LOD, starting from where Nanite left off.
for (int32 LodIndex = NaniteBuiltLevels; LodIndex < NumSourceModels; ++LodIndex)
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build LOD");
SlowTask.EnterProgressFrame(1);
FScopedSlowTask BuildLODSlowTask(3);
BuildLODSlowTask.EnterProgressFrame(1);
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LodIndex);
float MaxDeviation = 0.0f;
FMeshBuildSettings& LODBuildSettings = SrcModel.BuildSettings;
bool bIsMeshDescriptionValid = StaticMesh->CloneMeshDescription(LodIndex, MeshDescriptions[LodIndex]);
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, NaniteBuiltLevels, 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);
//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 (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;
}
}
// 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);
int32 OldSectionInfoMapCount = StaticMesh->GetSectionInfoMap().GetSectionNumber(LodIndex);
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());
}
};
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
TArray< FStaticMeshBuildVertex > StaticMeshBuildVertices;
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());
//Build the vertex and index buffer
UE::Private::StaticMeshBuilder::BuildVertexBuffer(
StaticMesh,
MeshDescriptions[LodIndex],
LODBuildSettings,
WedgeMap,
StaticMeshLOD.Sections,
PerSectionIndices,
StaticMeshBuildVertices,
MeshDescriptionHelper.GetOverlappingCorners(),
RemapVerts,
true
);
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);
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(StaticMeshBuildVertices, NumTextureCoord, StaticMeshVertexBufferFlags);
StaticMeshLOD.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
StaticMeshLOD.VertexBuffers.ColorVertexBuffer.Init(StaticMeshBuildVertices);
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, StaticMeshBuildVertices);
}
} //End of LOD for loop
{
TRACE_CPUPROFILER_EVENT_SCOPE_STR("FStaticMeshBuilder::Build - Calculate Bounds");
// Calculate the bounding box of LOD0 buffer
FPositionVertexBuffer& BasePositionVertexBuffer = StaticMeshRenderData.LODResources[0].VertexBuffers.PositionVertexBuffer;
ComputeBoundsFromPositionBuffer(BasePositionVertexBuffer, StaticMeshRenderData.Bounds);
// combine with high-res bounds if it was computed
if (bHaveHiResBounds)
{
StaticMeshRenderData.Bounds = StaticMeshRenderData.Bounds + HiResBounds;
}
}
return true;
}
bool FStaticMeshBuilder::BuildMeshVertexPositions(
UStaticMesh* StaticMesh,
TArray<uint32>& BuiltIndices,
TArray<FVector3f>& BuiltVertices)
{
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;
}
const int32 NumSourceModels = StaticMesh->GetNumSourceModels();
if (NumSourceModels > 0)
{
FMeshDescription MeshDescription;
const bool bIsMeshDescriptionValid = SourceModel.CloneMeshDescription(MeshDescription);
if (bIsMeshDescriptionValid)
{
FElementIDRemappings Remappings;
MeshDescription.Compact(Remappings);
const FMeshBuildSettings& BuildSettings = SourceModel.BuildSettings;
const FStaticMeshConstAttributes Attributes(MeshDescription);
TArrayView<const FVector3f> VertexPositions = Attributes.GetVertexPositions().GetRawArray();
TArrayView<const FVertexID> VertexIndices = Attributes.GetTriangleVertexIndices().GetRawArray();
const FVector3f BuildScale3D = (FVector3f)BuildSettings.BuildScale3D;
BuiltVertices.Reserve(VertexPositions.Num());
for (int32 VertexIndex = 0; VertexIndex < VertexPositions.Num(); ++VertexIndex)
{
BuiltVertices.Add(VertexPositions[VertexIndex] * BuildScale3D);
}
BuiltIndices.Reserve(VertexIndices.Num());
for (int32 TriangleIndex = 0; TriangleIndex < VertexIndices.Num() / 3; ++TriangleIndex)
{
const uint32 I0 = VertexIndices[TriangleIndex * 3 + 0];
const uint32 I1 = VertexIndices[TriangleIndex * 3 + 1];
const uint32 I2 = VertexIndices[TriangleIndex * 3 + 2];
if (!ensureMsgf(I0 != INDEX_NONE && I1 != INDEX_NONE && I2 != INDEX_NONE,
TEXT("Mesh '%s' has triangles with uninitialized vertex indices"), *StaticMesh->GetName()))
{
continue;
}
const FVector3f V0 = BuiltVertices[I0];
const FVector3f V1 = BuiltVertices[I1];
const FVector3f V2 = BuiltVertices[I2];
const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
if (!bDegenerateTriangle)
{
BuiltIndices.Add(I0);
BuiltIndices.Add(I1);
BuiltIndices.Add(I2);
}
}
}
}
return true;
}
bool AreVerticesEqual(FStaticMeshBuildVertex const& A, FStaticMeshBuildVertex const& B, float ComparisonThreshold)
{
if ( !A.Position.Equals(B.Position, ComparisonThreshold)
|| !NormalsEqual(A.TangentX, B.TangentX)
|| !NormalsEqual(A.TangentY, B.TangentY)
|| !NormalsEqual(A.TangentZ, B.TangentZ)
|| A.Color != B.Color)
{
return false;
}
// UVs
for (int32 UVIndex = 0; UVIndex < MAX_STATIC_TEXCOORDS; UVIndex++)
{
if (!UVsEqual(A.UVs[UVIndex], B.UVs[UVIndex]))
{
return false;
}
}
return true;
}
namespace UE::Private::StaticMeshBuilder
{
void BuildVertexBuffer(
UStaticMesh* StaticMesh,
const FMeshDescription& MeshDescription,
const FMeshBuildSettings& BuildSettings,
TArray<int32>& OutWedgeMap,
FStaticMeshSectionArray& OutSections,
TArray<TArray<uint32>>& OutPerSectionIndices,
TArray< FStaticMeshBuildVertex>& StaticMeshBuildVertices,
const FOverlappingCorners& OverlappingCorners,
TArray<int32>& RemapVerts,
bool bNeedTangents
)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BuildVertexBuffer);
TArray<int32> RemapVertexInstanceID;
// set up vertex buffer elements
const int32 NumVertexInstances = MeshDescription.VertexInstances().GetArraySize();
StaticMeshBuildVertices.Reserve(NumVertexInstances);
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();
const uint32 NumTextureCoord = VertexInstanceUVs.IsValid() ? VertexInstanceUVs.GetNumChannels() : 0;
const FVector3f BuildScale(BuildSettings.BuildScale3D);
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();
OutWedgeMap.AddZeroed(ReserveIndicesCount);
float VertexComparisonThreshold = BuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
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];
FStaticMeshBuildVertex StaticMeshVertex;
StaticMeshVertex.Position = VertexPosition * BuildScale;
if (bNeedTangents)
{
StaticMeshVertex.TangentX = VertexInstanceTangent / BuildScale;
StaticMeshVertex.TangentY = ( (VertexInstanceNormal ^ VertexInstanceTangent) * VertexInstanceBinormalSign ) / BuildScale;
StaticMeshVertex.TangentX.Normalize();
StaticMeshVertex.TangentY.Normalize();
}
else
{
StaticMeshVertex.TangentX = FVector3f(1.0f, 0.0f, 0.0f);
StaticMeshVertex.TangentY = FVector3f(0.0f, 1.0f, 0.0f);
}
StaticMeshVertex.TangentZ = VertexInstanceNormal / BuildScale;
StaticMeshVertex.TangentZ.Normalize();
if (bHasColors)
{
const FVector4f& VertexInstanceColor = VertexInstanceColors[VertexInstanceID];
const FLinearColor LinearColor(VertexInstanceColor);
StaticMeshVertex.Color = LinearColor.ToFColor(true);
}
else
{
StaticMeshVertex.Color = FColor::White;
}
const uint32 MaxNumTexCoords = FMath::Min<int32>(MAX_MESH_TEXTURE_COORDS_MD, MAX_STATIC_TEXCOORDS);
for (uint32 UVIndex = 0; UVIndex < MaxNumTexCoords; ++UVIndex)
{
if (UVIndex < NumTextureCoord)
{
StaticMeshVertex.UVs[UVIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
}
else
{
StaticMeshVertex.UVs[UVIndex] = FVector2f(0.0f, 0.0f);
}
}
//Never add duplicated vertex instance
//Use WedgeIndex since OverlappingCorners has been built based on that
const TArray<int32>& DupVerts = OverlappingCorners.FindIfOverlapping(WedgeIndex);
int32 Index = INDEX_NONE;
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(StaticMeshVertex, StaticMeshBuildVertices[Location], VertexComparisonThreshold))
{
Index = Location;
break;
}
}
if (Index == INDEX_NONE)
{
Index = StaticMeshBuildVertices.Add(StaticMeshVertex);
}
RemapVerts[WedgeIndex] = Index;
OutWedgeMap[WedgeIndex] = Index;
SectionIndices.Add(Index);
}
}
//Optimize before setting the buffer
if (NumVertexInstances < 100000 * 3)
{
BuildOptimizationHelper::CacheOptimizeVertexAndIndexBuffer(StaticMeshBuildVertices, OutPerSectionIndices, OutWedgeMap);
//check(OutWedgeMap.Num() == MeshDescription->VertexInstances().Num());
}
}
/**
* 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, TArray< FStaticMeshBuildVertex >& StaticMeshBuildVertices)
{
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,
StaticMeshBuildVertices,
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::FStaticMeshEdgeBuilder(IndexBuffer, StaticMeshBuildVertices, 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);
}
}