Files
UnrealEngine/Engine/Source/Developer/NaniteUtilities/Public/SGGX.h
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

253 lines
5.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "VectorUtil.h"
#include "MatrixUtil.h"
// [ Heitz et al. 2015, "The SGGX Microflake Distribution" ]
// Linearly filtering SGGX, which is the same as using the covariance matrix, directly is a decent fit.
// Reprojecting area to eigenvectors can be better but requires a second pass.
template< typename T >
struct TSGGX
{
template< typename U > using TVector2 = UE::Math::TVector2<U>;
template< typename U > using TVector = UE::Math::TVector<U>;
T xx;
T yy;
T zz;
T xy;
T xz;
T yz;
TSGGX() = default;
TSGGX( const TVector<T>& Normal );
TSGGX& operator+=( const TSGGX& Other );
TSGGX& operator+=( const TVector<T>& Normal );
TSGGX& operator*=( T Scalar );
TSGGX& operator/=( T Scalar );
TSGGX operator+( const TSGGX& Other ) const;
TSGGX operator*( T Scalar ) const;
T ProjectedArea( const TVector<T>& Direction ) const;
T SurfaceArea() const;
void EigenSolve( TVector<T>& Values, TVector<T> Vectors[3] ) const;
void FitIsotropic( TVector<T>& Center, TVector2<T>& Alpha ) const;
static T SurfaceArea( const TVector<T>& EigenValues );
};
template< typename T >
FORCEINLINE TSGGX<T>::TSGGX( const TVector<T>& Normal )
{
// n n^T
xx = Normal.X * Normal.X;
yy = Normal.Y * Normal.Y;
zz = Normal.Z * Normal.Z;
xy = Normal.X * Normal.Y;
xz = Normal.X * Normal.Z;
yz = Normal.Y * Normal.Z;
}
template< typename T >
FORCEINLINE TSGGX<T>& TSGGX<T>::operator+=( const TSGGX<T>& Other )
{
xx += Other.xx;
yy += Other.yy;
zz += Other.zz;
xy += Other.xy;
xz += Other.xz;
yz += Other.yz;
return *this;
}
template< typename T >
FORCEINLINE TSGGX<T>& TSGGX<T>::operator+=( const TVector<T>& Normal )
{
return *this += TSGGX( Normal );
}
template< typename T >
FORCEINLINE TSGGX<T>& TSGGX<T>::operator*=( T Scalar )
{
xx *= Scalar;
yy *= Scalar;
zz *= Scalar;
xy *= Scalar;
xz *= Scalar;
yz *= Scalar;
return *this;
}
template< typename T >
FORCEINLINE TSGGX<T>& TSGGX<T>::operator/=( T Scalar )
{
xx /= Scalar;
yy /= Scalar;
zz /= Scalar;
xy /= Scalar;
xz /= Scalar;
yz /= Scalar;
return *this;
}
template< typename T >
FORCEINLINE TSGGX<T> TSGGX<T>::operator+( const TSGGX<T>& Other ) const
{
return TSGGX<T>( *this ) += Other;
}
template< typename T >
FORCEINLINE TSGGX<T> TSGGX<T>::operator*( T Scalar ) const
{
return TSGGX<T>( *this ) *= Scalar;
}
template< typename T >
FORCEINLINE TSGGX<T> operator*( T Scalar, const TSGGX<T>& SGGX )
{
return SGGX * Scalar;
}
template< typename T >
FORCEINLINE T TSGGX<T>::ProjectedArea( const TVector<T>& Direction ) const
{
// Projected area
// alpha = sqrt( w^T S w )
// alpha = sqrt( w^T n n^T w )
// alpha = abs( dot( n, w ) )
T x = Direction | TVector<T>( xx, xy, xz );
T y = Direction | TVector<T>( xy, yy, yz );
T z = Direction | TVector<T>( xz, yz, zz );
T wSw = Direction | TVector<T>( x, y, z );
return FMath::Sqrt( FMath::Max( 0.0f, wSw ) );
}
template< typename T >
void TSGGX<T>::EigenSolve( TVector<T>& Values, TVector<T> Vectors[3] ) const
{
// Diagonalize matrix
// A = V S V^T
T A[] =
{
xx, xy, xz,
xy, yy, yz,
xz, yz, zz
};
T S[3];
T V[9];
JacobiSVD::EigenSolver3( A, S, V, 1e-8f );
for( uint32 i = 0; i < 3; i++ )
{
Values[i] = S[i];
Vectors[i][0] = V[i+0];
Vectors[i][1] = V[i+3];
Vectors[i][2] = V[i+6];
}
}
template< typename T >
void TSGGX<T>::FitIsotropic( TVector<T>& Center, TVector2<T>& Alpha ) const
{
TVector<T> Values;
TVector<T> Vectors[3];
EigenSolve( Values, Vectors );
T Scale[3];
for( uint32 k = 0; k < 3; k++ )
Scale[k] = FMath::Sqrt( FMath::Abs( Values[k] ) ); // Should be positive already
T MaxRatio = 0.0;
int32 MaxIndex = 0;
for( uint32 k = 0; k < 3; k++ )
{
const uint32 k0 = k;
const uint32 k1 = (1 << k0) & 3;
T Ratio = FMath::Min( Scale[k0], Scale[k1] )
/ FMath::Max( Scale[k0], Scale[k1] );
if( MaxRatio < Ratio )
{
MaxRatio = Ratio;
MaxIndex = k;
}
}
const uint32 k0 = MaxIndex;
const uint32 k1 = (1 << k0) & 3;
const uint32 k2 = (1 << k1) & 3;
Center = Vectors[k2];
Alpha[0] = 0.5f * ( Scale[k0] + Scale[k1] );
Alpha[1] = Scale[k2];
}
// Approx surface area of ellipsoid
FORCEINLINE float SurfaceAreaEllipsoid( float a, float b, float c )
{
#if 1
float a2 = a*a;
float b2 = b*b;
float c2 = c*c;
//area ~= pi * ( (1-1/sqrt3) * (ab + ac + bc) + (1+1/sqrt3) * sqrt( a^2*b^2 + a^2*c^2 + b^2*c^2 ) )
return 1.32779329f * ( a*b + a*c + b*c ) +
4.95539202f * FMath::Sqrt( a2 * b2 + a2 * c2 + b2 * c2 );
#else
const float p = 1.6f; // or p=ln(3)/ln(2)
float ap = FMath::Pow( a, p );
float bp = FMath::Pow( b, p );
float cp = FMath::Pow( c, p );
return (2.0f * PI) * FMath::Pow( ap * bp + ap * cp + bp * cp, 1.0f / p );
#endif
}
template< typename T >
T TSGGX<T>::SurfaceArea( const TVector<T>& Values )
{
/*
integral sqrt(wSw) = pi * SurfaceArea
Average ProjectedArea() = 1/4 * SurfaceArea
Ellipsoid scale
a = ( S1 * S2 / S0 )^0.25 / sqrt(pi);
b = ( S2 * S0 / S1 )^0.25 / sqrt(pi);
c = ( S0 * S1 / S2 )^0.25 / sqrt(pi);
area ~= pi * ( (1-1/sqrt3) * (ab + ac + bc) + (1+1/sqrt3) * sqrt( a^2*b^2 + a^2*c^2 + b^2*c^2 ) )
*/
const float sqrt3 = 1.73205080756887729353f;
return (1.0f - 1.0f / sqrt3) * ( FMath::Sqrt( Values[0] ) + FMath::Sqrt( Values[1] ) + FMath::Sqrt( Values[2] ) ) +
(1.0f + 1.0f / sqrt3) * FMath::Sqrt( Values[0] + Values[1] + Values[2] );
}
template< typename T >
T TSGGX<T>::SurfaceArea() const
{
TVector<T> Values;
TVector<T> Vectors[3];
EigenSolve( Values, Vectors );
return SurfaceArea( Values );
}