Files
UnrealEngine/Engine/Source/Developer/NaniteBuilder/Private/NaniteDisplace.cpp
2025-05-18 13:04:45 +08:00

293 lines
8.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Components.h"
#include "DisplacementMap.h"
#include "Affine.h"
#include "LerpVert.h"
#include "AdaptiveTessellator.h"
#include "Math/Bounds.h"
#include "Engine/EngineTypes.h"
#include "Engine/Texture2D.h"
namespace Nanite
{
static FVector3f GetDisplacement(
const FVector3f& Barycentrics,
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
int32 MaterialIndex,
int32 UVIndex,
TArrayView< FDisplacementMap > DisplacementMaps )
{
FVector2f UV;
UV = Vert0.UVs[ UVIndex ] * Barycentrics.X;
UV += Vert1.UVs[ UVIndex ] * Barycentrics.Y;
UV += Vert2.UVs[ UVIndex ] * Barycentrics.Z;
FVector3f Normal;
Normal = Vert0.TangentZ * Barycentrics.X;
Normal += Vert1.TangentZ * Barycentrics.Y;
Normal += Vert2.TangentZ * Barycentrics.Z;
Normal.Normalize();
float Displacement = 0.0f;
if( DisplacementMaps.IsValidIndex( MaterialIndex ) )
Displacement = DisplacementMaps[ MaterialIndex ].Sample( UV );
return Normal * Displacement;
}
static FVector2f GetErrorBounds(
const FVector3f Barycentrics[3],
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
const FVector3f& Displacement0,
const FVector3f& Displacement1,
const FVector3f& Displacement2,
int32 MaterialIndex,
int32 UVIndex,
TArrayView< FDisplacementMap > DisplacementMaps )
{
float MinBarycentric0 = FMath::Min3( Barycentrics[0].X, Barycentrics[1].X, Barycentrics[2].X );
float MaxBarycentric0 = FMath::Max3( Barycentrics[0].X, Barycentrics[1].X, Barycentrics[2].X );
float MinBarycentric1 = FMath::Min3( Barycentrics[0].Y, Barycentrics[1].Y, Barycentrics[2].Y );
float MaxBarycentric1 = FMath::Max3( Barycentrics[0].Y, Barycentrics[1].Y, Barycentrics[2].Y );
TAffine< float, 2 > Barycentric0( MinBarycentric0, MaxBarycentric0, 0 );
TAffine< float, 2 > Barycentric1( MinBarycentric1, MaxBarycentric1, 1 );
TAffine< float, 2 > Barycentric2 = TAffine< float, 2 >( 1.0f ) - Barycentric0 - Barycentric1;
TAffine< FVector3f, 2 > LerpedDisplacement;
LerpedDisplacement = TAffine< FVector3f, 2 >( Displacement0 ) * Barycentric0;
LerpedDisplacement += TAffine< FVector3f, 2 >( Displacement1 ) * Barycentric1;
LerpedDisplacement += TAffine< FVector3f, 2 >( Displacement2 ) * Barycentric2;
TAffine< FVector3f, 2 > Normal;
Normal = TAffine< FVector3f, 2 >( Vert0.TangentZ ) * Barycentric0;
Normal += TAffine< FVector3f, 2 >( Vert1.TangentZ ) * Barycentric1;
Normal += TAffine< FVector3f, 2 >( Vert2.TangentZ ) * Barycentric2;
Normal = Normalize( Normal );
FVector2f MinUV = { MAX_flt, MAX_flt };
FVector2f MaxUV = { -MAX_flt, -MAX_flt };
for( int k = 0; k < 3; k++ )
{
FVector2f UV;
UV = Vert0.UVs[ UVIndex ] * Barycentrics[k].X;
UV += Vert1.UVs[ UVIndex ] * Barycentrics[k].Y;
UV += Vert2.UVs[ UVIndex ] * Barycentrics[k].Z;
MinUV = FVector2f::Min( MinUV, UV );
MaxUV = FVector2f::Max( MaxUV, UV );
}
FVector2f DisplacementBounds( 0.0f, 0.0f );
if( DisplacementMaps.IsValidIndex( MaterialIndex ) )
DisplacementBounds = DisplacementMaps[ MaterialIndex ].Sample( MinUV, MaxUV );
TAffine< float, 2 > Displacement( DisplacementBounds.X, DisplacementBounds.Y );
TAffine< float, 2 > Error = ( Normal * Displacement - LerpedDisplacement ).SizeSquared();
return FVector2f( Error.GetMin(), Error.GetMax() );
}
static int32 GetNumSamples(
const FVector3f Barycentrics[3],
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
int32 MaterialIndex,
int32 UVIndex,
TArrayView< FDisplacementMap > DisplacementMaps )
{
if( DisplacementMaps.IsValidIndex( MaterialIndex ) )
{
FVector2f UVs[3];
for( int k = 0; k < 3; k++ )
{
UVs[k] = Vert0.UVs[ UVIndex ] * Barycentrics[k].X;
UVs[k] += Vert1.UVs[ UVIndex ] * Barycentrics[k].Y;
UVs[k] += Vert2.UVs[ UVIndex ] * Barycentrics[k].Z;
UVs[k].X *= (float)DisplacementMaps[ MaterialIndex ].SizeX;
UVs[k].Y *= (float)DisplacementMaps[ MaterialIndex ].SizeY;
}
FVector2f Edge01 = UVs[1] - UVs[0];
FVector2f Edge12 = UVs[2] - UVs[1];
FVector2f Edge20 = UVs[0] - UVs[2];
float MaxEdgeLength = FMath::Sqrt( FMath::Max3(
Edge01.SizeSquared(),
Edge12.SizeSquared(),
Edge20.SizeSquared() ) );
float AreaInTexels = FMath::Abs( 0.5f * ( Edge01 ^ Edge12 ) );
return FMath::CeilToInt( FMath::Max( MaxEdgeLength, AreaInTexels ) );
}
return 1;
}
void TessellateAndDisplace(
FMeshBuildVertexData& Verts,
TArray< uint32 >& Indexes,
TArray< int32 >& MaterialIndexes,
const FBounds3f& MeshBounds,
const FMeshNaniteSettings& Settings )
{
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::TessellateAndDisplace);
float SurfaceArea = 0.0f;
for( int32 TriIndex = 0; TriIndex < MaterialIndexes.Num(); TriIndex++ )
{
auto& Vert0Position = Verts.Position[ Indexes[ TriIndex * 3 + 0 ] ];
auto& Vert1Position = Verts.Position[ Indexes[ TriIndex * 3 + 1 ] ];
auto& Vert2Position = Verts.Position[ Indexes[ TriIndex * 3 + 2 ] ];
FVector3f Edge01 = Vert1Position - Vert0Position;
FVector3f Edge12 = Vert2Position - Vert1Position;
FVector3f Edge20 = Vert0Position - Vert2Position;
SurfaceArea += 0.5f * ( Edge01 ^ Edge20 ).Size();
}
float TargetError = Settings.TrimRelativeError * 0.01f * FMath::Sqrt( FMath::Min( 2.0f * SurfaceArea, MeshBounds.GetSurfaceArea() ) );
// Overtessellate by 50% and simplify down
TargetError *= 1.5f;
TArray< FDisplacementMap > DisplacementMaps;
for( auto& DisplacementMap : Settings.DisplacementMaps )
{
if ( DisplacementMap.Texture )
{
if ( IsValid( DisplacementMap.Texture ) && DisplacementMap.Texture->Source.IsValid() )
{
FImage FirstMipImage;
if ( DisplacementMap.Texture->Source.GetMipImage( FirstMipImage, 0 ) )
{
DisplacementMaps.Add( Nanite::FDisplacementMap(
MoveTemp( FirstMipImage ),
DisplacementMap.Magnitude,
DisplacementMap.Center,
DisplacementMap.Texture->AddressX,
DisplacementMap.Texture->AddressY ) );
}
else
{
// Virtualization can fail to fetch the bulk data.
checkNoEntry();
}
}
else
{
// If the raw pointer is not null, but for some reason it is not valid better to crash then continuing with some false data
checkNoEntry();
}
}
else
{
DisplacementMaps.AddDefaulted();
}
}
TArray< FLerpVert > LerpVerts;
LerpVerts.AddUninitialized( Verts.Position.Num() );
for( int i = 0; i < Verts.Position.Num(); i++ )
LerpVerts[i] = MakeStaticMeshVertex(Verts, i);
FAdaptiveTessellator Tessellator( LerpVerts, Indexes, MaterialIndexes, TargetError, TargetError, true,
[&](const FVector3f& Barycentrics,
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
int32 MaterialIndex )
{
return GetDisplacement(
Barycentrics,
Vert0,
Vert1,
Vert2,
MaterialIndex,
Settings.DisplacementUVChannel,
DisplacementMaps );
},
[&](const FVector3f Barycentrics[3],
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
const FVector3f& Displacement0,
const FVector3f& Displacement1,
const FVector3f& Displacement2,
int32 MaterialIndex )
{
return GetErrorBounds(
Barycentrics,
Vert0,
Vert1,
Vert2,
Displacement0,
Displacement1,
Displacement2,
MaterialIndex,
Settings.DisplacementUVChannel,
DisplacementMaps );
},
[&](const FVector3f Barycentrics[3],
const FLerpVert& Vert0,
const FLerpVert& Vert1,
const FLerpVert& Vert2,
int32 MaterialIndex )
{
return GetNumSamples(
Barycentrics,
Vert0,
Vert1,
Vert2,
MaterialIndex,
Settings.DisplacementUVChannel,
DisplacementMaps );
} );
const bool bHasVertexColor = Verts.Color.Num() > 0;
Verts.Position.SetNumUninitialized(LerpVerts.Num());
Verts.TangentX.SetNumUninitialized(LerpVerts.Num());
Verts.TangentY.SetNumUninitialized(LerpVerts.Num());
Verts.TangentZ.SetNumUninitialized(LerpVerts.Num());
for (int32 UVCoord = 0; UVCoord < Verts.UVs.Num(); ++UVCoord)
{
Verts.UVs[UVCoord].SetNumUninitialized(LerpVerts.Num());
}
Verts.Color.SetNumUninitialized(bHasVertexColor ? LerpVerts.Num() : 0);
for (int32 LerpIndex = 0; LerpIndex < LerpVerts.Num(); ++LerpIndex)
{
const FLerpVert& LerpVert = LerpVerts[LerpIndex];
Verts.Position[LerpIndex] = LerpVert.Position;
Verts.TangentX[LerpIndex] = LerpVert.TangentX;
Verts.TangentY[LerpIndex] = LerpVert.TangentY;
Verts.TangentZ[LerpIndex] = LerpVert.TangentZ;
for (int32 UVCoord = 0; UVCoord < Verts.UVs.Num(); ++UVCoord)
{
Verts.UVs[UVCoord][LerpIndex] = LerpVert.UVs[UVCoord];
}
if (bHasVertexColor)
{
Verts.Color[LerpIndex] = LerpVert.Color.ToFColor(false);
}
}
}
} // namespace Nanite