774 lines
28 KiB
HLSL
774 lines
28 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
BRDF.usf: Bidirectional reflectance distribution functions.
|
|
=============================================================================*/
|
|
|
|
#pragma once
|
|
|
|
struct BxDFContext
|
|
{
|
|
half NoV;
|
|
half NoL;
|
|
half VoL;
|
|
half NoH;
|
|
half VoH;
|
|
half XoV;
|
|
half XoL;
|
|
half XoH;
|
|
half YoV;
|
|
half YoL;
|
|
half YoH;
|
|
};
|
|
|
|
void Init( inout BxDFContext Context, half3 N, half3 V, half3 L )
|
|
{
|
|
Context.NoL = dot(N, L);
|
|
Context.NoV = dot(N, V);
|
|
Context.VoL = dot(V, L);
|
|
float InvLenH = rsqrt( 2 + 2 * Context.VoL );
|
|
Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
|
|
Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );
|
|
//NoL = saturate( NoL );
|
|
//NoV = saturate( abs( NoV ) + 1e-5 );
|
|
|
|
Context.XoV = 0.0f;
|
|
Context.XoL = 0.0f;
|
|
Context.XoH = 0.0f;
|
|
Context.YoV = 0.0f;
|
|
Context.YoL = 0.0f;
|
|
Context.YoH = 0.0f;
|
|
}
|
|
|
|
void Init( inout BxDFContext Context, half3 N, half3 X, half3 Y, half3 V, half3 L )
|
|
{
|
|
Context.NoL = dot(N, L);
|
|
Context.NoV = dot(N, V);
|
|
Context.VoL = dot(V, L);
|
|
float InvLenH = rsqrt( 2 + 2 * Context.VoL );
|
|
Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
|
|
Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );
|
|
//NoL = saturate( NoL );
|
|
//NoV = saturate( abs( NoV ) + 1e-5 );
|
|
|
|
Context.XoV = dot(X, V);
|
|
Context.XoL = dot(X, L);
|
|
Context.XoH = (Context.XoL + Context.XoV) * InvLenH;
|
|
Context.YoV = dot(Y, V);
|
|
Context.YoL = dot(Y, L);
|
|
Context.YoH = (Context.YoL + Context.YoV) * InvLenH;
|
|
}
|
|
|
|
void InitMobile(inout BxDFContext Context, half3 N, half3 V, half3 L, half NoL)
|
|
{
|
|
Context.NoL = NoL;
|
|
Context.NoV = dot(N, V);
|
|
Context.VoL = dot(V, L);
|
|
float3 H = normalize(float3(V + L));
|
|
Context.NoH = max(0, dot(N, H));
|
|
Context.VoH = max(0, dot(V, H));
|
|
|
|
//NoL = saturate( NoL );
|
|
//NoV = saturate( abs( NoV ) + 1e-5 );
|
|
|
|
Context.XoV = 0.0f;
|
|
Context.XoL = 0.0f;
|
|
Context.XoH = 0.0f;
|
|
Context.YoV = 0.0f;
|
|
Context.YoL = 0.0f;
|
|
Context.YoH = 0.0f;
|
|
}
|
|
|
|
// [ de Carpentier 2017, "Decima Engine: Advances in Lighting and AA" ]
|
|
void SphereMaxNoH( inout BxDFContext Context, float SinAlpha, bool bNewtonIteration )
|
|
{
|
|
if( SinAlpha > 0 )
|
|
{
|
|
float CosAlpha = sqrt( 1 - Pow2( SinAlpha ) );
|
|
|
|
float RoL = 2 * Context.NoL * Context.NoV - Context.VoL;
|
|
if( RoL >= CosAlpha )
|
|
{
|
|
Context.NoH = 1;
|
|
Context.XoH = 0;
|
|
Context.YoH = 0;
|
|
Context.VoH = abs( Context.NoV );
|
|
}
|
|
else
|
|
{
|
|
float rInvLengthT = SinAlpha * rsqrt( 1 - RoL*RoL );
|
|
float NoTr = rInvLengthT * ( Context.NoV - RoL * Context.NoL );
|
|
// Enable once anisotropic materials support area lights
|
|
#if 0
|
|
float XoTr = rInvLengthT * ( Context.XoV - RoL * Context.XoL );
|
|
float YoTr = rInvLengthT * ( Context.YoV - RoL * Context.YoL );
|
|
#endif
|
|
float VoTr = rInvLengthT * ( 2 * Context.NoV*Context.NoV - 1 - RoL * Context.VoL );
|
|
|
|
if (bNewtonIteration)
|
|
{
|
|
// dot( cross(N,L), V )
|
|
float NxLoV = sqrt( saturate( 1 - Pow2(Context.NoL) - Pow2(Context.NoV) - Pow2(Context.VoL) + 2 * Context.NoL * Context.NoV * Context.VoL ) );
|
|
|
|
float NoBr = rInvLengthT * NxLoV;
|
|
float VoBr = rInvLengthT * NxLoV * 2 * Context.NoV;
|
|
|
|
float NoLVTr = Context.NoL * CosAlpha + Context.NoV + NoTr;
|
|
float VoLVTr = Context.VoL * CosAlpha + 1 + VoTr;
|
|
|
|
float p = NoBr * VoLVTr;
|
|
float q = NoLVTr * VoLVTr;
|
|
float s = VoBr * NoLVTr;
|
|
|
|
float xNum = q * ( -0.5 * p + 0.25 * VoBr * NoLVTr );
|
|
float xDenom = p*p + s * (s - 2*p) + NoLVTr * ( (Context.NoL * CosAlpha + Context.NoV) * Pow2(VoLVTr) + q * (-0.5 * (VoLVTr + Context.VoL * CosAlpha) - 0.5) );
|
|
float TwoX1 = 2 * xNum / ( Pow2(xDenom) + Pow2(xNum) );
|
|
float SinTheta = TwoX1 * xDenom;
|
|
float CosTheta = 1.0 - TwoX1 * xNum;
|
|
NoTr = CosTheta * NoTr + SinTheta * NoBr;
|
|
VoTr = CosTheta * VoTr + SinTheta * VoBr;
|
|
}
|
|
|
|
Context.NoL = Context.NoL * CosAlpha + NoTr; // dot( N, L * CosAlpha + T * SinAlpha )
|
|
// Enable once anisotropic materials support area lights
|
|
#if 0
|
|
Context.XoL = Context.XoL * CosAlpha + XoTr;
|
|
Context.YoL = Context.YoL * CosAlpha + YoTr;
|
|
#endif
|
|
Context.VoL = Context.VoL * CosAlpha + VoTr;
|
|
|
|
#if SUBSTRATE_ENABLED
|
|
// It seems that VoL can end up out of the [-1, 1] range.
|
|
// This causes NaN when evaluating the phase function for Substrate SimpleVolume diffuse (sqrt of a negative number is NaN, see HenyeyGreensteinPhase).
|
|
// So we enforce a valid VoL range here for substrate which does do math when VoL is close to -1 in this case.
|
|
Context.VoL = clamp(Context.VoL, -1.0, 1.0);
|
|
#endif
|
|
|
|
float InvLenH = rsqrt( 2 + 2 * Context.VoL );
|
|
Context.NoH = saturate( ( Context.NoL + Context.NoV ) * InvLenH );
|
|
// Enable once anisotropic materials support area lights
|
|
#if 0
|
|
Context.XoH = ((Context.XoL + Context.XoV) * InvLenH); // dot(X, (L+V)/|L+V|)
|
|
Context.YoH = ((Context.YoL + Context.YoV) * InvLenH);
|
|
#endif
|
|
Context.VoH = saturate( InvLenH + InvLenH * Context.VoL );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Physically based shading model
|
|
// parameterized with the below options
|
|
// [ Karis 2013, "Real Shading in Unreal Engine 4" slide 11 ]
|
|
|
|
// E = Random sample for BRDF.
|
|
// N = Normal of the macro surface.
|
|
// H = Normal of the micro surface.
|
|
// V = View vector going from surface's position towards the view's origin.
|
|
// L = Light ray direction
|
|
|
|
// D = Microfacet NDF
|
|
// G = Shadowing and masking
|
|
// F = Fresnel
|
|
|
|
// Vis = G / (4*NoL*NoV)
|
|
// f = Microfacet specular BRDF = D*G*F / (4*NoL*NoV) = D*Vis*F
|
|
|
|
|
|
half3 Diffuse_Lambert( half3 DiffuseColor )
|
|
{
|
|
return DiffuseColor * (1 / PI);
|
|
}
|
|
|
|
// [Burley 2012, "Physically-Based Shading at Disney"]
|
|
float3 Diffuse_Burley( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
|
|
{
|
|
float FD90 = 0.5 + 2 * VoH * VoH * Roughness;
|
|
float FdV = 1 + (FD90 - 1) * Pow5( 1 - NoV );
|
|
float FdL = 1 + (FD90 - 1) * Pow5( 1 - NoL );
|
|
return DiffuseColor * ( (1 / PI) * FdV * FdL );
|
|
}
|
|
|
|
// [Gotanda 2012, "Beyond a Simple Physically Based Blinn-Phong Model in Real-Time"]
|
|
float3 Diffuse_OrenNayar( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
|
|
{
|
|
float a = Roughness * Roughness;
|
|
float s = a;// / ( 1.29 + 0.5 * a );
|
|
float s2 = s * s;
|
|
float VoL = 2 * VoH * VoH - 1; // double angle identity
|
|
float Cosri = VoL - NoV * NoL;
|
|
float C1 = 1 - 0.5 * s2 / (s2 + 0.33);
|
|
float C2 = 0.45 * s2 / (s2 + 0.09) * Cosri * ( Cosri >= 0 ? rcp( max( NoL, NoV ) ) : 1 );
|
|
return DiffuseColor / PI * ( C1 + C2 ) * ( 1 + Roughness * 0.5 );
|
|
}
|
|
|
|
// [Gotanda 2014, "Designing Reflectance Models for New Consoles"]
|
|
float3 Diffuse_Gotanda( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH )
|
|
{
|
|
float a = Roughness * Roughness;
|
|
float a2 = a * a;
|
|
float F0 = 0.04;
|
|
float VoL = 2 * VoH * VoH - 1; // double angle identity
|
|
float Cosri = VoL - NoV * NoL;
|
|
#if 1
|
|
float a2_13 = a2 + 1.36053;
|
|
float Fr = ( 1 - ( 0.542026*a2 + 0.303573*a ) / a2_13 ) * ( 1 - pow( 1 - NoV, 5 - 4*a2 ) / a2_13 ) * ( ( -0.733996*a2*a + 1.50912*a2 - 1.16402*a ) * pow( 1 - NoV, 1 + rcp(39*a2*a2+1) ) + 1 );
|
|
//float Fr = ( 1 - 0.36 * a ) * ( 1 - pow( 1 - NoV, 5 - 4*a2 ) / a2_13 ) * ( -2.5 * Roughness * ( 1 - NoV ) + 1 );
|
|
float Lm = ( max( 1 - 2*a, 0 ) * ( 1 - Pow5( 1 - NoL ) ) + min( 2*a, 1 ) ) * ( 1 - 0.5*a * (NoL - 1) ) * NoL;
|
|
float Vd = ( a2 / ( (a2 + 0.09) * (1.31072 + 0.995584 * NoV) ) ) * ( 1 - pow( 1 - NoL, ( 1 - 0.3726732 * NoV * NoV ) / ( 0.188566 + 0.38841 * NoV ) ) );
|
|
float Bp = Cosri < 0 ? 1.4 * NoV * NoL * Cosri : Cosri;
|
|
float Lr = (21.0 / 20.0) * (1 - F0) * ( Fr * Lm + Vd + Bp );
|
|
return DiffuseColor / PI * Lr;
|
|
#else
|
|
float a2_13 = a2 + 1.36053;
|
|
float Fr = ( 1 - ( 0.542026*a2 + 0.303573*a ) / a2_13 ) * ( 1 - pow( 1 - NoV, 5 - 4*a2 ) / a2_13 ) * ( ( -0.733996*a2*a + 1.50912*a2 - 1.16402*a ) * pow( 1 - NoV, 1 + rcp(39*a2*a2+1) ) + 1 );
|
|
float Lm = ( max( 1 - 2*a, 0 ) * ( 1 - Pow5( 1 - NoL ) ) + min( 2*a, 1 ) ) * ( 1 - 0.5*a + 0.5*a * NoL );
|
|
float Vd = ( a2 / ( (a2 + 0.09) * (1.31072 + 0.995584 * NoV) ) ) * ( 1 - pow( 1 - NoL, ( 1 - 0.3726732 * NoV * NoV ) / ( 0.188566 + 0.38841 * NoV ) ) );
|
|
float Bp = Cosri < 0 ? 1.4 * NoV * Cosri : Cosri / max( NoL, 1e-8 );
|
|
float Lr = (21.0 / 20.0) * (1 - F0) * ( Fr * Lm + Vd + Bp );
|
|
return DiffuseColor / PI * Lr;
|
|
#endif
|
|
}
|
|
|
|
// [ Chan 2018, "Material Advances in Call of Duty: WWII" ]
|
|
// It has been extended here to fade out retro reflectivity contribution from area light in order to avoid visual artefacts.
|
|
float3 Diffuse_Chan( float3 DiffuseColor, float a2, float NoV, float NoL, float VoH, float NoH, float RetroReflectivityWeight)
|
|
{
|
|
// We saturate each input to avoid out of range negative values which would result in weird darkening at the edge of meshes (resulting from tangent space interpolation).
|
|
NoV = saturate(NoV);
|
|
NoL = saturate(NoL);
|
|
VoH = saturate(VoH);
|
|
NoH = saturate(NoH);
|
|
|
|
// a2 = 2 / ( 1 + exp2( 18 * g )
|
|
float g = saturate( (1.0 / 18.0) * log2( 2 * rcpFast(a2) - 1 ) );
|
|
|
|
float F0 = VoH + Pow5( 1 - VoH );
|
|
float FdV = 1 - 0.75 * Pow5( 1 - NoV );
|
|
float FdL = 1 - 0.75 * Pow5( 1 - NoL );
|
|
|
|
// Rough (F0) to smooth (FdV * FdL) response interpolation
|
|
float Fd = lerp( F0, FdV * FdL, saturate( 2.2 * g - 0.5 ) );
|
|
|
|
// Retro reflectivity contribution.
|
|
float Fb = ( (34.5 * g - 59 ) * g + 24.5 ) * VoH * exp2( -max( 73.2 * g - 21.2, 8.9 ) * sqrtFast( NoH ) );
|
|
// It fades out when lights become area lights in order to avoid visual artefacts.
|
|
Fb *= RetroReflectivityWeight;
|
|
|
|
float Lobe = (1 / PI) * (Fd + Fb);
|
|
|
|
// We clamp the BRDF lobe value to an arbitrary value of 1 to get some practical benefits at high roughness:
|
|
// - This is to avoid too bright edges when using normal map on a mesh and the local bases, L, N and V ends up in an top emisphere setup.
|
|
// - This maintains the full proper rough look of a sphere when not using normal maps.
|
|
// - This also fixes the furnace test returning too much energy at the edge of a mesh.
|
|
Lobe = min(1.0, Lobe);
|
|
|
|
return DiffuseColor * Lobe;
|
|
}
|
|
|
|
// [Blinn 1977, "Models of light reflection for computer synthesized pictures"]
|
|
float D_Blinn( float a2, float NoH )
|
|
{
|
|
float n = 2 / a2 - 2;
|
|
return (n+2) / (2*PI) * PhongShadingPow( NoH, n ); // 1 mad, 1 exp, 1 mul, 1 log
|
|
}
|
|
|
|
// [Beckmann 1963, "The scattering of electromagnetic waves from rough surfaces"]
|
|
float D_Beckmann( float a2, float NoH )
|
|
{
|
|
float NoH2 = NoH * NoH;
|
|
return exp( (NoH2 - 1) / (a2 * NoH2) ) / ( PI * a2 * NoH2 * NoH2 );
|
|
}
|
|
|
|
// GGX / Trowbridge-Reitz
|
|
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"]
|
|
float D_GGX( float a2, float NoH )
|
|
{
|
|
float d = ( NoH * a2 - NoH ) * NoH + 1; // 2 mad
|
|
return a2 / ( PI*d*d ); // 4 mul, 1 rcp
|
|
}
|
|
|
|
// Anisotropic GGX
|
|
// [Burley 2012, "Physically-Based Shading at Disney"]
|
|
float D_GGXaniso( float ax, float ay, float NoH, float XoH, float YoH )
|
|
{
|
|
// The two formulations are mathematically equivalent
|
|
#if 1
|
|
float a2 = ax * ay;
|
|
float3 V = float3(ay * XoH, ax * YoH, a2 * NoH);
|
|
float S = dot(V, V);
|
|
|
|
return (1.0f / PI) * a2 * Square(a2 / S);
|
|
#else
|
|
float d = XoH*XoH / (ax*ax) + YoH*YoH / (ay*ay) + NoH*NoH;
|
|
return 1.0f / ( PI * ax*ay * d*d );
|
|
#endif
|
|
}
|
|
|
|
float Vis_Implicit()
|
|
{
|
|
return 0.25;
|
|
}
|
|
|
|
// [Neumann et al. 1999, "Compact metallic reflectance models"]
|
|
float Vis_Neumann( float NoV, float NoL )
|
|
{
|
|
return 1 / ( 4 * max( NoL, NoV ) );
|
|
}
|
|
|
|
// [Kelemen 2001, "A microfacet based coupled specular-matte brdf model with importance sampling"]
|
|
float Vis_Kelemen( float VoH )
|
|
{
|
|
// constant to prevent NaN
|
|
return rcp( 4 * VoH * VoH + 1e-5);
|
|
}
|
|
|
|
// Tuned to match behavior of Vis_Smith
|
|
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
|
|
float Vis_Schlick( float a2, float NoV, float NoL )
|
|
{
|
|
float k = sqrt(a2) * 0.5;
|
|
float Vis_SchlickV = NoV * (1 - k) + k;
|
|
float Vis_SchlickL = NoL * (1 - k) + k;
|
|
return 0.25 / ( Vis_SchlickV * Vis_SchlickL );
|
|
}
|
|
|
|
// Smith term for GGX
|
|
// [Smith 1967, "Geometrical shadowing of a random rough surface"]
|
|
float Vis_Smith( float a2, float NoV, float NoL )
|
|
{
|
|
float Vis_SmithV = NoV + sqrt( NoV * (NoV - NoV * a2) + a2 );
|
|
float Vis_SmithL = NoL + sqrt( NoL * (NoL - NoL * a2) + a2 );
|
|
return rcp( Vis_SmithV * Vis_SmithL );
|
|
}
|
|
|
|
// Appoximation of joint Smith term for GGX
|
|
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
|
|
float Vis_SmithJointApprox( float a2, float NoV, float NoL )
|
|
{
|
|
float a = sqrt(a2);
|
|
float Vis_SmithV = NoL * ( NoV * ( 1 - a ) + a );
|
|
float Vis_SmithL = NoV * ( NoL * ( 1 - a ) + a );
|
|
return 0.5 * rcp( Vis_SmithV + Vis_SmithL );
|
|
}
|
|
|
|
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
|
|
float Vis_SmithJoint(float a2, float NoV, float NoL)
|
|
{
|
|
float Vis_SmithV = NoL * sqrt(NoV * (NoV - NoV * a2) + a2);
|
|
float Vis_SmithL = NoV * sqrt(NoL * (NoL - NoL * a2) + a2);
|
|
return 0.5 * rcp(Vis_SmithV + Vis_SmithL);
|
|
}
|
|
|
|
// [Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"]
|
|
float Vis_SmithJointAniso(float ax, float ay, float NoV, float NoL, float XoV, float XoL, float YoV, float YoL)
|
|
{
|
|
float Vis_SmithV = NoL * length(float3(ax * XoV, ay * YoV, NoV));
|
|
float Vis_SmithL = NoV * length(float3(ax * XoL, ay * YoL, NoL));
|
|
return 0.5 * rcp(Vis_SmithV + Vis_SmithL);
|
|
}
|
|
|
|
float3 F_None( float3 SpecularColor )
|
|
{
|
|
return SpecularColor;
|
|
}
|
|
|
|
// [Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"]
|
|
float3 F_Schlick( float3 SpecularColor, float VoH )
|
|
{
|
|
float Fc = Pow5( 1 - VoH ); // 1 sub, 3 mul
|
|
//return Fc + (1 - Fc) * SpecularColor; // 1 add, 3 mad
|
|
|
|
// Anything less than 2% is physically impossible and is instead considered to be shadowing
|
|
return saturate( 50.0 * SpecularColor.g ) * Fc + (1 - Fc) * SpecularColor;
|
|
}
|
|
|
|
float3 F_Schlick(float3 F0, float3 F90, float VoH)
|
|
{
|
|
float Fc = Pow5(1 - VoH);
|
|
return F90 * Fc + (1 - Fc) * F0;
|
|
}
|
|
|
|
float3 F_AdobeF82(float3 F0, float3 F82, float VoH)
|
|
{
|
|
// [Kutz et al. 2021, "Novel aspects of the Adobe Standard Material" ]
|
|
// See Section 2.3 (note the formulas in the paper do not match the code, the code is the correct version)
|
|
// The constants below are derived by just constant folding the terms dependent on CosThetaMax=1/7
|
|
const float Fc = Pow5(1 - VoH);
|
|
const float K = 49.0 / 46656.0;
|
|
float3 b = (K - K * F82) * (7776.0 + 9031.0 * F0);
|
|
return saturate(F0 + Fc * ((1 - F0) - b * (VoH - VoH * VoH)));
|
|
}
|
|
|
|
float3 F_Fresnel( float3 SpecularColor, float VoH )
|
|
{
|
|
float3 SpecularColorSqrt = sqrt( clamp(SpecularColor, float3(0, 0, 0), float3(0.99, 0.99, 0.99) ) );
|
|
float3 n = ( 1 + SpecularColorSqrt ) / ( 1 - SpecularColorSqrt );
|
|
float3 g = sqrt( n*n + VoH*VoH - 1 );
|
|
return 0.5 * Square( (g - VoH) / (g + VoH) ) * ( 1 + Square( ((g+VoH)*VoH - 1) / ((g-VoH)*VoH + 1) ) );
|
|
}
|
|
|
|
#if SUPPORTS_INDEPENDENT_SAMPLERS
|
|
#define SharedSheenLTCSampler View.SharedBilinearClampedSampler
|
|
#else
|
|
#define SharedSheenLTCSampler InSheenSampler
|
|
#endif
|
|
|
|
float4 SheenLTC_Cofficients(float NoV, float Roughness, Texture2D InSheenLTCTexture, SamplerState InSheenSampler)
|
|
{
|
|
#if 1
|
|
const float Alpha = sqrt(Roughness);
|
|
const float SatNoV = saturate(abs(NoV) + 1e-5);
|
|
float2 UV = float2(Alpha, SatNoV);
|
|
UV = UV * (31.0 / 32.0) + (0.5 / 32.0);
|
|
return InSheenLTCTexture.SampleLevel(SharedSheenLTCSampler, UV, 0);
|
|
#else
|
|
// Sheen model LUT approximation with ALU under the MaterialX TPS.
|
|
// SUBSTRATE_TODO: measure if this is worth it on some platforms.
|
|
float x = saturate(abs(NoV) + 1e-5);
|
|
float y = sqrt(Roughness);
|
|
|
|
float A = ((2.58126 * x + 0.813703 * y) * y) / (1.0 + 0.310327 * x * x + 2.60994 * x * y);
|
|
float B = sqrt(1.0 - x) * (y - 1.0) * y * y * y / (0.0000254053 + 1.71228 * x - 1.71506 * x * y + 1.34174 * y * y);
|
|
|
|
float s = y * (0.0206607 + 1.58491 * y) / (0.0379424 + y * (1.32227 + y));
|
|
float m = y * (-0.193854 + y * (-1.14885 + y * (1.7932 - 0.95943 * y * y))) / (0.046391 + y);
|
|
float o = y * (0.000654023 + (-0.0207818 + 0.119681 * y) * y) / (1.26264 + y * (-1.92021 + y));
|
|
float R = exp(-0.5 * Square((x - m) / s)) / (s * sqrt(2.0 * PI)) + o;
|
|
|
|
return float4(A, B, R, 0.0f);
|
|
#endif
|
|
}
|
|
|
|
float SheenLTC_DirectionalAlbedo(float NoV, float Roughness, Texture2D InSheenLTCTexture, SamplerState InSheenSampler)
|
|
{
|
|
return SheenLTC_Cofficients(NoV, Roughness, InSheenLTCTexture, InSheenSampler).z;
|
|
}
|
|
|
|
// [Zeltner et al. 2022, "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines"]
|
|
float3 SheenLTC_Sample(float3 CosSample, float3 V, half3 N, float NoV, float Roughness, Texture2D InSheenLTCTexture, SamplerState InSheenSampler)
|
|
{
|
|
const float4 LTC = SheenLTC_Cofficients(NoV, Roughness, InSheenLTCTexture, InSheenSampler);
|
|
const float aInv = LTC.x;
|
|
const float bInv = LTC.y;
|
|
// NOTE: The vector is scaled by aInv compared to the reference implementation to avoid unecessary divides
|
|
const float3 LocalL = normalize(float3(CosSample.x - CosSample.z * bInv, CosSample.y, aInv * CosSample.z));
|
|
|
|
// Rotate L into tangent space
|
|
const float3 T1 = normalize(V - N * NoV);
|
|
const float3 T2 = cross(N, T1);
|
|
const float3x3 TangentBasis = float3x3(T1, T2, N);
|
|
const float3 S = mul(LocalL, TangentBasis);
|
|
return S;
|
|
}
|
|
|
|
// [Zeltner et al. 2022, "Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines"]
|
|
// The "OutDirectionalAlbedo" should be multiplied by the result (it is kept separate because in the sampling case it needs to be only included in the weight, not the pdf)
|
|
float SheenLTC_Eval(float3 V, float3 L, half3 N, float NoV, float Roughness, Texture2D InSheenLTCTexture, SamplerState InSheenSampler, inout float OutDirectionalAlbedo)
|
|
{
|
|
// Rotate L into tangent space
|
|
const float3 T1 = normalize(V - N * NoV);
|
|
const float3 T2 = cross(N, T1);
|
|
const float3x3 TangentBasis = float3x3(T1, T2, N);
|
|
|
|
// L' = mul(L, InvT)
|
|
// Since TangentBasis is orthnormal Inv(T)=Transpose(T), i.e., mul(L, transpose(T)), which is equivalent to mul(T,L)
|
|
const float3 LocalL = mul(TangentBasis, L);
|
|
|
|
const float4 LTC = SheenLTC_Cofficients(NoV, Roughness, InSheenLTCTexture, InSheenSampler);
|
|
const float aInv = LTC.x;
|
|
const float bInv = LTC.y;
|
|
OutDirectionalAlbedo = LTC.z;
|
|
|
|
float3 WrappedLocalL = float3(
|
|
aInv * LocalL.x + bInv * LocalL.z,
|
|
aInv * LocalL.y,
|
|
LocalL.z);
|
|
|
|
const float InvLenWrappedLocalL2 = rcp(length2(WrappedLocalL));
|
|
// Jabobian is defined as: det(InvLTC) / ||InvLTC . L||
|
|
// Jacobian should be aInv^2/Length^3 but since we skip the normalization
|
|
// we end up with aInv^2/Length^4 which is cheaper to compute
|
|
const float Jacobian = Pow2(aInv * InvLenWrappedLocalL2);
|
|
|
|
|
|
// Evaluation a normalized cos distribution (note: WrappedLocalL was not normalized, we folded the extra length division into the Jacobian)
|
|
const float CosDistribution = WrappedLocalL.z / PI;
|
|
const float Out = CosDistribution * Jacobian;
|
|
|
|
return max(Out, 0.f);
|
|
}
|
|
|
|
//---------------
|
|
// EnvBRDF
|
|
//---------------
|
|
void ModifyGGXAnisotropicNormalRoughness(float3 WorldTangent, float Anisotropy, inout float Roughness, inout float3 N, float3 V)
|
|
{
|
|
if (abs(Anisotropy) > 0.0f)
|
|
{
|
|
float3 X = WorldTangent;
|
|
float3 Y = normalize(cross(N, X));
|
|
|
|
float3 AnisotropicDir = Anisotropy >= 0.0f ? Y : X;
|
|
float3 AnisotropicT = cross(AnisotropicDir, V);
|
|
float3 AnisotropicN = cross(AnisotropicT, AnisotropicDir);
|
|
|
|
float AnisotropicStretch = abs(Anisotropy) * saturate(5.0f * Roughness);
|
|
N = normalize(lerp(N, AnisotropicN, AnisotropicStretch));
|
|
#if 0
|
|
Roughness *= saturate(1.2f - abs(Anisotropy));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Convert a roughness and an anisotropy factor into GGX alpha values respectively for the major and minor axis of the tangent frame
|
|
void GetAnisotropicRoughness(float Alpha, float Anisotropy, out float ax, out float ay)
|
|
{
|
|
#if 1
|
|
// Anisotropic parameters: ax and ay are the roughness along the tangent and bitangent
|
|
// Kulla 2017, "Revisiting Physically Based Shading at Imageworks"
|
|
ax = max(Alpha * (1.0 + Anisotropy), 0.001f);
|
|
ay = max(Alpha * (1.0 - Anisotropy), 0.001f);
|
|
#else
|
|
float K = sqrt(1.0f - 0.95f * Anisotropy);
|
|
ax = max(Alpha / K, 0.001f);
|
|
ay = max(Alpha * K, 0.001f);
|
|
#endif
|
|
}
|
|
|
|
// Convert a roughness and an anisotropy factor, into two roughnesses respectively for the major and minor axis of the tangent frame
|
|
float2 GetAnisotropicRoughness(float Roughness, float Anisotropy)
|
|
{
|
|
// Anisotropic parameters: ax and ay are the roughness along the tangent and bitangent
|
|
// Kulla 2017, "Revisiting Physically Based Shading at Imageworks"
|
|
float2 Out = saturate(Roughness);
|
|
Anisotropy = clamp(Anisotropy, -1.0, 1.0);
|
|
Out.x = max(Roughness * sqrt(1.0 + Anisotropy), 0.001f);
|
|
Out.y = max(Roughness * sqrt(1.0 - Anisotropy), 0.001f);
|
|
return Out;
|
|
}
|
|
|
|
// Return anisotropy factor (+1: Tangent is major axis, -1: Bitangent is the major axis), and the original roughness
|
|
void GetAnisotropicFactor(float RoughnessX, float RoughnessY, inout float Anisotropy, inout float OriginalRoughness)
|
|
{
|
|
const float MinRoughness = 0.001f;
|
|
float r = Pow2(max(RoughnessX, MinRoughness) / max(RoughnessY, MinRoughness));
|
|
Anisotropy = (r - 1.0) / (r + 1.0);
|
|
OriginalRoughness = (RoughnessX + RoughnessY) / (sqrt(1.0 + Anisotropy) + sqrt(1.0 - Anisotropy));
|
|
}
|
|
|
|
#ifndef PreIntegratedGF
|
|
Texture2D PreIntegratedGF;
|
|
SamplerState PreIntegratedGFSampler;
|
|
#endif
|
|
|
|
// [Karis 2013, "Real Shading in Unreal Engine 4" slide 11]
|
|
half3 EnvBRDF( half3 SpecularColor, half Roughness, half NoV )
|
|
{
|
|
// Importance sampled preintegrated G * F
|
|
float2 AB = Texture2DSampleLevel( PreIntegratedGF, PreIntegratedGFSampler, float2( NoV, Roughness ), 0 ).rg;
|
|
|
|
// Anything less than 2% is physically impossible and is instead considered to be shadowing
|
|
float3 GF = SpecularColor * AB.x + saturate( 50.0 * SpecularColor.g ) * AB.y;
|
|
return GF;
|
|
}
|
|
|
|
half3 EnvBRDF(half3 F0, half3 F90, half Roughness, half NoV)
|
|
{
|
|
// Importance sampled preintegrated G * F
|
|
float2 AB = Texture2DSampleLevel(PreIntegratedGF, PreIntegratedGFSampler, float2(NoV, Roughness), 0).rg;
|
|
float3 GF = F0 * AB.x + F90 * AB.y;
|
|
return GF;
|
|
}
|
|
|
|
half2 EnvBRDFApproxLazarov(half Roughness, half NoV)
|
|
{
|
|
// [ Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II" ]
|
|
// Adaptation to fit our G term.
|
|
const half4 c0 = { -1, -0.0275, -0.572, 0.022 };
|
|
const half4 c1 = { 1, 0.0425, 1.04, -0.04 };
|
|
half4 r = Roughness * c0 + c1;
|
|
half a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
|
|
half2 AB = half2(-1.04, 1.04) * a004 + r.zw;
|
|
return AB;
|
|
}
|
|
|
|
half3 EnvBRDFApprox( half3 SpecularColor, half Roughness, half NoV )
|
|
{
|
|
half2 AB = EnvBRDFApproxLazarov(Roughness, NoV);
|
|
|
|
// Anything less than 2% is physically impossible and is instead considered to be shadowing
|
|
// Note: this is needed for the 'specular' show flag to work, since it uses a SpecularColor of 0
|
|
float F90 = saturate( 50.0 * SpecularColor.g );
|
|
|
|
return SpecularColor * AB.x + F90 * AB.y;
|
|
}
|
|
|
|
half3 EnvBRDFApprox(half3 F0, half3 F90, half Roughness, half NoV)
|
|
{
|
|
half2 AB = EnvBRDFApproxLazarov(Roughness, NoV);
|
|
return F0 * AB.x + F90 * AB.y;
|
|
}
|
|
|
|
half EnvBRDFApproxNonmetal( half Roughness, half NoV )
|
|
{
|
|
// Same as EnvBRDFApprox( 0.04, Roughness, NoV )
|
|
const half2 c0 = { -1, -0.0275 };
|
|
const half2 c1 = { 1, 0.0425 };
|
|
half2 r = Roughness * c0 + c1;
|
|
return min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
|
|
}
|
|
|
|
void EnvBRDFApproxFullyRough(inout half3 DiffuseColor, inout half3 SpecularColor)
|
|
{
|
|
// Factors derived from EnvBRDFApprox( SpecularColor, 1, 1 ) == SpecularColor * 0.4524 - 0.0024
|
|
DiffuseColor += SpecularColor * 0.45;
|
|
SpecularColor = 0;
|
|
// We do not modify Roughness here as this is done differently at different places.
|
|
}
|
|
void EnvBRDFApproxFullyRough(inout half3 DiffuseColor, inout half SpecularColor)
|
|
{
|
|
DiffuseColor += SpecularColor * 0.45;
|
|
SpecularColor = 0;
|
|
}
|
|
void EnvBRDFApproxFullyRough(inout half3 DiffuseColor, inout half3 F0, inout half3 F90)
|
|
{
|
|
DiffuseColor += F0 * 0.45;
|
|
F0 = F90 = 0;
|
|
}
|
|
|
|
|
|
float D_InvBlinn( float a2, float NoH )
|
|
{
|
|
float A = 4;
|
|
float Cos2h = NoH * NoH;
|
|
float Sin2h = 1 - Cos2h;
|
|
//return rcp( PI * (1 + A*m2) ) * ( 1 + A * ClampedPow( Sin2h, 1 / m2 - 1 ) );
|
|
return rcp( PI * (1 + A*a2) ) * ( 1 + A * exp( -Cos2h / a2 ) );
|
|
}
|
|
|
|
float D_InvBeckmann( float a2, float NoH )
|
|
{
|
|
float A = 4;
|
|
float Cos2h = NoH * NoH;
|
|
float Sin2h = 1 - Cos2h;
|
|
float Sin4h = Sin2h * Sin2h;
|
|
return rcp( PI * (1 + A*a2) * Sin4h ) * ( Sin4h + A * exp( -Cos2h / (a2 * Sin2h) ) );
|
|
}
|
|
|
|
float D_InvGGX( float a2, float NoH )
|
|
{
|
|
float A = 4;
|
|
float d = ( NoH - a2 * NoH ) * NoH + a2;
|
|
return rcp( PI * (1 + A*a2) ) * ( 1 + 4 * a2*a2 / ( d*d ) );
|
|
}
|
|
|
|
float Vis_Cloth( float NoV, float NoL )
|
|
{
|
|
return rcp( 4 * ( NoL + NoV - NoL * NoV ) );
|
|
}
|
|
|
|
float D_Charlie(float Roughness, float NoH)
|
|
{
|
|
float InvR = 1 / Roughness;
|
|
float Cos2H = NoH * NoH;
|
|
float Sin2H = 1 - Cos2H;
|
|
return (2 + InvR) * pow(Sin2H, InvR * 0.5) / (2 * PI);
|
|
}
|
|
|
|
// [Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"]
|
|
float Vis_Charlie_L(float x, float r)
|
|
{
|
|
r = saturate(r);
|
|
r = 1.0 - (1. - r) * (1. - r);
|
|
|
|
float a = lerp(25.3245 , 21.5473 , r);
|
|
float b = lerp( 3.32435, 3.82987, r);
|
|
float c = lerp( 0.16801, 0.19823, r);
|
|
float d = lerp(-1.27393, -1.97760, r);
|
|
float e = lerp(-4.85967, -4.32054, r);
|
|
|
|
return a * rcp( (1 + b * pow(x, c)) + d * x + e);
|
|
}
|
|
float Vis_Charlie(float Roughness, float NoV, float NoL)
|
|
{
|
|
float VisV = NoV < 0.5 ? exp(Vis_Charlie_L(NoV, Roughness)) : exp(2 * Vis_Charlie_L(0.5, Roughness) - Vis_Charlie_L(1 - NoV, Roughness));
|
|
float VisL = NoL < 0.5 ? exp(Vis_Charlie_L(NoL, Roughness)) : exp(2 * Vis_Charlie_L(0.5, Roughness) - Vis_Charlie_L(1 - NoL, Roughness));
|
|
|
|
return rcp(((1 + VisV + VisL) * (4 * NoV * NoL)));
|
|
}
|
|
|
|
float Vis_Ashikhmin(float NoV, float NoL)
|
|
{
|
|
return rcp(4 * (NoL + NoV - NoL * NoV));
|
|
}
|
|
|
|
// Special medium transmittance evaluation for the clear coat BSDF.
|
|
float3 SimpleClearCoatTransmittance(float NoL, float NoV, float Metallic, float3 BaseColor)
|
|
{
|
|
float3 Transmittance = 1.0;
|
|
|
|
float ClearCoatCoverage = Metallic;
|
|
if (ClearCoatCoverage > 0.0)
|
|
{
|
|
float LayerThickness = 1.0; // Assume a normalized thickness
|
|
|
|
// The path length inside the medium is from light to bottom surface, then back from bottom surface to the viewer.
|
|
// This assuming an infinite and flat medium slab.
|
|
float ThinDistance = LayerThickness * (rcp(NoV) + rcp(NoL));
|
|
|
|
// BaseColor represents reflected color viewed at 0 incidence angle for the view and light direction...
|
|
float3 TransmittanceColor = Diffuse_Lambert(BaseColor);
|
|
// ... and after being affected by the substrate transmittance when traveling through it, back and forth.
|
|
// Because of this, extinction is normalized by traveling through layer thickness twice
|
|
float3 ExtinctionCoefficient = -log(max(TransmittanceColor,0.0001)) / (2.0 * LayerThickness);
|
|
|
|
// Optical depth is evaluated for ThinDistance computed above. But we also subtract 2.0 * LayerThickness
|
|
// in order to respect the fact that BaseColor represents reflected color viewed at 0 incidence angle. => In this case, OpticalDepth will be 0 and thus Transmittance=1.
|
|
float3 OpticalDepth = ExtinctionCoefficient * max(ThinDistance - 2.0 * LayerThickness, 0.0);
|
|
|
|
// Compute transmittance as a fonction of OpticalDepth
|
|
Transmittance = exp(-OpticalDepth);
|
|
// And as a fucntion of the ClearCoatCoverage.
|
|
Transmittance = lerp(1.0, Transmittance, ClearCoatCoverage);
|
|
}
|
|
|
|
// Note: this is a really special way to handle clear coat transmittance.
|
|
// - The basecolor of the bottom layer is used as the transmittance color.
|
|
// - So basically, a colored metal as bottom layer will look even more saturated at grazing angle.
|
|
// - Transmittance is only computed for metals, for some reason plastic do not receive transmittance.
|
|
|
|
return Transmittance;
|
|
}
|
|
|
|
// Conversion of GGX roughness to Beckmann roughness
|
|
// Using Microfacet Distribution Function: To Change or Not to Change, That Is the Question.
|
|
float ConvertRoughnessGGXToBeckmann(float Roughness)
|
|
{
|
|
// Use a custom remapping to better match the overall highlight shape, in particular for lower roughness (i.e., <0.5).
|
|
// The original mapping was Ctr=Roughness based on the paper
|
|
const float Ctr = pow(Roughness, 0.7f);
|
|
return saturate(0.8388 * Ctr+ 0.7 * Pow4(Ctr));
|
|
}
|
|
float2 ConvertRoughnessGGXToBeckmann(float2 Roughness)
|
|
{
|
|
return float2(ConvertRoughnessGGXToBeckmann(Roughness.x), ConvertRoughnessGGXToBeckmann(Roughness.y));
|
|
}
|
|
|
|
struct FBeckmannDesc
|
|
{
|
|
float2 Roughness; // Roughness
|
|
float2 Alpha; // Roughness squared
|
|
float2 Sigma; // RMS slope of the microfacets
|
|
float Rho; // Slope correlation factor to be used with LEAN mapping. See http://igg.unistra.fr/People/chermain/assets/pdf/Chermain2021RealTime.pdf.
|
|
};
|
|
FBeckmannDesc GGXToBeckmann(float2 GGXSafeRoughness)
|
|
{
|
|
const float InvSqrt2 = 0.707106f;
|
|
|
|
FBeckmannDesc Out;
|
|
Out.Roughness = ConvertRoughnessGGXToBeckmann(GGXSafeRoughness);
|
|
Out.Alpha = Square(Out.Roughness);
|
|
Out.Sigma = Out.Alpha * InvSqrt2;
|
|
Out.Rho = 0.0f;
|
|
return Out;
|
|
} |