Files
UnrealEngine/Engine/Shaders/Private/ShadingModels.ush
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1112 lines
41 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "DeferredShadingCommon.ush"
#include "BRDF.ush"
#include "FastMath.ush"
#include "CapsuleLight.ush"
#include "RectLight.ush"
#include "AreaLightCommon.ush"
#include "TransmissionCommon.ush"
#include "HairBsdf.ush"
#include "ShadingEnergyConservation.ush"
#include "ParticipatingMediaCommon.ush"
#include "ColorSpace.ush"
#include "SGGX.ush"
#include "PathTracing/Utilities/PathTracingRandomSequence.ush"
#ifndef HAIR_BSDF_BACKLIT
#define HAIR_BSDF_BACKLIT 1
#endif
#if SHADING_PATH_MOBILE
#include "MobileGGX.ush"
#ifndef MOBILE_SHADOW_QUALITY
#define MOBILE_SHADOW_QUALITY 2
#endif
//It's always 0 in mobile deferred lighting shaders
#ifndef FULLY_ROUGH
#define FULLY_ROUGH 0
#endif
//It's always 0 in mobile deferred lighting shaders
#ifndef NONMETAL
#define NONMETAL 0
#endif
//It's always 0 in mobile deferred lighting shaders
#ifndef MATERIAL_SHADINGMODEL_SINGLELAYERWATER
#define MATERIAL_SHADINGMODEL_SINGLELAYERWATER 0
#endif
// Always enabled in mobile deferred lighting shaders
#ifndef MOBILE_USE_PREINTEGRATED_GF
#define MOBILE_USE_PREINTEGRATED_GF 1
#endif
//It's always 1 in mobile deferred lighting shaders
#ifndef MOBILE_DEFERRED_LIGHTING
#define MOBILE_DEFERRED_LIGHTING 0
#endif
#ifndef DEFERRED_SHADING_PATH
#define DEFERRED_SHADING_PATH 0
#endif
/*------------------------------------------------------------------------------
Mobile Shading Model Functions
------------------------------------------------------------------------------*/
half4 GetSSProfilePreIntegratedValue(uint SubsurfaceProfileInt, half NoL, half Curvature)
{
float3 UV = float3((NoL * .5 + .5), Curvature, SubsurfaceProfileInt);
return Texture2DArraySampleLevel(View.SSProfilesPreIntegratedTexture, View.SSProfilesPreIntegratedSampler, UV, 0);
}
half3 GetEnvBRDF(half3 SpecularColor, half Roughness, half NoV)
{
#if FULLY_ROUGH
return 0.0f;
#elif MOBILE_USE_PREINTEGRATED_GF
return EnvBRDF(SpecularColor, Roughness, NoV);
#elif NONMETAL
// If nothing is hooked up to Metalic and Specular,
// then defaults are the same as a non-metal,
// so this define is safe.
return EnvBRDFApproxNonmetal(Roughness, NoV).xxx;
#else
return EnvBRDFApprox(SpecularColor, Roughness, NoV);
#endif
}
#endif // SHADING_PATH_MOBILE
struct FDirectLighting
{
float3 Diffuse;
float3 Specular;
float3 Transmission;
};
struct FShadowTerms
{
half SurfaceShadow;
half TransmissionShadow;
half TransmissionThickness;
FHairTransmittanceData HairTransmittance;
};
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting HairBxDF(FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow)
{
const float3 BsdfValue = HairShading(GBuffer, AreaLight.DiffuseL, V, N, Shadow.TransmissionShadow, Shadow.HairTransmittance, HAIR_BSDF_BACKLIT, 0, uint2(0, 0));
FDirectLighting Lighting;
Lighting.Diffuse = 0;
Lighting.Specular = 0;
Lighting.Transmission = AreaLight.FalloffColor * AreaLight.Falloff * BsdfValue;
return Lighting;
}
float New_a2( float a2, float SinAlpha, float VoH )
{
return a2 + 0.25 * SinAlpha * (3.0 * sqrtFast(a2) + SinAlpha) / ( VoH + 0.001 );
//return a2 + 0.25 * SinAlpha * ( saturate(12 * a2 + 0.125) + SinAlpha ) / ( VoH + 0.001 );
//return a2 + 0.25 * SinAlpha * ( a2 * 2 + 1 + SinAlpha ) / ( VoH + 0.001 );
}
float EnergyNormalization( inout float a2, float VoH, FAreaLight AreaLight )
{
if( AreaLight.SphereSinAlphaSoft > 0 )
{
// Modify Roughness
a2 = saturate( a2 + Pow2( AreaLight.SphereSinAlphaSoft ) / ( VoH * 3.6 + 0.4 ) );
}
float Sphere_a2 = a2;
float Energy = 1;
if( AreaLight.SphereSinAlpha > 0 )
{
Sphere_a2 = New_a2( a2, AreaLight.SphereSinAlpha, VoH );
Energy = a2 / Sphere_a2;
}
if( AreaLight.LineCosSubtended < 1 )
{
#if 1
float LineCosTwoAlpha = AreaLight.LineCosSubtended;
float LineTanAlpha = sqrt( ( 1.0001 - LineCosTwoAlpha ) / ( 1 + LineCosTwoAlpha ) );
float Line_a2 = New_a2( Sphere_a2, LineTanAlpha, VoH );
Energy *= sqrt( Sphere_a2 / max(Line_a2, 1e-5) );
#else
float LineCosTwoAlpha = AreaLight.LineCosSubtended;
float LineSinAlpha = sqrt( 0.5 - 0.5 * LineCosTwoAlpha );
float Line_a2 = New_a2( Sphere_a2, LineSinAlpha, VoH );
Energy *= Sphere_a2 / max(Line_a2, 1e-5);
#endif
}
return Energy;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
float3 SpecularGGX(float Roughness, float Anisotropy, float3 SpecularColor, BxDFContext Context, FAreaLight AreaLight)
{
float Alpha = Roughness * Roughness;
float ax = 0;
float ay = 0;
GetAnisotropicRoughness(Alpha, Anisotropy, ax, ay);
// Generalized microfacet specular
float3 D = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH);
float3 Vis = Vis_SmithJointAniso(ax, ay, Context.NoV, AreaLight.NoL, Context.XoV, Context.XoL, Context.YoV, Context.YoL);
float3 F = F_Schlick( SpecularColor, Context.VoH );
return (D * Vis) * F;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
float3 SpecularGGX( float Roughness, float3 SpecularColor, BxDFContext Context, FAreaLight AreaLight )
{
float a2 = Pow4( Roughness );
float Energy = EnergyNormalization( a2, Context.VoH, AreaLight );
// Generalized microfacet specular
float D = D_GGX( a2, Context.NoH ) * Energy;
float Vis = Vis_SmithJointApprox( a2, Context.NoV, AreaLight.NoL );
float3 F = F_Schlick( SpecularColor, Context.VoH );
return (D * Vis) * F;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
half3 DualSpecularGGX(half AverageRoughness, half Lobe0Roughness, half Lobe1Roughness, half LobeMix, half3 SpecularColor, BxDFContext Context, FAreaLight AreaLight)
{
float AverageAlpha2 = Pow4(AverageRoughness);
float Lobe0Alpha2 = Pow4(Lobe0Roughness);
float Lobe1Alpha2 = Pow4(Lobe1Roughness);
float Lobe0Energy = EnergyNormalization(Lobe0Alpha2, Context.VoH, AreaLight);
float Lobe1Energy = EnergyNormalization(Lobe1Alpha2, Context.VoH, AreaLight);
// Generalized microfacet specular
float D = lerp(D_GGX(Lobe0Alpha2, Context.NoH) * Lobe0Energy, D_GGX(Lobe1Alpha2, Context.NoH) * Lobe1Energy, LobeMix);
float Vis = Vis_SmithJointApprox(AverageAlpha2, Context.NoV, AreaLight.NoL); // Average visibility well approximates using two separate ones (one per lobe).
float3 F = F_Schlick(SpecularColor, Context.VoH);
return (D * Vis) * F;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
FDirectLighting Lighting;
Lighting.Diffuse = 0;
Lighting.Specular = 0;
Lighting.Transmission = 0;
BRANCH
if (AreaLight.NoL > 0.0f)
{
#if SUPPORTS_ANISOTROPIC_MATERIALS
bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask);
#else
bool bHasAnisotropy = false;
#endif
float NoV, VoH, NoH;
BRANCH
if (bHasAnisotropy)
{
half3 X = GBuffer.WorldTangent;
half3 Y = normalize(cross(N, X));
Init(Context, N, X, Y, V, AreaLight.SpecularL);
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
}
else
{
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, AreaLight.SpecularL, AreaLight.NoL);
#else
Init(Context, N, V, AreaLight.SpecularL);
#endif
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
}
Context.NoV = saturate(abs( Context.NoV ) + 1e-5);
#if MATERIAL_ROUGHDIFFUSE
// Diffuse model with roughness == specular roughness. This is not necessarily a good model of reality because it assumes microfacets with lambertian response.
// The limit behavior of SSS as the mean free path gets small behaves differently, but this is a start.
// Also we cannot use the morphed context maximising NoH as this is causing visual artefact when interpolating rough/smooth diffuse response. (SUBSTRATE_TODO: Check this again with latest rough diffuse model)
Lighting.Diffuse = Diffuse_GGX_Rough(GBuffer.DiffuseColor, GBuffer.Roughness, NoV, AreaLight.NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
#endif
Lighting.Diffuse *= AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL);
BRANCH
if (bHasAnisotropy)
{
Lighting.Specular = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, AreaLight);
}
else
{
if( IsRectLight(AreaLight) )
{
Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
}
else
{
Lighting.Specular = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, AreaLight);
}
}
FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
// Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component
Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
// Add specular microfacet multiple scattering term (energy-conservation)
Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);
Lighting.Transmission = 0;
}
return Lighting;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
// Simple default lit model used by Lumen
float3 SimpleShading( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 L, float3 V, half3 N )
{
const float NoV = saturate(dot(N, V));
const FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(Roughness, NoV, SpecularColor);
float3 H = normalize(V + L);
float NoH = saturate( dot(N, H) );
// Generalized microfacet specular
float D = D_GGX( Pow4(Roughness), NoH );
float Vis = Vis_Implicit();
float3 F = F_None( SpecularColor );
return
Diffuse_Lambert( DiffuseColor ) * ComputeEnergyPreservation(EnergyTerms) +
(D * Vis) * F * ComputeEnergyConservation(EnergyTerms);
}
float RefractBlend(float VoH, float Eta)
{
// Refraction blend factor for normal component of VoH
float k = 1.0 - Eta * Eta * (1.0 - VoH * VoH);
return Eta * VoH - sqrt(k);
}
half RefractBlendClearCoatApprox(half VoH)
{
// Polynomial approximation of refraction blend factor for normal component of VoH with fixed Eta (1/1.5):
return (0.63 - 0.22 * VoH) * VoH - 0.745;
}
float3 Refract(float3 V, float3 H, float Eta)
{
// Assumes V points away from the point of incidence
float VoH = dot(V, H);
return RefractBlend(VoH, Eta) * H - Eta * V;
}
BxDFContext RefractClearCoatContext(BxDFContext Context)
{
// Reference: Propagation of refraction through dot-product NoV
// Note: This version of Refract requires V to point away from the point of incidence
// NoV2 = -dot(N, Refract(V, H, Eta))
// NoV2 = -dot(N, RefractBlend(VoH, Eta) * H - Eta * V)
// NoV2 = -(RefractBlend(VoH, Eta) * NoH - Eta * NoV)
// NoV2 = Eta * NoV - RefractBlend(VoH, Eta) * NoH
// NoV2 = 1.0 / 1.5 * NoV - RefractBlendClearCoatApprox(VoH) * NoH
BxDFContext RefractedContext = Context;
half Eta = 1.0 / 1.5;
half RefractionBlendFactor = RefractBlendClearCoatApprox(Context.VoH);
half RefractionProjectionTerm = RefractionBlendFactor * Context.NoH;
RefractedContext.NoV = clamp(Eta * Context.NoV - RefractionProjectionTerm, 0.001, 1.0); // Due to CalcThinTransmission and Vis_SmithJointAniso, we need to make sure
RefractedContext.NoL = clamp(Eta * Context.NoL - RefractionProjectionTerm, 0.001, 1.0); // those values are not 0s to avoid NaNs.
RefractedContext.VoH = saturate(Eta * Context.VoH - RefractionBlendFactor);
RefractedContext.VoL = 2.0 * RefractedContext.VoH * RefractedContext.VoH - 1.0;
RefractedContext.NoH = Context.NoH;
return RefractedContext;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
const float ClearCoat = GBuffer.CustomData.x;
const float ClearCoatRoughness = max(GBuffer.CustomData.y, 0.02f);
FDirectLighting Lighting = {
float3(0.0, 0.0, 0.0),
float3(0.0, 0.0, 0.0),
float3(0.0, 0.0, 0.0)
};
BxDFContext Context;
half3 Nspec = N;
if (CLEAR_COAT_BOTTOM_NORMAL)
{
Nspec = GBuffer.WorldNormal;
}
#if SUPPORTS_ANISOTROPIC_MATERIALS
bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask);
#else
bool bHasAnisotropy = false;
#endif
half3 X = 0;
half3 Y = 0;
//////////////////////////////
/// Top Layer
//////////////////////////////
// No anisotropy for the top layer
Init(Context, Nspec, V, AreaLight.SpecularL);
// Modify SphereSinAlpha, knowing that it was previously manipulated by roughness of the under coat
// Note: the operation is not invertible for GBuffer.Roughness = 1.0, so roughness is clamped to 254.0/255.0
float SphereSinAlpha = AreaLight.SphereSinAlpha;
float RoughnessCompensation = 1 - Pow2(GBuffer.Roughness);
half Alpha = ClearCoatRoughness * ClearCoatRoughness;
RoughnessCompensation = RoughnessCompensation > 0.0 ? (1 - Alpha) / RoughnessCompensation : 0.0;
AreaLight.SphereSinAlpha = saturate(AreaLight.SphereSinAlpha * RoughnessCompensation);
SphereMaxNoH(Context, AreaLight.SphereSinAlpha, CLEAR_COAT_BOTTOM_NORMAL == 0);
Context.NoV = saturate(abs(Context.NoV) + 1e-5);
const bool bIsRect = IsRectLight(AreaLight);
Context.VoH = bIsRect ? Context.NoV : Context.VoH;
// Hard-coded Fresnel evaluation with IOR = 1.5 (for polyurethane cited by Disney BRDF)
float F0 = 0.04;
float Fc = Pow5(1 - Context.VoH);
float F = Fc + (1 - Fc) * F0;
FBxDFEnergyTerms EnergyTermsCoat = ComputeGGXSpecEnergyTerms(ClearCoatRoughness, Context.NoV, F0);
if (bIsRect)
{
Lighting.Specular = ClearCoat * RectGGXApproxLTC(ClearCoatRoughness, F0, Nspec, V, AreaLight.Rect, AreaLight.Texture);
}
else
{
// Generalized microfacet specular
float a2 = Pow2(Alpha);
float ClearCoatEnergy = EnergyNormalization(a2, Context.VoH, AreaLight);
half Vis = Vis_SmithJointApprox(a2, Context.NoV, AreaLight.NoL);
float D = D_GGX(a2, Context.NoH) * ClearCoatEnergy;
half Fr1 = (D * Vis) * F;
Lighting.Specular = ClearCoat * AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL * Fr1);
}
Lighting.Specular *= ComputeEnergyConservation(EnergyTermsCoat);
// Restore previously changed SphereSinAlpha for the top layer.
// Alpha needs to also be restored to the bottom layer roughness.
AreaLight.SphereSinAlpha = SphereSinAlpha;
Alpha = Pow2(GBuffer.Roughness);
// Incoming and exiting Fresnel terms are identical to incoming Fresnel term (VoH == HoL)
// float FresnelCoeff = (1.0 - F1) * (1.0 - F2);
#if USE_ENERGY_CONSERVATION
float3 FresnelCoeff = ComputeEnergyPreservation(EnergyTermsCoat); // 1.0 - F;
#else
// Preserve old behavior when energy conservation is disabled
half FresnelCoeff = 1.0 - F;
#endif
FresnelCoeff *= FresnelCoeff;
//////////////////////////////
/// Bottom Layer
//////////////////////////////
if (CLEAR_COAT_BOTTOM_NORMAL)
{
BxDFContext TempContext;
BRANCH
if (bHasAnisotropy)
{
Init(TempContext, N, X, Y, V, AreaLight.SpecularL);
}
else
{
Init(TempContext, Nspec, V, AreaLight.SpecularL);
}
// If bottom-normal, update normal-based dot products:
float3 H = normalize(V + AreaLight.SpecularL);
Context.NoH = saturate(dot(N, H));
Context.NoV = saturate(dot(N, V));
Context.NoL = saturate(dot(N, AreaLight.SpecularL));
Context.VoL = saturate(dot(V, AreaLight.SpecularL));
Context.VoH = saturate(dot(V, H));
Context.XoV = TempContext.XoV;
Context.XoL = TempContext.XoL;
Context.XoH = TempContext.XoH;
Context.YoV = TempContext.YoV;
Context.YoL = TempContext.YoL;
Context.YoH = TempContext.YoH;
if (!bHasAnisotropy)
{
bool bNewtonIteration = true;
SphereMaxNoH(Context, AreaLight.SphereSinAlpha, bNewtonIteration);
}
Context.NoV = saturate(abs(Context.NoV) + 1e-5);
}
// Propagate refraction through dot-products rather than the original vectors:
// Reference:
// float Eta = 1.0 / 1.5;
// float3 H = normalize(V + L);
// float3 V2 = Refract(V, H, Eta);
// float3 L2 = reflect(V2, H);
// V2 = -V2;
// BxDFContext BottomContext;
// Init(BottomContext, N, X, Y, V2, L2);
if (bHasAnisotropy)
{
// Prepare the anisotropic Context to refract.
X = GBuffer.WorldTangent;
Y = normalize(cross(N, X));
Init(Context, Nspec, X, Y, V, AreaLight.SpecularL);
}
BxDFContext BottomContext = RefractClearCoatContext(Context);
BottomContext.VoH = bIsRect ? BottomContext.NoV : BottomContext.VoH;
FBxDFEnergyTerms EnergyTermsBottom = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, BottomContext.NoV, GBuffer.SpecularColor);
// Absorption
float3 Transmission = SimpleClearCoatTransmittance(BottomContext.NoL, BottomContext.NoV, GBuffer.Metallic, GBuffer.BaseColor);
// Default Lit
half3 DefaultDiffuse = (AreaLight.Falloff * AreaLight.NoL) * AreaLight.FalloffColor * Diffuse_Lambert(GBuffer.DiffuseColor) * ComputeEnergyPreservation(EnergyTermsBottom);
half3 RefractedDiffuse = FresnelCoeff * Transmission * DefaultDiffuse;
Lighting.Diffuse = lerp(DefaultDiffuse, RefractedDiffuse, ClearCoat);
if (!bHasAnisotropy && bIsRect)
{
// Note: V is used instead of V2 because LTC integration is not tuned to handle refraction direction
float3 DefaultSpecular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
float3 RefractedSpecular = FresnelCoeff * Transmission * DefaultSpecular;
Lighting.Specular += lerp(DefaultSpecular, RefractedSpecular, ClearCoat);
}
else
{
float a2 = Pow4(GBuffer.Roughness);
half D2 = 0;
half Vis2 = 0;
// The energy factor for correcting area light shape needs to be computed "before" the actual D_GGX term,
// as calling EnergyNormalization() modify the "a2" value.
float Energy = 1;
BRANCH
if (!bHasAnisotropy)
{
Energy = EnergyNormalization(a2, Context.VoH, AreaLight);
}
BRANCH
if (bHasAnisotropy)
{
float ax = 0;
float ay = 0;
GetAnisotropicRoughness(Alpha, GBuffer.Anisotropy, ax, ay);
D2 = D_GGXaniso(ax, ay, Context.NoH, Context.XoH, Context.YoH);
Vis2 = Vis_SmithJointAniso(ax, ay, BottomContext.NoV, BottomContext.NoL, BottomContext.XoV, BottomContext.XoL, BottomContext.YoV, BottomContext.YoL);
}
else
{
// NoL is chosen to provide better parity with DefaultLit when ClearCoat=0
Vis2 = Vis_SmithJointApprox(a2, BottomContext.NoV, AreaLight.NoL);
D2 = D_GGX(a2, BottomContext.NoH);
}
half3 F_Bot = F_Schlick(GBuffer.SpecularColor, BottomContext.VoH);
half3 F_DefaultLit = F_Schlick(GBuffer.SpecularColor, Context.VoH);
// Note: reusing D and V from refracted context to save computation when ClearCoat < 1
float3 CommonSpecular = (Energy * AreaLight.Falloff * AreaLight.NoL * D2 * Vis2) * AreaLight.FalloffColor;
float3 DefaultSpecular = F_DefaultLit;
float3 RefractedSpecular = FresnelCoeff * Transmission * F_Bot;
Lighting.Specular += CommonSpecular * lerp(DefaultSpecular, RefractedSpecular, ClearCoat);
}
return Lighting;
}
// HG phase function approximated
float ApproximateHG(float cosJ, float g)
{
float g2 = g * g;
float gcos2 = 1.0f - (g * cosJ);
gcos2 *= gcos2;
const float ISO_PHASE_FUNC_Normalized = 0.5;
return (ISO_PHASE_FUNC_Normalized * (1.0f - g2) / max( 1e-5, gcos2));
}
void GetProfileDualSpecular(uint SubsurfaceProfileInt, half Roughness, half Opacity, out half LobeRoughness0, out half LobeRoughness1, out half LobeMix)
{
#if !FORWARD_SHADING
GetSubsurfaceProfileDualSpecular(SubsurfaceProfileInt, Roughness, Opacity, LobeRoughness0, LobeRoughness1, LobeMix);
#else
// Disable dual lobe, as subsurface profile doesn't work with forward.
LobeRoughness0 = Roughness;
LobeRoughness1 = Roughness;
LobeMix = 0.f;
#endif
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting SubsurfaceProfileBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, AreaLight.SpecularL, AreaLight.NoL);
#else
Init( Context, N, V, AreaLight.SpecularL );
#endif
SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true );
Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );
uint SubsurfaceProfileId = ExtractSubsurfaceProfileInt(GBuffer);
half Opacity = GBuffer.CustomData.a;
half Roughness = GBuffer.Roughness;
half Lobe0Roughness = 0;
half Lobe1Roughness = 0;
half LobeMix = 0;
GetProfileDualSpecular(SubsurfaceProfileId, Roughness, Opacity, Lobe0Roughness, Lobe1Roughness, LobeMix);
half AverageRoughness = lerp(Lobe0Roughness, Lobe1Roughness, LobeMix);
// Take the average roughness instead of compute a separate energy term for each roughness
const FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(AverageRoughness, Context.NoV, GBuffer.SpecularColor);
FDirectLighting Lighting;
#if SHADING_PATH_MOBILE
half Curvature = GBuffer.Curvature;
half UnClampedNoL = dot(GBuffer.WorldNormal, AreaLight.DiffuseL);
half ShadowFactor = 1.0f - sqrt(Shadow.SurfaceShadow);
// Rotate the world normal based on the shadow value, it's just a experimental value
half UnClampedRotatedNoL = max(UnClampedNoL - max(2.0f * UnClampedNoL, 0.4f) * ShadowFactor, -1.0f);
half4 BurleyDiffuse = GetSSProfilePreIntegratedValue(SubsurfaceProfileId, UnClampedRotatedNoL, Curvature);
// asset specific color
half3 Tint = GetSubsurfaceProfileTexture(SSSS_TINT_SCALE_OFFSET, SubsurfaceProfileId).rgb;
// Needs to apply shadow at here, since the preintegrated value has already counted it, and we need to skip applying shadow outside.
Lighting.Diffuse = lerp(AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * Shadow.SurfaceShadow, BurleyDiffuse.rgb, Tint) * Diffuse_Lambert(GBuffer.DiffuseColor);
#else
#if MATERIAL_ROUGHDIFFUSE
// Use Chan's diffuse model. It reduces flatness look and has better match, e.g., at the edge of human face skin when compared to GT.
const float3 DiffuseReflection = Diffuse_GGX_Rough(GBuffer.DiffuseColor, GBuffer.Roughness, Context.NoV, AreaLight.NoL, Context.VoH, Context.NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
const float3 DiffuseReflection = Diffuse_Burley(GBuffer.DiffuseColor, GBuffer.Roughness, Context.NoV, AreaLight.NoL, Context.VoH);
#endif
Lighting.Diffuse = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * DiffuseReflection;
#endif
if (IsRectLight(AreaLight))
{
float3 Lobe0Specular = RectGGXApproxLTC(Lobe0Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
float3 Lobe1Specular = RectGGXApproxLTC(Lobe1Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
Lighting.Specular = lerp(Lobe0Specular, Lobe1Specular, LobeMix);
}
else
{
Lighting.Specular = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * DualSpecularGGX(AverageRoughness, Lobe0Roughness, Lobe1Roughness, LobeMix, GBuffer.SpecularColor, Context, AreaLight);
}
Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);
Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);
#if USE_TRANSMISSION
const uint ProfileId = ExtractSubsurfaceProfileInt(GBuffer);
FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams(ProfileId);
float Thickness = Shadow.TransmissionThickness;
Thickness = DecodeThickness(Thickness);
Thickness *= SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE;
float3 Profile = GetTransmissionProfile(ProfileId, Thickness).rgb;
float3 RefracV = refract(V, -N, TransmissionParams.OneOverIOR);
float PhaseFunction = ApproximateHG( dot(-AreaLight.DiffuseL, RefracV), TransmissionParams.ScatteringDistribution );
Lighting.Transmission = AreaLight.FalloffColor * Profile * (AreaLight.Falloff * PhaseFunction); // TODO: This probably should also include cosine term (NoL)
#else // USE_TRANSMISSION
Lighting.Transmission = 0;
#endif // USE_TRANSMISSION
return Lighting;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
const float3 FuzzColor = ExtractSubsurfaceColor(GBuffer);
const float Cloth = saturate(GBuffer.CustomData.a);
BxDFContext Context;
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, AreaLight.SpecularL, AreaLight.NoL);
#else
Init( Context, N, V, AreaLight.SpecularL );
#endif
SphereMaxNoH( Context, AreaLight.SphereSinAlpha, true );
Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );
float3 Spec1;
if(IsRectLight(AreaLight))
Spec1 = RectGGXApproxLTC( GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture );
else
Spec1 = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, AreaLight );
const FBxDFEnergyTerms EnergyTerms1 = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);
Spec1 *= ComputeEnergyConservation(EnergyTerms1);
// Cloth - Asperity Scattering - Inverse Beckmann Layer
float D2 = D_InvGGX( Pow4( GBuffer.Roughness ), Context.NoH );
float Vis2 = Vis_Cloth( Context.NoV, AreaLight.NoL );
#if SHADING_PATH_MOBILE
Vis2 = saturate(Vis2);
#endif
float3 F2 = F_Schlick( FuzzColor, Context.VoH );
float3 Spec2 = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * (D2 * Vis2) * F2;
const FBxDFEnergyTermsA EnergyTerms2 = ComputeClothEnergyTermsA(GBuffer.Roughness, Context.NoV);
Spec2 *= ComputeEnergyConservation(EnergyTerms2);
FDirectLighting Lighting;
Lighting.Diffuse = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = lerp( Spec1, Spec2, Cloth );
Lighting.Transmission = 0;
Lighting.Diffuse *= lerp(ComputeEnergyPreservation(EnergyTerms1), ComputeEnergyPreservation(EnergyTerms2), Cloth);
return Lighting;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting SubsurfaceBxDF(FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, AreaLight, Shadow);
half3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
half Opacity = GBuffer.CustomData.a;
// to get an effect when you see through the material
// hard coded pow constant
half InScatter = pow(saturate(dot(AreaLight.DiffuseL, -V)), 12) * lerp(3, .1f, Opacity);
// Wrap around lighting,
// * /(PI*2) to be energy consistent (hack do get some view dependnt and light dependent effect)
// * Opacity of 0 gives no normal dependent lighting, Opacity of 1 gives strong normal contribution
// * Simplified version (with w=.5, n=1.5):
// half WrappedDiffuse = 2 * pow(saturate((dot(N, L) + w) / (1.0f + w)), n) * (n + 1) / (2 * (1 + w));
// NormalContribution = WrappedDiffuse * Opacity + 1 - Opacity;
const half WrappedDiffuse = pow(saturate(dot(N, AreaLight.DiffuseL) * (1.f / 1.5f) + (0.5f / 1.5f)), 1.5f) * (2.5f / 1.5f);
const half NormalContribution = lerp(1.f, WrappedDiffuse, Opacity);
const half BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2);
// Transmission
// * Emulate Beer-Lambert absorption by retrieving extinction coefficient from SubSurfaceColor. Subsurface is interpreted as a 'transmittance color'
// at a certain 'normalized' distance (SubSurfaceColorAsTransmittanceAtDistanceInMeters). This is a coarse approximation for getting hue-shiting.
// * TransmittedColor is computed for the 1-normalized distance, and then transformed back-and-forth in HSV space to preserving the luminance value
// of the original color, but getting hue shifting
const half3 ExtinctionCoefficients = TransmittanceToExtinction(SubsurfaceColor, View.SubSurfaceColorAsTransmittanceAtDistanceInMeters);
const half3 RawTransmittedColor = ExtinctionToTransmittance(ExtinctionCoefficients, 1.0f /*At 1 meters, as we use normalized units*/);
const half3 TransmittedColor = HSV_2_LinearRGB(half3(LinearRGB_2_HSV(RawTransmittedColor).xy, LinearRGB_2_HSV(SubsurfaceColor).z));
// lerp to never exceed 1 (energy conserving)
Lighting.Transmission = AreaLight.FalloffColor * (AreaLight.Falloff * lerp(BackScatter, 1, InScatter)) * lerp(TransmittedColor, SubsurfaceColor, Shadow.TransmissionThickness);
return Lighting;
}
#if 0 // SGGX ref
float PDF_GGX( float a2, half3 N, half3 H )
{
half NoH = saturate( dot(N,H) );
return D_GGX( a2, NoH ) * NoH;
}
float3 SimpleSpec( FGBufferData GBuffer, half3 N, half3 V, half3 L )
{
half3 H = normalize( L + V );
half NoL = saturate( dot(N,L) );
half NoV = saturate( dot(N,V) );
half VoL = saturate( dot(V,L) );
half NoH = saturate( dot(N,H) );
half VoH = saturate( dot(V,H) );
float a2 = Pow4( GBuffer.Roughness );
// Generalized microfacet specular
float D = D_GGX( a2, NoH );
float Vis = Vis_SmithJoint( a2, max( NoV, 1e-5 ), NoL );
float3 F = F_Schlick( GBuffer.SpecularColor, VoH );
return (D * Vis) * F;
}
FDirectLighting ShadeSGGX( FGBufferData GBuffer, float Geo_a2, half3 V, half3 L )
{
FDirectLighting Lighting = (FDirectLighting)0;
half VoL = dot(V, L);
half3 H = normalize( L + V );
float Mat_a2 = Pow4( GBuffer.Roughness );
const uint NumSamples = 64;
LOOP
for( uint i = 0; i < NumSamples; i++ )
{
RandomSequence RandSequence;
RandomSequence_Initialize( RandSequence, View.StateFrameIndex, i );
float4 Sample_VNDF = SGGX_DvisSample( RandomSequence_GenerateSample2D( RandSequence ), Geo_a2, V );
float MoL_VNDF = saturate( dot( Sample_VNDF.xyz, L ) );
{
float4 Sample_CosL = CosineSampleHemisphere( RandomSequence_GenerateSample2D( RandSequence ), L );
float MoL_CosL = saturate( dot( Sample_CosL.xyz, L ) );
float Dvis_CosL = SGGX_Dvis( Geo_a2, V.z, dot( V, Sample_CosL.xyz ), Sample_CosL.z );
Lighting.Diffuse += MoL_VNDF * MISWeightBalanced( Sample_VNDF.w, MoL_VNDF / PI );
//Lighting.Diffuse += Dvis_CosL * MoL_CosL / Sample_CosL.w * MISWeightBalanced( Sample_CosL.w, Dvis_CosL );
Lighting.Diffuse += Dvis_CosL * PI * MISWeightBalanced( Sample_CosL.w, Dvis_CosL );
}
{
float4 Sample_Material = ImportanceSampleGGX( RandomSequence_GenerateSample2D( RandSequence ), Mat_a2 );
Sample_Material.xyz = mul( Sample_Material.xyz, GetTangentBasis(H) );
float Dvis_Material = SGGX_Dvis( Geo_a2, V.z, dot( V, Sample_Material.xyz ), Sample_Material.z );
float MoL_Material = saturate( dot( Sample_Material.xyz, L ) );
Lighting.Specular += SimpleSpec( GBuffer, Sample_VNDF.xyz, V, L ) * MoL_VNDF * MISWeightBalanced( Sample_VNDF.w, PDF_GGX( Mat_a2, Sample_VNDF.xyz, H ) );
Lighting.Specular += SimpleSpec( GBuffer, Sample_Material.xyz, V, L ) * MoL_Material * Dvis_Material / Sample_Material.w * MISWeightBalanced( Sample_Material.w, Dvis_Material );
}
{
// http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
half Wrap = 0.5;
half WrapNoL = saturate( ( -dot( Sample_VNDF.xyz, L ) + Wrap ) / Square( 1 + Wrap ) );
// Scatter distribution
float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) );
Lighting.Transmission += WrapNoL * Scatter;
}
}
Lighting.Diffuse /= NumSamples;
Lighting.Specular /= NumSamples;
Lighting.Transmission /= NumSamples;
Lighting.Diffuse *= GBuffer.DiffuseColor / PI;
Lighting.Transmission *= ExtractSubsurfaceColor( GBuffer );
return Lighting;
}
#endif
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
half3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
#if 0
half NormalLength2 = length2( GBuffer.NormalDistribution );
half RcpNormalLength = rsqrt( NormalLength2 );
half NormalLength = NormalLength2 * RcpNormalLength;
N = GBuffer.NormalDistribution * RcpNormalLength;
half3 L = AreaLight.DiffuseL;
half NoL = dot(N, L);
half NoV = dot(N, V);
half VoL = dot(V, L);
half InvLenH = rsqrt( 2 + 2 * VoL );
half NoH = ( NoL + NoV ) * InvLenH;
half VoH = InvLenH + InvLenH * VoL;
half3 H = ( L + V ) * InvLenH;
float Geo_a = 0.5 - 0.5 * NormalLength;
if( NoV < 0 )
Geo_a = 1 - Geo_a;
Geo_a = clamp( Geo_a, 0.01, 0.999 );
if( Geo_a > 0.5 )
Geo_a = rcp( 2 - 2 * Geo_a );
else
Geo_a = 2 * Geo_a;
float Geo_a2 = Pow2( Geo_a );
float Geo_Area = SGGX_SurfaceArea( Geo_a2 );
float3x3 TangentToWorld = GetTangentBasis( N );
float3 TangentV = mul( TangentToWorld, V );
float3 TangentL = mul( TangentToWorld, L );
float3 SphereScale = 1;
SphereScale.xy = Geo_a;
float3 SphereV = normalize( TangentV * SphereScale );
float3 SphereL = normalize( TangentL * SphereScale );
float SphereVoL = dot( SphereV, SphereL );
#if 0
float vNoV = SGGX_ProjectedArea( Geo_a2, NoV ) * 2 / Geo_Area;
float vNoL = SGGX_ProjectedArea( Geo_a2, NoL ) * 2 / Geo_Area;
#else
float vNoV = 2 * TangentV.z / ( SphereV.z * Geo_Area );
float vNoL = 2 * TangentL.z / ( SphereL.z * Geo_Area );
#endif
vNoL *= SGGX_Diffuse( SphereVoL );
vNoL *= lerp( 0.5 * PI, 2, saturate( Geo_a ) );
FDirectLighting Lighting = (FDirectLighting)0;
{
Lighting.Diffuse = AreaLight.FalloffColor * (AreaLight.Falloff * vNoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
}
{
float Mat_a2 = Pow4( GBuffer.Roughness );
float Spec_a2 = SGGX_Convolve( Geo_a2, Mat_a2 );
float D = D_GGX( Spec_a2, NoH );
float Vis_SmithV = vNoL * SGGX_ProjectedArea( Spec_a2, NoV );
float Vis_SmithL = vNoV * SGGX_ProjectedArea( Spec_a2, NoL );
//Vis_SmithV = lerp( vNoL * SGGX_ProjectedArea( Mat_a2, vNoV ), Vis_SmithV, VoH );
Vis_SmithL = lerp( vNoV * SGGX_ProjectedArea( Mat_a2, vNoL ), Vis_SmithL, VoH );
float Vis = 0.5 * rcp( Vis_SmithV + Vis_SmithL );
float3 F = F_Schlick( GBuffer.SpecularColor, VoH );
Lighting.Specular = AreaLight.FalloffColor * (AreaLight.Falloff * vNoL * D * Vis) * F;
}
{
float InvNoL = -0.6 * SphereVoL;
InvNoL *= TangentL.z / SphereL.z;
InvNoL /= 0.25 * Geo_Area;
// http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
half Wrap = 0.5;
half WrapNoL = saturate( ( InvNoL + Wrap ) / Square( 1 + Wrap ) );
// Scatter distribution
float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) );
Lighting.Transmission = AreaLight.FalloffColor * (AreaLight.Falloff * WrapNoL * Scatter) * SubsurfaceColor;
}
#else
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, AreaLight, Shadow );
// http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
half Wrap = 0.5;
half WrapNoL = saturate( ( -dot(N, AreaLight.DiffuseL) + Wrap ) / Square( 1 + Wrap ) );
// Scatter distribution
half VoL = dot(V, AreaLight.DiffuseL);
float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) );
Lighting.Transmission = AreaLight.FalloffColor * (AreaLight.Falloff * WrapNoL * Scatter) * SubsurfaceColor;
#endif
return Lighting;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate
FDirectLighting EyeBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
#if IRIS_NORMAL
const float2 CausticNormalDelta = float2( GBuffer.StoredMetallic, GBuffer.StoredSpecular ) * 2 - (256.0/255.0);
const float2 IrisNormalDelta = float2( GBuffer.CustomData.y, GBuffer.CustomData.z ) * 2 - (256.0/255.0);
const float IrisMask = 1.0f - GBuffer.CustomData.w;
const float2 WorldNormalOct = UnitVectorToOctahedron( GBuffer.WorldNormal );
const float3 CausticNormal = OctahedronToUnitVector( WorldNormalOct + CausticNormalDelta );
const float3 IrisNormal = OctahedronToUnitVector( WorldNormalOct + IrisNormalDelta );
#else
const float3 IrisNormal = OctahedronToUnitVector( GBuffer.CustomData.yz * 2 - 1 );
const float IrisDistance = GBuffer.StoredMetallic;
const float IrisMask = 1.0f - GBuffer.CustomData.w;
// Blend in the negative intersection normal to create some concavity
// Not great as it ties the concavity to the convexity of the cornea surface
// No good justification for that. On the other hand, if we're just looking to
// introduce some concavity, this does the job.
const float3 CausticNormal = normalize(lerp(IrisNormal, -N, IrisMask*IrisDistance));
#endif
BxDFContext Context;
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, AreaLight.SpecularL, AreaLight.NoL);
#else
Init( Context, N, V, AreaLight.SpecularL );
#endif
SphereMaxNoH( Context, AreaLight.SphereSinAlpha, false );
Context.NoV = saturate( abs( Context.NoV ) + 1e-5 );
const bool bIsRect = IsRectLight(AreaLight);
Context.VoH = bIsRect ? Context.NoV : Context.VoH;
// F_Schlick
float F0 = GBuffer.Specular * 0.08;
float Fc = Pow5( 1 - Context.VoH );
float F = Fc + (1 - Fc) * F0;
const FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, F0);
FDirectLighting Lighting;
if( bIsRect )
{
Lighting.Specular = RectGGXApproxLTC( GBuffer.Roughness, F0, N, V, AreaLight.Rect, AreaLight.Texture );
}
else
{
float a2 = Pow4( GBuffer.Roughness );
float Energy = EnergyNormalization( a2, Context.VoH, AreaLight );
// Generalized microfacet specular
float Vis = Vis_SmithJointApprox(a2, Context.NoV, AreaLight.NoL);
float D = D_GGX(a2, Context.NoH) * Energy;
Lighting.Specular = AreaLight.FalloffColor * (AreaLight.Falloff * AreaLight.NoL) * D * Vis * F;
}
float IrisNoL = saturate( dot( IrisNormal, AreaLight.SpecularL ) );
float Power = lerp( 12, 1, IrisNoL );
float Caustic = 0.8 + 0.2 * ( Power + 1 ) * pow( saturate( dot( CausticNormal, AreaLight.SpecularL ) ), Power );
float Iris = IrisNoL * Caustic;
float Sclera = AreaLight.NoL;
Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);
#if USE_ENERGY_CONSERVATION
const float3 EnergyPreservation = ComputeEnergyPreservation(EnergyTerms);
#else
// Preserve old behavior when energy conservation is disabled
const float EnergyPreservation = 1.0f - F;
#endif
Lighting.Diffuse = 0;
Lighting.Transmission = AreaLight.FalloffColor * ( AreaLight.Falloff * lerp( Sclera, Iris, IrisMask ) * EnergyPreservation ) * Diffuse_Lambert( GBuffer.DiffuseColor );
#if SHADING_PATH_MOBILE
uint SubsurfaceProfileId = ExtractSubsurfaceProfileInt(GBuffer);
half Curvature = GBuffer.Curvature;
half ShadowFactor = 1.0f - sqrt(Shadow.SurfaceShadow);
// Rotate the world normal based on the shadow value, it's just a experimental value
half UnClampedNoL = dot(GBuffer.WorldNormal, AreaLight.DiffuseL);
half UnClampedRotatedNoL = max(UnClampedNoL - max(2.0f * UnClampedNoL, 0.4f) * ShadowFactor, -1.0f);
half4 BurleyDiffuse = GetSSProfilePreIntegratedValue(SubsurfaceProfileId, UnClampedRotatedNoL, Curvature);
// Rotate the world normal based on the shadow value, it's just a experimental value
half UnClampedIrisNoL = dot(IrisNormal, AreaLight.SpecularL);
half UnClampedRotatedIrisNoL = max(UnClampedIrisNoL - max(2.0f * UnClampedIrisNoL, 0.4f) * ShadowFactor, -1.0f);
half4 IrisBurleyDiffuse = GetSSProfilePreIntegratedValue(SubsurfaceProfileId, UnClampedRotatedIrisNoL * Caustic, Curvature);
// asset specific color
half3 Tint = GetSubsurfaceProfileTexture(SSSS_TINT_SCALE_OFFSET, SubsurfaceProfileId).rgb;
// Needs to apply shadow at here, since the preintegrated value has already counted it, and we need to skip applying shadow outside.
Lighting.Transmission = lerp(Lighting.Transmission * Shadow.SurfaceShadow, AreaLight.Falloff * lerp(BurleyDiffuse.rgb, IrisBurleyDiffuse.rgb, IrisMask) * Diffuse_Lambert(GBuffer.DiffuseColor), Tint);
#endif
return Lighting;
}
FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, AreaLight, Shadow );
half3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
half Opacity = GBuffer.CustomData.a;
half3 PreintegratedBRDF = Texture2DSampleLevel(View.PreIntegratedBRDF, View.PreIntegratedBRDFSampler, float2(saturate(dot(N, AreaLight.DiffuseL) * .5 + .5), 1 - Opacity), 0).rgb;
Lighting.Transmission = AreaLight.FalloffColor * AreaLight.Falloff * PreintegratedBRDF * SubsurfaceColor;
return Lighting;
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate - Substrate uses SubstrateEvaluateBSDFCommon() instead
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow )
{
switch( GBuffer.ShadingModelID )
{
case SHADINGMODELID_DEFAULT_LIT:
case SHADINGMODELID_SINGLELAYERWATER:
case SHADINGMODELID_THIN_TRANSLUCENT:
return DefaultLitBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE:
return SubsurfaceBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_PREINTEGRATED_SKIN:
return PreintegratedSkinBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_CLEAR_COAT:
return ClearCoatBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE_PROFILE:
return SubsurfaceProfileBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_TWOSIDED_FOLIAGE:
return TwoSidedBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_HAIR:
return HairBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_CLOTH:
return ClothBxDF( GBuffer, N, V, AreaLight, Shadow );
case SHADINGMODELID_EYE:
return EyeBxDF( GBuffer, N, V, AreaLight, Shadow );
default:
return (FDirectLighting)0;
}
}
// UE_DEPRECATED 5.7 - Deprecated by Substrate - Substrate uses SubstrateEvaluateBSDFCommon() instead
FDirectLighting EvaluateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float NoL, FShadowTerms Shadow )
{
FAreaLight AreaLight = InitAreaLight();
AreaLight.DiffuseL = L;
AreaLight.SpecularL = L;
AreaLight.NoL = NoL;
return IntegrateBxDF( GBuffer, N, V, AreaLight, Shadow );
}