Files
UnrealEngineUWP/Engine/Source/Developer/MeshBuilder/Private/StaticMeshBuilder.cpp
ionut matasaru a90718a7fb Fixed usage of TRACE_CPUPROFILER_EVENT_SCOPE macro(s) in various places.
Common mistakes:

a) TRACE_CPUPROFILER_EVENT_SCOPE("Foo") or TRACE_CPUPROFILER_EVENT_SCOPE("FClass::Foo")
    --> results in a timer named "Foo" or "FClass::Foo" (the quotes will be included in the name)

b) TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("Foo"))
    --> results in a timer named L"Foo" (L and quotes will be included in the name)

c) TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(TEXT("Other Foo")) or TRACE_CPUPROFILER_EVENT_SCOPE_TEXT("Foo")
    --> Slow! It will use dynamic string matching that adds an unnecessary overhead.

Correct usage:
    TRACE_CPUPROFILER_EVENT_SCOPE(Foo)
    TRACE_CPUPROFILER_EVENT_SCOPE(FClass::Foo)
    TRACE_CPUPROFILER_EVENT_SCOPE_STR("Other Foo") // when timer name has spaces
    TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*Foo.ToString()) // only if a dynamic name is really needed

#rb Catalin.Dragoiu
#fyi Marc.Audy, Krzysztof.Narkowicz, Rune.Stubbe, Michal.Valient

[CL 15134822 by ionut matasaru in ue5-main branch]
2021-01-19 06:29:15 -04:00

724 lines
30 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 "NaniteBuilder.h"
#include "Rendering/NaniteResources.h"
DEFINE_LOG_CATEGORY(LogStaticMeshBuilder);
//////////////////////////////////////////////////////////////////////////
//Local functions definition
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);
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;
}
bool FStaticMeshBuilder::Build(FStaticMeshRenderData& StaticMeshRenderData, UStaticMesh* StaticMesh, const FStaticMeshLODGroup& LODGroup)
{
FScopedSlowTask SlowTask(StaticMesh->GetNumSourceModels(), 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();
const FMeshNaniteSettings NaniteSettings = StaticMesh->NaniteSettings;
for (int32 LodIndex = 0; LodIndex < NumSourceModels; ++LodIndex)
{
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 himself
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]);
}
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 not building for Nanite).
// TODO: Skip all LOD reduction for Nanite?
if (bUseReduction && (!StaticMesh->NaniteSettings.bEnabled || LodIndex != 0))
{
float OverlappingThreshold = LODBuildSettings.bRemoveDegenerates ? THRESH_POINTS_ARE_SAME : 0.0f;
FOverlappingCorners OverlappingCorners;
FStaticMeshOperations::FindOverlappingCorners(OverlappingCorners, MeshDescriptions[BaseReduceLodIndex], OverlappingThreshold);
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);
}
else
{
MeshDescriptionHelper.ReduceLOD(MeshDescriptions[BaseReduceLodIndex], MeshDescriptions[LodIndex], ReductionSettings, OverlappingCorners, MaxDeviation);
}
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
BuildVertexBuffer(StaticMesh, MeshDescriptions[LodIndex], LODBuildSettings, WedgeMap, StaticMeshLOD.Sections, PerSectionIndices, StaticMeshBuildVertices, MeshDescriptionHelper.GetOverlappingCorners(), RemapVerts);
const uint32 NumTextureCoord = MeshDescriptions[LodIndex].VertexInstanceAttributes().GetAttributesRef<FVector2D>( MeshAttribute::VertexInstance::TextureCoordinate ).GetNumChannels();
// 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;
for (int32 SectionIndex = 0; SectionIndex < StaticMeshLOD.Sections.Num(); SectionIndex++)
{
FStaticMeshSection& Section = StaticMeshLOD.Sections[SectionIndex];
TArray<uint32> const& SectionIndices = PerSectionIndices[SectionIndex];
Section.FirstIndex = 0;
Section.NumTriangles = 0;
Section.MinVertexIndex = 0;
Section.MaxVertexIndex = 0;
if (SectionIndices.Num())
{
Section.FirstIndex = CombinedIndices.Num();
Section.NumTriangles = SectionIndices.Num() / 3;
CombinedIndices.AddUninitialized(SectionIndices.Num());
uint32* DestPtr = &CombinedIndices[Section.FirstIndex];
uint32 const* SrcPtr = SectionIndices.GetData();
Section.MinVertexIndex = *SrcPtr;
Section.MaxVertexIndex = *SrcPtr;
for (int32 Index = 0; Index < SectionIndices.Num(); Index++)
{
uint32 VertIndex = *SrcPtr++;
bNeeds32BitIndices |= (VertIndex > MAX_uint16);
Section.MinVertexIndex = FMath::Min<uint32>(VertIndex, Section.MinVertexIndex);
Section.MaxVertexIndex = FMath::Max<uint32>(VertIndex, Section.MaxVertexIndex);
*DestPtr++ = VertIndex;
}
}
}
if (StaticMesh->NaniteSettings.bEnabled && LodIndex == 0)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::Nanite);
WedgeMap.Empty(); // 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.
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
if( !NaniteBuilderModule.Build( StaticMeshRenderData.NaniteResources, StaticMeshBuildVertices, CombinedIndices, StaticMeshLOD.Sections, NumTextureCoord, NaniteSettings ) )
{
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for static mesh. See previous line(s) for details."));
}
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::Build::BufferInit);
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(LODBuildSettings.bUseHighPrecisionTangentBasis);
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(LODBuildSettings.bUseFullPrecisionUVs);
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer.Init(StaticMeshBuildVertices, NumTextureCoord);
StaticMeshLOD.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
StaticMeshLOD.VertexBuffers.ColorVertexBuffer.Init(StaticMeshBuildVertices);
const EIndexBufferStride::Type IndexBufferStride = bNeeds32BitIndices ? EIndexBufferStride::Force32Bit : EIndexBufferStride::Force16Bit;
StaticMeshLOD.IndexBuffer.SetIndices(CombinedIndices, IndexBufferStride);
}
BuildLODSlowTask.EnterProgressFrame(1);
BuildAllBufferOptimizations(StaticMeshLOD, LODBuildSettings, CombinedIndices, bNeeds32BitIndices, StaticMeshBuildVertices);
} //End of LOD for loop
// Calculate the bounding box.
FBox BoundingBox(ForceInit);
FPositionVertexBuffer& BasePositionVertexBuffer = StaticMeshRenderData.LODResources[0].VertexBuffers.PositionVertexBuffer;
for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
{
BoundingBox += BasePositionVertexBuffer.VertexPosition(VertexIndex);
}
BoundingBox.GetCenterAndExtents(StaticMeshRenderData.Bounds.Origin, StaticMeshRenderData.Bounds.BoxExtent);
// Calculate the bounding sphere, using the center of the bounding box as the origin.
StaticMeshRenderData.Bounds.SphereRadius = 0.0f;
for (uint32 VertexIndex = 0; VertexIndex < BasePositionVertexBuffer.GetNumVertices(); VertexIndex++)
{
StaticMeshRenderData.Bounds.SphereRadius = FMath::Max(
(BasePositionVertexBuffer.VertexPosition(VertexIndex) - StaticMeshRenderData.Bounds.Origin).Size(),
StaticMeshRenderData.Bounds.SphereRadius
);
}
return true;
}
bool FStaticMeshBuilder::BuildMeshVertexPositions(
UStaticMesh* StaticMesh,
TArray<uint32>& BuiltIndices,
TArray<FVector>& BuiltVertices)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FStaticMeshBuilder::BuildMeshVertexPositions);
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;
}
const int32 NumSourceModels = StaticMesh->GetNumSourceModels();
if (NumSourceModels > 0)
{
FMeshDescription MeshDescription;
const bool bIsMeshDescriptionValid = StaticMesh->CloneMeshDescription(/*LodIndex*/ 0, MeshDescription);
if (bIsMeshDescriptionValid)
{
const FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
const FStaticMeshConstAttributes Attributes(MeshDescription);
TArrayView<const FVector> VertexPositions = Attributes.GetVertexPositions().GetRawArray();
TArrayView<const FVertexID> VertexIndices = Attributes.GetTriangleVertexIndices().GetRawArray();
BuiltVertices.Reserve(VertexPositions.Num());
for (int32 VertexIndex = 0; VertexIndex < VertexPositions.Num(); ++VertexIndex)
{
BuiltVertices.Add(VertexPositions[VertexIndex] * BuildSettings.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];
const FVector V0 = BuiltVertices[I0];
const FVector V1 = BuiltVertices[I1];
const FVector V2 = BuiltVertices[I2];
const FVector 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;
}
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)
{
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<FVector> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
const bool bHasColors = VertexInstanceColors.IsValid();
const bool bIgnoreTangents = StaticMesh->NaniteSettings.bEnabled;
const uint32 NumTextureCoord = VertexInstanceUVs.GetNumChannels();
const FMatrix ScaleMatrix = FScaleMatrix(BuildSettings.BuildScale3D).Inverse().GetTransposed();
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);
FVector 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 FVector& VertexPosition = CornerPositions[TriVert];
const FVector& VertexInstanceNormal = VertexInstanceNormals[VertexInstanceID];
const FVector& VertexInstanceTangent = VertexInstanceTangents[VertexInstanceID];
const float VertexInstanceBinormalSign = VertexInstanceBinormalSigns[VertexInstanceID];
FStaticMeshBuildVertex StaticMeshVertex;
StaticMeshVertex.Position = VertexPosition * BuildSettings.BuildScale3D;
if( bIgnoreTangents )
{
StaticMeshVertex.TangentX = FVector( 1.0f, 0.0f, 0.0f );
StaticMeshVertex.TangentY = FVector( 0.0f, 1.0f, 0.0f );
}
else
{
StaticMeshVertex.TangentX = ScaleMatrix.TransformVector(VertexInstanceTangent).GetSafeNormal();
StaticMeshVertex.TangentY = ScaleMatrix.TransformVector(FVector::CrossProduct(VertexInstanceNormal, VertexInstanceTangent) * VertexInstanceBinormalSign).GetSafeNormal();
}
StaticMeshVertex.TangentZ = ScaleMatrix.TransformVector(VertexInstanceNormal).GetSafeNormal();
if (bHasColors)
{
const FVector4& 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] = FVector2D(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());
}
}
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);
}
// Build the adjacency index buffer used for tessellation.
if (LODBuildSettings.bBuildAdjacencyBuffer)
{
TArray<uint32> AdjacencyIndices;
BuildOptimizationThirdParty::NvTriStripHelper::BuildStaticAdjacencyIndexBuffer(
StaticMeshLOD.VertexBuffers.PositionVertexBuffer,
StaticMeshLOD.VertexBuffers.StaticMeshVertexBuffer,
IndexBuffer,
AdjacencyIndices
);
StaticMeshLOD.AdditionalIndexBuffers->AdjacencyIndexBuffer.SetIndices(AdjacencyIndices, IndexBufferStride);
}
}