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

160 lines
4.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DisplacementMap.h"
#include "ImageCore.h"
#include "ImageCoreUtils.h"
namespace Nanite
{
FDisplacementMap::FDisplacementMap()
: SourceFormat( TSF_G8 )
, BytesPerPixel(1)
, SizeX(1)
, SizeY(1)
, NumLevels(1)
, Magnitude( 0.0f )
, Center( 0.0f )
, AddressX( TA_Wrap )
, AddressY( TA_Wrap )
{
SourceData.Add(0);
}
FDisplacementMap::FDisplacementMap( FImage&& TextureSourceImage, float InMagnitude, float InCenter, TextureAddress InAddressX, TextureAddress InAddressY )
: NumLevels(1)
, Magnitude( InMagnitude )
, Center( InCenter )
, AddressX( InAddressX )
, AddressY( InAddressY )
{
SourceData = MoveTemp(TextureSourceImage.RawData);
check(!SourceData.IsEmpty());
SourceFormat = FImageCoreUtils::ConvertToTextureSourceFormat(TextureSourceImage.Format);
BytesPerPixel = ERawImageFormat::GetBytesPerPixel(TextureSourceImage.Format);
SizeX = TextureSourceImage.GetWidth();
SizeY = TextureSourceImage.GetHeight();
uint32 PrevSizeX = SizeX;
uint32 PrevSizeY = SizeY;
for( uint32 Level = 1; ; Level++ )
{
uint32 MipSizeX = ( ( SizeX - 1 ) >> Level ) + 1;
uint32 MipSizeY = ( ( SizeY - 1 ) >> Level ) + 1;
if( MipSizeX == 1 && MipSizeY == 1 )
break;
MipData[ Level - 1 ].AddUninitialized( MipSizeX * MipSizeY );
for( uint32 y = 0; y < MipSizeY; y++ )
{
for( uint32 x = 0; x < MipSizeX; x++ )
{
uint32 x0 = x*2;
uint32 y0 = y*2;
uint32 x1 = FMath::Min( x0 + 1, PrevSizeX - 1 );
uint32 y1 = FMath::Min( y0 + 1, PrevSizeY - 1 );
if( Level == 1 )
{
float d0 = Load( x0, y0 );
float d1 = Load( x1, y0 );
float d2 = Load( x0, y1 );
float d3 = Load( x1, y1 );
MipData[ Level - 1 ][ x + y * MipSizeX ] = FVector2f(
FMath::Min( d0, FMath::Min3( d1, d2, d3 ) ),
FMath::Max( d0, FMath::Max3( d1, d2, d3 ) ) );
}
else
{
FVector2f d0 = Load( x0, y0, Level - 1 );
FVector2f d1 = Load( x1, y0, Level - 1 );
FVector2f d2 = Load( x0, y1, Level - 1 );
FVector2f d3 = Load( x1, y1, Level - 1 );
MipData[ Level - 1 ][ x + y * MipSizeX ] = FVector2f(
FMath::Min( d0.X, FMath::Min3( d1.X, d2.X, d3.X ) ),
FMath::Max( d0.Y, FMath::Max3( d1.Y, d2.Y, d3.Y ) ) );
}
}
}
PrevSizeX = MipSizeX;
PrevSizeY = MipSizeY;
NumLevels++;
}
}
// Bilinear filtered
float FDisplacementMap::Sample( FVector2f UV ) const
{
// Half texel
UV.X = UV.X * SizeX - 0.5f;
UV.Y = UV.Y * SizeY - 0.5f;
int32 x0 = FMath::FloorToInt32( UV.X );
int32 y0 = FMath::FloorToInt32( UV.Y );
int32 x1 = x0 + 1;
int32 y1 = y0 + 1;
float wx1 = UV.X - x0;
float wy1 = UV.Y - y0;
float wx0 = 1.0f - wx1;
float wy0 = 1.0f - wy1;
return
Sample( x0, y0 ) * wx0 * wy0 +
Sample( x1, y0 ) * wx1 * wy0 +
Sample( x0, y1 ) * wx0 * wy1 +
Sample( x1, y1 ) * wx1 * wy1;
}
// Returns min/max over bilinear footprint
FVector2f FDisplacementMap::Sample( FVector2f MinUV, FVector2f MaxUV ) const
{
// Half texel
MinUV = MinUV * FVector2f( SizeX, SizeY ) - FVector2f( 0.5f );
MaxUV = MaxUV * FVector2f( SizeX, SizeY ) - FVector2f( 0.5f );
int32 x0 = FMath::FloorToInt32( MinUV.X );
int32 y0 = FMath::FloorToInt32( MinUV.Y );
int32 x1 = FMath::FloorToInt32( MaxUV.X ) + 1;
int32 y1 = FMath::FloorToInt32( MaxUV.Y ) + 1;
uint32 Level = FMath::FloorLog2( FMath::Max( x1 - x0, y1 - y0 ) );
if( (x1 >> Level) - (x0 >> Level) > 1 ||
(y1 >> Level) - (y0 >> Level) > 1 )
Level++;
Level = FMath::Min( Level, NumLevels - 1 );
if( Level == 0 )
{
float d0 = Sample( x0, y0 );
float d1 = Sample( x1, y0 );
float d2 = Sample( x0, y1 );
float d3 = Sample( x1, y1 );
return FVector2f(
FMath::Min( d0, FMath::Min3( d1, d2, d3 ) ),
FMath::Max( d0, FMath::Max3( d1, d2, d3 ) ) );
}
else
{
FVector2f d0 = Sample( x0, y0, Level );
FVector2f d1 = Sample( x1, y0, Level );
FVector2f d2 = Sample( x0, y1, Level );
FVector2f d3 = Sample( x1, y1, Level );
return FVector2f(
FMath::Min( d0.X, FMath::Min3( d1.X, d2.X, d3.X ) ),
FMath::Max( d0.Y, FMath::Max3( d1.Y, d2.Y, d3.Y ) ) );
}
}
} // namespace Nanite