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

325 lines
11 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=========================================================================
BurleyNormalizedSSSCommon.ush: Burley common functions.
=========================================================================*/
#pragma once
#include "SubsurfaceProfileCommon.ush"
inline float3 Burley_Profile(float Radius, float3 Albedo, float3 S3D, float L)
{ //R(r)r
float3 D = 1 / S3D;
float R = Radius / L;
const float Inv8Pi = 1.0 / (8 * PI);
float3 NegRbyD = -R / D;
return Albedo * max((exp(NegRbyD) + exp(NegRbyD / 3.0)) / (D*L)*Inv8Pi, 0);
}
//Diffuse profile basic formula
// D : shapes the height and width of the profile curve.
// Radius : the distance between the entering surface point and the exit surface point.
//Assume: r and d >0
float GetDiffuseReflectProfile(float D, float Radius)
{
//The diffuse reflectance profile:
//R(d,r) = \frac{e^{-r/d}+e^{-r/(3d)}}{8*pi*d*r}
const float Inv8Pi = 1.0 / (8 * PI);
float NegRbyD = -Radius / D;
return (exp(NegRbyD) + exp(NegRbyD / 3.0)) / (D*Radius)*Inv8Pi;
}
float3 GetDiffuseReflectProfileWithDiffuseMeanFreePath(float3 L, float3 S3D, float Radius)
{
//rR(r)
float3 D = 1 / S3D;
float3 R = Radius / L;
const float Inv8Pi = 1.0 / (8 * PI);
float3 NegRbyD = -R / D;
return max((exp(NegRbyD) + exp(NegRbyD / 3.0)) / (D * L) * Inv8Pi, 0.000000000001f /*Fix color shift due to precision issue for substrate per-pixel MFP SSS*/);
}
float GetComponentForScalingFactorEstimation(float4 SurfaceAlbedo)
{
return SurfaceAlbedo.a;
}
float GetDiffuseMeanFreePathForSampling(float4 DiffuseMeanFreePath)
{
return DiffuseMeanFreePath.a;
}
//-------------------------------------------------------------------------
// Three scaling factor function
// Method 1: The light directly goes into the volume in a direction perpendicular to the surface.
// Average relative error: 5.5% (reference to MC)
float GetPerpendicularScalingFactor(float SurfaceAlbedo)
{
// from the paper, the formula explicitly has an abs for (A-0.8)
float Value = abs(SurfaceAlbedo - 0.8);
return 1.85 - SurfaceAlbedo + 7 * Value * Value * Value;
}
float3 GetPerpendicularScalingFactor3D(float3 SurfaceAlbedo)
{
float3 Value = abs(SurfaceAlbedo - 0.8);
return 1.85 - SurfaceAlbedo + 7 * Value * Value * Value;
}
// Method 2: Ideal diffuse transmission at the surface. More appropriate for rough surface.
// Average relative error: 3.9% (reference to MC)
float GetDiffuseSurfaceScalingFactor(float SurfaceAlbedo)
{
float Value = SurfaceAlbedo - 0.8;
return 1.9 - SurfaceAlbedo + 3.5 * Value * Value;
}
float3 GetDiffuseSurfaceScalingFactor3D(float3 SurfaceAlbedo)
{
float3 Value = SurfaceAlbedo - 0.8;
return 1.9 - SurfaceAlbedo + 3.5 * Value * Value;
}
// Method 3: The spectral of diffuse mean free path on the surface.
// Avergate relative error: 7.7% (reference to MC)
float GetSearchLightDiffuseScalingFactor(float SurfaceAlbedo)
{
float Value = SurfaceAlbedo - 0.33;
return 3.5 + 100 * Value * Value * Value * Value;
}
float3 GetSearchLightDiffuseScalingFactor3D(float3 SurfaceAlbedo)
{
float3 Value = SurfaceAlbedo - 0.33;
return 3.5 + 100 * Value * Value * Value * Value;
}
// TODO: find the source of this magic number to convert dmfp to mfp to match path tracer and rasterizer.
// DMFP/SearchLightDiffuseScalingFactor goes to the normalized distance(d=l/s), then multiply the PerpendicularScalingFactor to get the mfp
// float Dmfp2MfpMagicNumber = 0.6f;
// Mathmatically matching based on diffusion coefficient instead of burley's approximation. However, it leads to incorrect result as
// we use burley's approximation (IOR=1.0) for screenspace diffuse scattering.
//float3 Alpha = 1 - exp(-11.43 * SurfaceAlbedo + 15.38 * SurfaceAlbedo * SurfaceAlbedo - 13.91 * SurfaceAlbedo * SurfaceAlbedo * SurfaceAlbedo);
//return DMFP * sqrt(3 * (1 - Alpha) / (2 - Alpha));
float3 GetMFPFromDMFPCoeff(float3 DMFPSurfaceAlbedo, float3 MFPSurfaceAlbedo, float Dmfp2MfpMagicNumber = 0.6f)
{
return Dmfp2MfpMagicNumber * GetPerpendicularScalingFactor3D(MFPSurfaceAlbedo) / GetSearchLightDiffuseScalingFactor3D(DMFPSurfaceAlbedo);
}
float3 GetMFPFromDMFPApprox(float3 SurfaceAlbedo, float3 TargetSurfaceAlbedo, float3 DMFP)
{
return GetMFPFromDMFPCoeff(SurfaceAlbedo, TargetSurfaceAlbedo) * DMFP;
}
float3 GetDMFPFromMFPApprox(float3 SurfaceAlbedo, float3 MFP)
{
float3 MFPFromDMFPCoeff = GetMFPFromDMFPCoeff(SurfaceAlbedo, SurfaceAlbedo);
return MFP / MFPFromDMFPCoeff;
}
// With world unit scale
float4 GetSubsurfaceProfileMFPInCm(int SubsurfaceProfileInt)
{
float4 DMFP = GetSubsurfaceProfileDMFPInCm(SubsurfaceProfileInt);
float4 SurfaceAlbedo = GetSubsurfaceProfileSurfaceAlbedo(SubsurfaceProfileInt);
return float4(GetMFPFromDMFPApprox(SurfaceAlbedo.xyz, SurfaceAlbedo.xyz, DMFP.xyz),0.0f);
}
float GetScalingFactor(float A)
{
#if LIGHT_PERPENDICULAR
float S = GetPerpendicularScalingFactor(A);
#elif (LIGHT_DIFFUSESURFACE)
float S = GetDiffuseSurfaceScalingFactor(A);
#elif (LIGHT_PERPENDICULAR_DIFFUSE_SURFACE)
float S = GetSearchLightDiffuseScalingFactor(A);
#endif
return S;
}
float3 GetScalingFactor3D(float3 SurfaceAlbedo)
{
#if LIGHT_PERPENDICULAR
float3 S3D = GetPerpendicularScalingFactor3D(SurfaceAlbedo);
#elif (LIGHT_DIFFUSESURFACE)
float3 S3D = GetDiffuseSurfaceScalingFactor3D(SurfaceAlbedo);
#elif (LIGHT_PERPENDICULAR_DIFFUSE_SURFACE)
float3 S3D = GetSearchLightDiffuseScalingFactor3D(SurfaceAlbedo);
#endif
return S3D;
}
float3 GetCDF3D(float3 D, float X)
{
return 1 - 0.25 * exp(-X / D) - 0.75 * exp(-X / (3 * D));
}
float GetCDF(float D, float X, float XI)
{
return 1 - 0.25*exp(-X / D) - 0.75*exp(-X / (3 * D)) - XI;
}
float GetCDFDeriv1(float D, float X)
{
return 0.25 / D * (exp(-X / D) + exp(-X / (3 * D)));
}
float GetCDFDeriv1InversD(float InvD, float X)
{
return 0.25 * InvD * (exp(-X*InvD)+exp(-3*X*InvD));
}
float GetCDFDeriv2(float D, float X)
{
return exp(-X / D)*(-0.0833333*exp(2 * X / (3 * D)) - 0.25) / (D*D);
}
float RadiusRootFindAnalytic(float D, float RandomNumber)
{
// solve for:
// F(X) = .25*exp(-X) + .75*exp(-X/3)
// cdf(X) = 1 - F(X/D)
// so to find X s.t.
// cdf(X) = RandomNumber
// we need to solve:
// 1 - RandomNumber = F(X/D)
// Finv(1 - RandomNumber) * D = X
// Finv(X) actually has an analytic solution:
// G = 1 + 4*U*(2*U + sqrt(1+4*U*U));
// Inv3 = 1.0 / 3.0;
// X/D = 3 * log((1+pow(G,-Inv3)+pow(G,Inv3))/(4*U));
float U = 1 - RandomNumber;
float G = 1.0f + 4*U*(2*U + sqrt(1+4*U*U));
float Inv3 = 1.0f / 3.0f;
float X = D * (3 * log( (1+pow(G,-Inv3)+pow(G,Inv3))/(4*U)));
return X;
}
// Without clamp, the result will be zero which will lead to infinity for R/pdf. and Nan for the weighted sum.
// We need to clamp pdf to a very small value. this clamp is based on value below
#define CLAMP_PDF 0.00001
// Given D and a random number, use root finding method to calculate the radius
// in meters
float RadiusRootFinding(float D, float RandomNumber, float X0)
{
// Make the distribution correct.
// r=cdf^(-1)(p) if p<1-CLAMP_PDF
// =cdf^(-1)(1-ClAMP_PDF) P>=1-CLAMP_PDF
// RandomNumber = clamp(RandomNumber,0,1-CLAMP_PDF);
const int NumOfIteration = 10;
float Xn = X0;
UNROLL for (int i = 0; i < NumOfIteration; ++i)
{
float Fx = GetCDF(D, Xn, RandomNumber);
float DFx = GetCDFDeriv1(D, Xn);
float DFxx = GetCDFDeriv2(D, Xn);
Xn = Xn - 2 * Fx*DFx / (2 * DFx*DFx - Fx * DFxx);
}
return Xn;
}
// Brian's approximation.
float RadiusRootFindByApproximation(float D, float RandomNumber)
{
return D * ((2 - 2.6)*RandomNumber - 2)*log(1 - RandomNumber);
}
// Get the probability to sample a disk.
float GetPdf(float Radius, float L, float S)
{
//without clamp, the result will be zero which will lead to infinity for R/pdf. and Nan for the weighted sum.
//we need to clamp this to a very small pdf.
float Pdf = GetCDFDeriv1(L / S, Radius);
return max(Pdf, CLAMP_PDF);
}
float GetPdfInvD(float Radius, float InvD)
{
float Pdf = GetCDFDeriv1InversD(InvD,Radius);
return max(Pdf, CLAMP_PDF);
}
//@TODO Revisit here after offline comparison.
//call this function in TransmissionCommon.ush for Debug. No transmission tint is applied.
// This code is not used
#if 0
float4 GetBurleyTransmissionProfile(FGBufferData GBufferData, float Thickness)
{
// 0..255, which SubSurface profile to pick
uint SubsurfaceProfileInt = ExtractSubsurfaceProfileInt(GBufferData);
float WorldUnitScale = DecodeWorldUnitScale(View.SSProfilesTexture.Load(int3(SSSS_TINT_SCALE_OFFSET, SubsurfaceProfileInt,0)).a)*10.0f; //in cm. we do not multiply by 100.0f as we did in the subsurface shader.
float4 MeanFreePath = DecodeDiffuseMeanFreePath(View.SSProfilesTexture.Load(int3(BSSS_DMFP_OFFSET, SubsurfaceProfileInt, 0)));
float3 SurfaceAlbedo = View.SSProfilesTexture.Load(int3(BSSS_SURFACEALBEDO_OFFSET, SubsurfaceProfileInt, 0)).rgb;
float3 ScalingFactor = GetSearchLightDiffuseScalingFactor3D(SurfaceAlbedo);//assuming that the volume albedo is the same to the surface albedo for transmission;
float4 Output=float4(0,0,0,1);
float3 r = Thickness / MeanFreePath.xyz;
Output.xyz= 0.25*SurfaceAlbedo*(exp(-ScalingFactor * r) + 3 * exp(-ScalingFactor * r / 3));
return Output;
}
#endif
float3 InternalGetBurleyTransmissionProfile(
float3 SubsurfaceAlebdo,
float3 MeanFreePathInCm,
float ThicknessInCm)
{
// Assuming that the volume albedo is the same to the surface albedo for transmission;
const float3 ScalingFactor = GetPerpendicularScalingFactor3D(SubsurfaceAlebdo);
const float3 r = ThicknessInCm / MeanFreePathInCm.xyz;
const float3 Output = 0.25 * SubsurfaceAlebdo * (exp(-ScalingFactor * r) + 3 * exp(-ScalingFactor * r / 3));
return Output;
}
float3 GetBurleyTransmission(
float3 SubsurfaceAlebdo,
float3 MeanFreePathInCm,
float ThicknessInCm)
{
const float TransmissionMFPScaleFactor = 100.f; // This factor compensate a mistake in SSS profile transmission computation. See BurleyNormalizedSSS.cpp for more details
return InternalGetBurleyTransmissionProfile(SubsurfaceAlebdo, MeanFreePathInCm * TransmissionMFPScaleFactor, ThicknessInCm);
}
float3 GetBurleyTransmissionProfile(
float3 SubsurfaceAlebdo,
float3 MeanFreePathInCm,
float ThicknessInCm)
{
const float TransmissionMFPScaleFactor = 100.f; // This factor compensate a mistake in SSS profile transmission computation. See BurleyNormalizedSSS.cpp for more details
const float3 TransmissionThroughput = InternalGetBurleyTransmissionProfile(SubsurfaceAlebdo, MeanFreePathInCm * TransmissionMFPScaleFactor, ThicknessInCm).xyz;
// Replicate SSS profile distance fade out, by fading the tail of the curve to black.
// * SSS Profile stores transmittance into a discrete set of samples: BSSS_TRANSMISSION_PROFILE_SIZE(=32)
// * The transmission distance is set to a max of SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE(=5) (in scaled cm, depend on the WorldToUnitScale, cm by default)
//
// This code fades the last value to black.
// FadeDistance = Distance * SampleCount - MaxDistance * (SampleCount-0.5f)
// FadeDistance = Distance * 32.f - 5.f * 31.5f
//
// Transmission
// 1 ___________
// \
// 0 \ _______
// ----------------------->
// .. 29 30 31 32 Sample Index / Distance
const float FadeDistance = 1.0f - saturate(ThicknessInCm * BSSS_TRANSMISSION_PROFILE_SIZE - SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE * (BSSS_TRANSMISSION_PROFILE_SIZE - 0.5f));
return TransmissionThroughput * FadeDistance;
}
struct FBurleyParameter
{
float4 SurfaceAlbedo;
float4 DiffuseMeanFreePath;
float WorldUnitScale;
float SurfaceOpacity; // 1.0 means full Burley, 0.0 means full Default Lit
};