293 lines
8.5 KiB
C++
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
|