325 lines
11 KiB
HLSL
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
|
|
}; |