Files
UnrealEngine/Engine/Shaders/Private/SphericalGaussian.ush
2025-05-18 13:04:45 +08:00

270 lines
6.8 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// G( v; u,L,a ) = a * exp( L * (dot(u,v) - 1) )
struct FSphericalGaussian
{
float3 Axis; // u
float Sharpness; // L
float Amplitude; // a
};
float Evaluate( FSphericalGaussian G, float3 Direction )
{
// G( v; u,L,a ) = a * exp( L * (dot(u,v) - 1) )
return G.Amplitude * exp( G.Sharpness * (dot( G.Axis, Direction ) - 1) );
}
// Integral over the sphere
float Integral( FSphericalGaussian G )
{
// integral G = 2*PI * a/L * ( 1 - exp(-2*L) )
// integral G ~= 2*PI * a/L, L > 2
return (2*PI) * G.Amplitude / G.Sharpness * ( 1 - exp( -2 * G.Sharpness ) );
}
// G / integral G
FSphericalGaussian Normalize( FSphericalGaussian G )
{
// G / integral G = G( v; u, L, L / ( 2*PI * ( 1 - exp(-2*L) ) ) )
// G / integral G ~= G( v; u, L, L / (2*PI) ), L > 2
G.Amplitude = G.Sharpness / ( (2*PI) - (2*PI) * exp( -2 * G.Sharpness ) );
return G;
}
// G0 * G1
FSphericalGaussian Mul( FSphericalGaussian G0, FSphericalGaussian G1 )
{
// um = L0 * u0 + L1 * u1
// G0 * G1 = G( v; um / |um|, |um|, a0*a1 * exp( |um| - L0 - L1 ) )
float Lm = G0.Sharpness + G1.Sharpness;
float3 um = G0.Sharpness * G0.Axis + G1.Sharpness * G1.Axis;
float umLength = length(um);
FSphericalGaussian G =
{
um / umLength,
umLength,
G0.Amplitude * G1.Amplitude * exp( umLength - Lm )
};
return G;
}
// integral G0 * G1
float Dot( FSphericalGaussian G0, FSphericalGaussian G1 )
{
// integral G0 * G1 = 4*PI * a0 * a1 * exp( -L0 - L1 ) * sinh( |um| ) / |um|
// integral G0 * G1 = 2*PI * a0 * a1 * ( exp( |um| - L0 - L1 ) - exp( -|um| - L0 - L1 ) ) / |um|
// integral G0 * G1 ~= 2*PI * a0 * a1 * exp( |um| - L0 - L1 ) / |um|
float Lm = G0.Sharpness + G1.Sharpness;
float3 um = G0.Sharpness * G0.Axis + G1.Sharpness * G1.Axis;
float umLength = length(um);
//return 2*PI * G0.a * G1.a * ( exp( umLength - G0.L - G1.L ) ) / umLength;
//return 2*PI * G0.Amplitude * G1.Amplitude * ( exp( umLength - Lm ) - exp( -umLength - Lm ) ) / umLength;
return (2*PI) * G0.Amplitude * G1.Amplitude * exp( umLength - Lm ) * ( 1 - exp( -2 * umLength ) ) / umLength;
}
// [ Iwasaki 2012, "Interactive Bi-scale Editing of Highly Glossy Materials" ]
FSphericalGaussian Convolve( FSphericalGaussian G0, FSphericalGaussian G1 )
{
FSphericalGaussian G =
{
G0.Axis,
( G0.Sharpness * G1.Sharpness ) / ( G0.Sharpness + G1.Sharpness ),
(2*PI) * ( G0.Amplitude * G1.Amplitude ) / ( G0.Sharpness + G1.Sharpness )
};
return G;
}
// approximation using von Mises-Fisher
FSphericalGaussian ToSphericalGaussian( float3 r, float Value )
{
// L = |r| * ( 3 - |r|^2 ) / ( 1 - |r|^2 )
FSphericalGaussian G;
float LengthR2 = dot( r, r );
float InvLengthR = rsqrt( LengthR2 );
float LengthR = LengthR2 * InvLengthR;
G.Axis = r * InvLengthR;
G.Sharpness = LengthR * ( 3 - LengthR2 ) / ( 1 - min( LengthR2, 0.9999 ) );
G.Amplitude = Value * G.Sharpness / ( (2*PI) - (2*PI) * exp( -2 * G.Sharpness ) );
//G.u = Value * G.L * (0.5/PI);
return G;
}
FSphericalGaussian Add( FSphericalGaussian G0, FSphericalGaussian G1 )
{
// r = ( 1 / tanh(L) - 1/L ) * u
// r = ( ( 1 + exp( -2*L ) ) / ( 1 - exp( -2*L ) ) - 1/L ) * u
// r ~= ( 1 - 1/L + 2 * exp( -2*L ) ) * u, L > 2
float exp2L0 = exp( -2 * G0.Sharpness );
float exp2L1 = exp( -2 * G1.Sharpness );
float3 r0 = ( (1 + exp2L0) / (1 - exp2L0) - rcp( G0.Sharpness ) ) * G0.Axis;
float3 r1 = ( (1 + exp2L1) / (1 - exp2L1) - rcp( G1.Sharpness ) ) * G1.Axis;
float w0 = Integral( G0 );
float w1 = Integral( G1 );
float3 r = ( r0*w0 + r1*w1 ) / (w0 + w1);
float w = w0 + w1;
return ToSphericalGaussian( r, w );
}
// Angle from axis of cone. Half subtended angle.
float GetConeAngle( FSphericalGaussian G )
{
// cone angle ~= PI - PI*|r|
// r = ( 1 / tanh(k) - 1/k ) * u ~= ( 1 - 1/k + 2 * exp( -2*k ) ) * u, k > 2
// ConeAngle ~= sqrt( 2/L )
return sqrt( 2 / G.Sharpness );
}
// Inner product with cosine lobe
// Assumes G is normalized
float DotCosineLobe( FSphericalGaussian G, float3 N )
{
const float muDotN = dot( G.Axis, N );
const float c0 = 0.36;
const float c1 = 0.25 / c0;
float eml = exp( -G.Sharpness );
float em2l = eml * eml;
float rl = rcp( G.Sharpness );
float scale = 1.0f + 2.0f * em2l - rl;
float bias = (eml - em2l) * rl - em2l;
float x = sqrt( 1.0 - scale );
float x0 = c0 * muDotN;
float x1 = c1 * x;
float n = x0 + x1;
float y = ( abs( x0 ) <= x1 ) ? n*n / x : saturate( muDotN );
return scale * y + bias;
}
// [ Wang et al. 2009, "All-Frequency Rendering of Dynamic, Spatially-Varying Reflectance" ]
FSphericalGaussian ClampedCosine_ToSphericalGaussian( float3 Normal )
{
FSphericalGaussian G;
G.Axis = Normal;
G.Sharpness = 2.133;
G.Amplitude = 1.17;
// Integrate to PI
//G.Sharpness = 2.3;
//G.Amplitude = 0.5 * 2.3 / ( 1 - exp( -2 * 2.3 ) );
return G;
}
FSphericalGaussian Hemisphere_ToSphericalGaussian( float3 Normal )
{
FSphericalGaussian G;
G.Axis = Normal;
G.Sharpness = 0.81;
G.Amplitude = 0.81 / ( 1 - exp( -2 * 0.81 ) );
return G;
}
// Bent normal is normalized. AO is [0,1]. Both are cosine weighted.
FSphericalGaussian BentNormalAO_ToSphericalGaussian( float3 BentNormal, float AO )
{
// ConeAngle ~= sqrt( 2/L )
// L ~= 2/ConeAngle^2
FSphericalGaussian G;
G.Axis = BentNormal;
#if 1
// Cosine weighted integration of spherical cap
// PI * SinAlpha^2
// L ~= 2 / Pow2( acos( sqrt(1- AO) ) );
// Approximation (no acos)
G.Sharpness = ( 0.75 + 1.25 * sqrt( 1 - AO ) ) / AO;
#else
// Solid angle of cone = 2 * PI * (1 - CosTheta)
// Solid angle of cone = 2*PI * AO
// AO = 1 - cos( ConeAngle )
// L ~= 2 / Pow2( acos( 1- AO ) );
// Approximation (no acos)
G.Sharpness = ( 1 - 0.19 * AO ) / AO;
#endif
// AO=1 integrates to 2pi
const float HemisphereSharpness = 0.81;
G.Amplitude = HemisphereSharpness / ( 1 - exp( -2 * HemisphereSharpness ) );
return G;
}
/*
static const float C[5] =
{
0.5 / sqrt(PI),
0.5 * sqrt(3/PI),
};
struct FSphericalHarmonics
{
float SH[4];
};
FSphericalHarmonics Add( float3 Direction, float Value )
{
}*/
struct FAnisoSphericalGaussian
{
float3 AxisX;
float3 AxisY;
float3 AxisZ;
float SharpnessX;
float SharpnessY;
float Amplitude;
};
float Evaluate( FAnisoSphericalGaussian ASG, float3 Direction )
{
float L = ASG.SharpnessX * Pow2( dot( Direction, ASG.AxisX ) );
float u = ASG.SharpnessY * Pow2( dot( Direction, ASG.AxisY ) );
return ASG.Amplitude * saturate( dot( Direction, ASG.AxisZ ) ) * exp( -L - u );
}
float Dot( FAnisoSphericalGaussian ASG, FSphericalGaussian SG )
{
// ASG( v; u,nu,a ) = a * exp( 2 * nu * (dot(u,v) - 1) )
float nu = SG.Sharpness * 0.5;
ASG.Amplitude *= SG.Amplitude;
ASG.Amplitude *= PI * rsqrt( (nu + ASG.SharpnessX) * (nu + ASG.SharpnessY) );
ASG.SharpnessX = (nu * ASG.SharpnessX) / (nu + ASG.SharpnessX);
ASG.SharpnessY = (nu * ASG.SharpnessY) / (nu + ASG.SharpnessY);
return Evaluate( ASG, SG.Axis );
}