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

973 lines
36 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"
#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
#if 0
void StandardShadingShared( float3 DiffuseColor, float3 SpecularColor, float Roughness, float3 V, half3 N )
{
float NoV = saturate( abs( dot(N, V) ) + 1e-5 );
// Diffuse_Lambert
Shared.DiffuseMul = DiffuseColor * (1.0 / PI);
// D_GGX, Vis_SmithJointApprox
float m = Roughness * Roughness;
Shared.m2 = m * m;
Shared.SpecularMul = (0.5 / PI) * Shared.m2;
Shared.VisMad = float2( 2 * NoV * ( 1 - m ) + m, NoV * m );
// F_Schlick
Shared.SpecularMul *= saturate( 50.0 * SpecularColor.g );
}
void StandardShadingPerLight( Shared, float3 L, float3 V, half3 N )
{
float3 H = normalize(V + L); // 3 add, 2 mad, 4 mul, 1 rsqrt
float NoL = saturate( dot(N, L) ); // 2 mad, 1 mul
float NoH = saturate( dot(N, H) ); // 2 mad, 1 mul
float VoH = saturate( dot(V, H) ); // 2 mad, 1 mul
// D_GGX, Vis_SmithJointApprox
float d = ( NoH * Shared.m2 - NoH ) * NoH + 1; // 2 mad
float v = NoL * Shared.VisMad.x + Shared.VisMad.y; // 1 mad
float D_Vis = Shared.SpecularMul * rcp( d * d * v ); // 3 mul, 1 rcp
// F_Schlick
float Fc = pow( 1 - VoH, 5 ); // 1 sub, 3 mul
float3 F = Fc + (1 - Fc) * SpecularColor; // 1 sub, 3 mad
return Shared.DiffuseMul + D_Vis * F; // 3 mad
}
#endif
struct FDirectLighting
{
float3 Diffuse;
float3 Specular;
float3 Transmission;
};
struct FShadowTerms
{
half SurfaceShadow;
half TransmissionShadow;
half TransmissionThickness;
FHairTransmittanceData HairTransmittance;
};
FDirectLighting HairBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow)
{
const float3 BsdfValue = HairShading(GBuffer, L, 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 * 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;
}
float3 SpecularGGX(float Roughness, float Anisotropy, float3 SpecularColor, BxDFContext Context, float NoL, FAreaLight AreaLight)
{
float Alpha = Roughness * Roughness;
float a2 = Alpha * Alpha;
FAreaLight Punctual = AreaLight;
Punctual.SphereSinAlpha = 0;
Punctual.SphereSinAlphaSoft = 0;
Punctual.LineCosSubtended = 1;
Punctual.Rect = (FRect)0;
Punctual.IsRectAndDiffuseMicroReflWeight = 0;
float Energy = EnergyNormalization(a2, Context.VoH, Punctual);
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) * Energy;
float3 Vis = Vis_SmithJointAniso(ax, ay, Context.NoV, NoL, Context.XoV, Context.XoL, Context.YoV, Context.YoL);
float3 F = F_Schlick( SpecularColor, Context.VoH );
return (D * Vis) * F;
}
float3 SpecularGGX( float Roughness, float3 SpecularColor, BxDFContext Context, half NoL, 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, NoL );
float3 F = F_Schlick( SpecularColor, Context.VoH );
return (D * Vis) * F;
}
half3 DualSpecularGGX(half AverageRoughness, half Lobe0Roughness, half Lobe1Roughness, half LobeMix, half3 SpecularColor, BxDFContext Context, half NoL, 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, NoL); // Average visibility well approximates using two separate ones (one per lobe).
float3 F = F_Schlick(SpecularColor, Context.VoH);
return (D * Vis) * F;
}
FDirectLighting DefaultLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
FDirectLighting Lighting;
Lighting.Diffuse = 0;
Lighting.Specular = 0;
Lighting.Transmission = 0;
BRANCH
if (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, L);
NoV = Context.NoV;
VoH = Context.VoH;
NoH = Context.NoH;
}
else
{
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, L, NoL);
#else
Init(Context, N, V, L);
#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
// Chan diffuse model with roughness == specular roughness. This is not necessarily a good modelisation of reality because when the mean free path is super small, the diffuse can in fact looks rougher. 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.
Lighting.Diffuse = Diffuse_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), NoV, NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);
#endif
Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);
BRANCH
if (bHasAnisotropy)
{
//Lighting.Specular = GBuffer.WorldTangent * .5f + .5f;
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight);
}
else
{
if( IsRectLight(AreaLight) )
{
Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);
}
else
{
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, 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;
}
// 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;
}
FDirectLighting ClearCoatBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, 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, L);
// 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, NoL);
float D = D_GGX(a2, Context.NoH) * ClearCoatEnergy;
half Fr1 = (D * Vis) * F;
Lighting.Specular = ClearCoat * AreaLight.FalloffColor * (Falloff * 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, L);
}
else
{
Init(TempContext, Nspec, V, L);
}
// If bottom-normal, update normal-based dot products:
float3 H = normalize(V + L);
Context.NoH = saturate(dot(N, H));
Context.NoV = saturate(dot(N, V));
Context.NoL = saturate(dot(N, L));
Context.VoL = saturate(dot(V, L));
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, L);
}
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 = (Falloff * 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 = 0;
BRANCH
if (bHasAnisotropy)
{
FAreaLight Punctual = AreaLight;
Punctual.SphereSinAlpha = 0;
Punctual.SphereSinAlphaSoft = 0;
Punctual.LineCosSubtended = 1;
Punctual.Rect = (FRect)0;
Punctual.IsRectAndDiffuseMicroReflWeight = 0;
Energy = EnergyNormalization(a2, Context.VoH, Punctual);
}
else
{
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, 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 * Falloff * 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
}
FDirectLighting SubsurfaceProfileBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
BxDFContext Context;
#if SHADING_PATH_MOBILE
InitMobile(Context, N, V, L, NoL);
#else
Init( Context, N, V, L );
#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, L);
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 * (Falloff * 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_Chan(GBuffer.DiffuseColor, Pow4(GBuffer.Roughness), Context.NoV, NoL, Context.VoH, Context.NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
#else
const float3 DiffuseReflection = Diffuse_Burley(GBuffer.DiffuseColor, GBuffer.Roughness, Context.NoV, NoL, Context.VoH);
#endif
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * 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 * (Falloff * NoL) * DualSpecularGGX(AverageRoughness, Lobe0Roughness, Lobe1Roughness, LobeMix, GBuffer.SpecularColor, Context, NoL, 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(-L, RefracV), TransmissionParams.ScatteringDistribution );
Lighting.Transmission = AreaLight.FalloffColor * Profile * (Falloff * PhaseFunction); // TODO: This probably should also include cosine term (NoL)
#else // USE_TRANSMISSION
Lighting.Transmission = 0;
#endif // USE_TRANSMISSION
return Lighting;
}
FDirectLighting ClothBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, 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, L, NoL);
#else
Init( Context, N, V, L );
#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 * (Falloff * NoL) * SpecularGGX( GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, 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, NoL );
#if SHADING_PATH_MOBILE
Vis2 = saturate(Vis2);
#endif
float3 F2 = F_Schlick( FuzzColor, Context.VoH );
float3 Spec2 = AreaLight.FalloffColor * (Falloff * NoL) * (D2 * Vis2) * F2;
const FBxDFEnergyTermsA EnergyTerms2 = ComputeClothEnergyTermsA(GBuffer.Roughness, Context.NoV);
Spec2 *= ComputeEnergyConservation(EnergyTerms2);
FDirectLighting Lighting;
Lighting.Diffuse = AreaLight.FalloffColor * (Falloff * NoL) * Diffuse_Lambert( GBuffer.DiffuseColor );
Lighting.Specular = lerp( Spec1, Spec2, Cloth );
Lighting.Transmission = 0;
Lighting.Diffuse *= lerp(ComputeEnergyPreservation(EnergyTerms1), ComputeEnergyPreservation(EnergyTerms2), Cloth);
return Lighting;
}
FDirectLighting SubsurfaceBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, 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(L, -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, L) * (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 * (Falloff * lerp(BackScatter, 1, InScatter)) * lerp(TransmittedColor, SubsurfaceColor, Shadow.TransmissionThickness);
return Lighting;
}
FDirectLighting TwoSidedBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
half3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
// http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
half Wrap = 0.5;
half WrapNoL = saturate( ( -dot(N, L) + Wrap ) / Square( 1 + Wrap ) );
// Scatter distribution
half VoL = dot(V, L);
float Scatter = D_GGX( 0.6*0.6, saturate( -VoL ) );
Lighting.Transmission = AreaLight.FalloffColor * (Falloff * WrapNoL * Scatter) * SubsurfaceColor;
return Lighting;
}
FDirectLighting EyeBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, 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, L, NoL);
#else
Init( Context, N, V, L );
#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, NoL);
float D = D_GGX(a2, Context.NoH) * Energy;
Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * D * Vis * F;
}
float IrisNoL = saturate( dot( IrisNormal, L ) );
float Power = lerp( 12, 1, IrisNoL );
float Caustic = 0.8 + 0.2 * ( Power + 1 ) * pow( saturate( dot( CausticNormal, L ) ), Power );
float Iris = IrisNoL * Caustic;
float Sclera = 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 * ( 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, L);
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, L);
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, Falloff * lerp(BurleyDiffuse.rgb, IrisBurleyDiffuse.rgb, IrisMask) * Diffuse_Lambert(GBuffer.DiffuseColor), Tint);
#endif
return Lighting;
}
FDirectLighting PreintegratedSkinBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
FDirectLighting Lighting = DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
half3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);
half Opacity = GBuffer.CustomData.a;
half3 PreintegratedBRDF = Texture2DSampleLevel(View.PreIntegratedBRDF, View.PreIntegratedBRDFSampler, float2(saturate(dot(N, L) * .5 + .5), 1 - Opacity), 0).rgb;
Lighting.Transmission = AreaLight.FalloffColor * Falloff * PreintegratedBRDF * SubsurfaceColor;
return Lighting;
}
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
switch( GBuffer.ShadingModelID )
{
case SHADINGMODELID_DEFAULT_LIT:
case SHADINGMODELID_SINGLELAYERWATER:
case SHADINGMODELID_THIN_TRANSLUCENT:
return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE:
return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_PREINTEGRATED_SKIN:
return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLEAR_COAT:
return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_SUBSURFACE_PROFILE:
return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_TWOSIDED_FOLIAGE:
return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_HAIR:
return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_CLOTH:
return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
case SHADINGMODELID_EYE:
return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
default:
return (FDirectLighting)0;
}
}
FDirectLighting EvaluateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float NoL, FShadowTerms Shadow )
{
FAreaLight AreaLight;
AreaLight.SphereSinAlpha = 0;
AreaLight.SphereSinAlphaSoft = 0;
AreaLight.LineCosSubtended = 1;
AreaLight.FalloffColor = 1;
AreaLight.Rect = (FRect)0;
AreaLight.IsRectAndDiffuseMicroReflWeight = 0;
AreaLight.Texture = InitRectTexture();
return IntegrateBxDF( GBuffer, N, V, L, 1, NoL, AreaLight, Shadow );
}