// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #ifndef SUBSTRATE_ENABLED #error "This header should only be included when Substrate is enabled." #endif #include "PathTracingSubstrateCommon.ush" #include "PathTracingMaterialCommon.ush" #include "PathTracingGlossy.ush" #include "PathTracingFresnel.ush" #include "../../BRDF.ush" struct FSubstrateSingleSlabData { float3x3 Basis; float3 V; float DiffuseWeight; float3 F0; float3 F90; float2 Alpha0; float2 Alpha1; float3 Spec0W; float3 Spec1W; FSubstrateSheenData Fuzz; float3 LobeCdf; float4 LobePdf; void PrepareBSDF(float3 V_World, FPathTracingPayload Payload, bool bUseSSSColor = false) { Basis = GetGGXBasis(Payload.RoughnessData.x, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, Alpha0); Alpha1 = GetGGXAlpha(Payload.RoughnessData.y, Payload.Anisotropy); V = mul(Basis, V_World); // make sure F0=0.0 fades off the specular lobe completely F0 = Payload.SpecularColor; F90 = Payload.SpecularEdgeColor * F0RGBToMicroOcclusion(F0); const float NoV = saturate(V.z); // SUBSTRATE_TODO: Add support for F82 model? FBxDFEnergyTermsRGB Spec0 = ComputeGGXSpecEnergyTermsRGB(Payload.RoughnessData.x, NoV, F0, F90); FBxDFEnergyTermsRGB Spec1 = ComputeGGXSpecEnergyTermsRGB(Payload.RoughnessData.y, NoV, F0, F90); Fuzz.Prepare(V, Payload.FuzzRoughness, Payload.FuzzAmount); // save the parts needed during eval/sampling Spec0W = Spec0.W; Spec1W = Spec1.W; const float3 SpecE = lerp(Spec0.E, Spec1.E, Payload.RoughnessData.z); DiffuseWeight = Fuzz.Attenuation * (1.0 - Luminance(SpecE)); const float3 Spec0Albedo = Fuzz.Attenuation * (1.0 - Payload.RoughnessData.z) * Spec0.E; const float3 Spec1Albedo = Fuzz.Attenuation * ( Payload.RoughnessData.z) * Spec1.E; const float3 FuzzAlbedo = Fuzz.Scale * Payload.FuzzColor; // Now prepare a cdf/pdf for lobe selection float3 MaxLobeWeight = Payload.GetMaxLobeWeight(); LobeCdf = LobeSelectionCdf( MaxLobeWeight * DiffuseWeight * (bUseSSSColor ? Payload.SubsurfaceColor : Payload.DiffuseColor), MaxLobeWeight * Spec0Albedo, MaxLobeWeight * Spec1Albedo, MaxLobeWeight * FuzzAlbedo); LobePdf = LobeSelectionPdf(LobeCdf); } }; FMaterialSample Substrate_SampleMaterial( float3 V_World, FPathTracingPayload Payload, float3 RandSample ) { FSubstrateSingleSlabData Data = (FSubstrateSingleSlabData)0; Data.PrepareBSDF(V_World, Payload); float3 L = 0, H = 0, V = Data.V; float OutRoughness = 1; const bool bSampledDiffuse = RandSample.x < Data.LobeCdf.x; const bool bSampledSpecular = RandSample.x < Data.LobeCdf.z; if (bSampledDiffuse) { RandSample.x = RescaleRandomNumber(RandSample.x, 0.0, Data.LobeCdf.x); // diffuse lobe L = CosineSampleHemisphere(RandSample.xy).xyz; H = normalize(L + V); } else if (bSampledSpecular) { // specular lobes const bool bUseSpec0 = RandSample.x < Data.LobeCdf.y; if (bUseSpec0) { RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.x, Data.LobeCdf.y); OutRoughness = Payload.RoughnessData.x; } else { RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.y, Data.LobeCdf.z); OutRoughness = Payload.RoughnessData.y; } H = ImportanceSampleVisibleGGX(RandSample.xy, bUseSpec0 ? Data.Alpha0 : Data.Alpha1, V).xyz; L = reflect(-V, H); if (L.z <= 0) { // invalid output direction, exit early return NullMaterialSample(); } } else { // cloth lobe RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.z, 1.0); L = Data.Fuzz.Sample(RandSample.xy); H = normalize(L + V); } // transform to world space const float3 L_World = normalize(mul(L, Data.Basis)); const float3 N_World = Payload.WorldNormal; const float NoV = saturate(V.z); const float NoL = saturate(L.z); const float VoH = saturate(dot(V, H)); const float NoH = saturate(H.z); FMaterialSample Result = CreateMaterialSample(L_World, 0.0, 0.0, 1.0, OutRoughness, PATHTRACER_SCATTER_DIFFUSE); const float DiffPdf = NoL / PI; float2 GGXResult0 = GGXEvalReflection(L, V, H, Data.Alpha0); const float2 GGXResult1 = GGXEvalReflection(L, V, H, Data.Alpha1); const float Spec0Pdf = GGXResult0.y; const float Spec1Pdf = GGXResult1.y; const float4 ClothResult = Data.Fuzz.Eval(L, V, H, Payload.FuzzColor); const float ClothPdf = ClothResult.w; const float ShadowTerminator = ShadowTerminatorTerm(L_World, N_World, Payload.WorldSmoothNormal); if (bSampledDiffuse) { const float3 Diffuse = Data.DiffuseWeight * GetPathTracingDiffuseModel(Payload.DiffuseColor, Payload.RoughnessData.x, NoV, NoL, VoH, NoH); Result.AddLobeWithMIS(Diffuse * ShadowTerminator, NoL / PI, Data.LobePdf.x); Result.Pdf += Data.LobePdf.y * Spec0Pdf; Result.Pdf += Data.LobePdf.z * Spec1Pdf; Result.Pdf += Data.LobePdf.w * ClothPdf; } else if (bSampledSpecular) { SubstrateGlint(Payload, V, L, Data.Alpha0, GGXResult0.y /*Pdf*/, GGXResult0.x /*Weight*/); const float3 FTint = SubstrateSpecularTint(Payload, NoV, NoL, VoH, NoH); #if SUBSTRATE_FRESNEL_F82 // SUBSTRATE_TODO: Need to tune the logic for specular micro-occlusion ... const float3 F = Data.Fuzz.Attenuation * F_AdobeF82(Data.F0, Data.F90, VoH) * FTint; #else const float3 F = Data.Fuzz.Attenuation * F_Schlick(Data.F0, Data.F90, VoH) * FTint; #endif Result.AddLobeWithMIS((1.0 - Payload.RoughnessData.z) * F * GGXResult0.x * Data.Spec0W, Spec0Pdf, Data.LobePdf.y); Result.AddLobeWithMIS(( Payload.RoughnessData.z) * F * GGXResult1.x * Data.Spec1W, Spec1Pdf, Data.LobePdf.z); Result.Pdf += Data.LobePdf.x * DiffPdf; Result.Pdf += Data.LobePdf.w * ClothPdf; Result.ScatterType = PATHTRACER_SCATTER_SPECULAR; } else { // Cloth Lobe Result.AddLobeWithMIS(ClothResult.xyz * ShadowTerminator, ClothPdf, Data.LobePdf.w); Result.ScatterType = PATHTRACER_SCATTER_SPECULAR; Result.Pdf += Data.LobePdf.x * DiffPdf; Result.Pdf += Data.LobePdf.y * Spec0Pdf; Result.Pdf += Data.LobePdf.z * Spec1Pdf; } Result.Weight *= SubstrateLobeWeight(Payload, NoL); return Result; } FMaterialEval Substrate_EvalMaterial( float3 V_World, float3 L_World, FPathTracingPayload Payload, float2 DiffuseSpecularScale ) { FSubstrateSingleSlabData Data = (FSubstrateSingleSlabData)0; Data.PrepareBSDF(V_World, Payload); const float3 N_World = Payload.WorldNormal; // move vectors into right shading frame const float3 V = Data.V; const float3 L = mul(Data.Basis, L_World); const float3 H = normalize(V + L); const float NoV = saturate(V.z); const float NoL = saturate(L.z); const float VoH = saturate(dot(V, H)); const float NoH = saturate(H.z); FMaterialEval Result = NullMaterialEval(); const float ShadowTerminator = ShadowTerminatorTerm(L_World, N_World, Payload.WorldSmoothNormal); { const float3 Diffuse = Data.DiffuseWeight * GetPathTracingDiffuseModel(Payload.DiffuseColor, Payload.RoughnessData.x, NoV, NoL, VoH, NoH); Result.AddLobeWithMIS(DiffuseSpecularScale.x * Diffuse * ShadowTerminator, NoL / PI, Data.LobePdf.x); } { float2 GGXResult0 = GGXEvalReflection(L, V, H, Data.Alpha0); const float2 GGXResult1 = GGXEvalReflection(L, V, H, Data.Alpha1); const float3 FTint = SubstrateSpecularTint(Payload, NoV, NoL, VoH, NoH); #if SUBSTRATE_FRESNEL_F82 const float3 F = Data.Fuzz.Attenuation * F_AdobeF82(Data.F0, Data.F90, VoH) * FTint; #else const float3 F = Data.Fuzz.Attenuation * F_Schlick(Data.F0, Data.F90, VoH) * FTint; #endif SubstrateGlint(Payload, V, L, Data.Alpha0, GGXResult0.y /*Pdf*/, GGXResult0.x /*Weight*/); Result.AddLobeWithMIS(DiffuseSpecularScale.y * (1.0 - Payload.RoughnessData.z) * F * GGXResult0.x * Data.Spec0W, GGXResult0.y, Data.LobePdf.y); Result.AddLobeWithMIS(DiffuseSpecularScale.y * ( Payload.RoughnessData.z) * F * GGXResult1.x * Data.Spec1W, GGXResult1.y, Data.LobePdf.z); } { // Cloth Lobe const float4 ClothResult = Data.Fuzz.Eval(L, V, H, Payload.FuzzColor); const float ClothPdf = ClothResult.w; Result.AddLobeWithMIS(DiffuseSpecularScale.y * ClothResult.xyz * ShadowTerminator, ClothPdf, Data.LobePdf.w); } Result.Weight *= SubstrateLobeWeight(Payload, NoL); return Result; } struct FSubstrateClearCoatData { float3x3 BaseBasis; float3x3 CoatBasis; float3 BaseV; float3 CoatV; float DiffuseWeight; float3 F0; float3 F90; float2 BaseAlpha; float2 CoatAlpha; float CoatAttenuation; FBxDFEnergyTermsRGB BaseSpec; FBxDFEnergyTermsA CoatSpec; FSubstrateSheenData Fuzz; float3 LobeCdf; float4 LobePdf; void PrepareBSDF(float3 V_World, FPathTracingPayload Payload, bool bUseSSSColor = false) { float3 BaseWorldNormal = Payload.WorldNormal; #if CLEAR_COAT_BOTTOM_NORMAL if ((Payload.BottomNormalOct16bits & 0xFFFF) != 0) { const float2 BottomNormalOct = UnpackBottomNormalOct(Payload.BottomNormalOct16bits); BaseWorldNormal = SubstrateGetSimpleCoatBottomNormal(BottomNormalOct, Payload.WorldNormal); } #endif BaseBasis = GetGGXBasis(Payload.RoughnessData.x, Payload.Anisotropy, BaseWorldNormal, Payload.WorldTangent, BaseAlpha); BaseAlpha = GetGGXAlpha(Payload.RoughnessData.x, Payload.Anisotropy); CoatBasis = GetGGXBasis(Payload.RoughnessData.y, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, CoatAlpha); CoatAlpha = GetGGXAlpha(Payload.RoughnessData.y, Payload.Anisotropy); BaseV = mul(BaseBasis, V_World); CoatV = mul(CoatBasis, V_World); // make sure F0=0.0 fades off the specular lobe completely F0 = Payload.SpecularColor; F90 = Payload.SpecularEdgeColor * F0RGBToMicroOcclusion(F0); const float BaseNoV = saturate(BaseV.z); const float CoatNoV = saturate(CoatV.z); // SUBSTRATE_TODO: Add support for F82 model? BaseSpec = ComputeGGXSpecEnergyTermsRGB(Payload.RoughnessData.x, BaseNoV, F0, F90); CoatSpec = ComputeGGXSpecEnergyTermsA(Payload.RoughnessData.y, CoatNoV, CLEAR_COAT_F0); Fuzz.Prepare(CoatNoV, Payload.FuzzRoughness, Payload.FuzzAmount); CoatAttenuation = 1.0 - Payload.RoughnessData.z * CoatSpec.E; DiffuseWeight = Fuzz.Attenuation * CoatAttenuation * (1.0 - Luminance(BaseSpec.E)); const float3 BaseSpecAlbedo = Fuzz.Attenuation * CoatAttenuation * BaseSpec.E; const float CoatSpecAlbedo = Fuzz.Attenuation * Payload.RoughnessData.z * CoatSpec.E; const float3 FuzzAlbedo = Fuzz.Scale * Payload.FuzzColor; // Now prepare a cdf/pdf for lobe selection float3 MaxLobeWeight = Payload.GetMaxLobeWeight(); LobeCdf = LobeSelectionCdf( MaxLobeWeight * DiffuseWeight * (bUseSSSColor ? Payload.SubsurfaceColor : Payload.DiffuseColor), MaxLobeWeight * BaseSpecAlbedo, MaxLobeWeight * CoatSpecAlbedo, MaxLobeWeight * FuzzAlbedo); LobePdf = LobeSelectionPdf(LobeCdf); } }; FMaterialSample SubstrateClearCoat_SampleMaterial( float3 V_World, FPathTracingPayload Payload, float3 RandSample ) { FSubstrateClearCoatData Data = (FSubstrateClearCoatData)0; Data.PrepareBSDF(V_World, Payload); float OutRoughness = 1; float3 OutNormal = Payload.WorldNormal; float3 OutL = 0; float3 OutV = 0; float3 OutH = 0; float3x3 OutBasis; const bool bSampledDiffuse = RandSample.x < Data.LobeCdf.x; const bool bSampledSpecular = RandSample.x < Data.LobeCdf.z; if (bSampledDiffuse) { RandSample.x = RescaleRandomNumber(RandSample.x, 0.0, Data.LobeCdf.x); // diffuse lobe OutL = CosineSampleHemisphere(RandSample.xy).xyz; OutV = Data.BaseV; OutH = normalize(OutL + OutV); OutBasis = Data.BaseBasis; } else if (bSampledSpecular) { // specular lobes const bool bUseBaseSpec = RandSample.x < Data.LobeCdf.y; if (bUseBaseSpec) { RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.x, Data.LobeCdf.y); OutRoughness = Payload.RoughnessData.x; const float2 BottomNormalOct = UnpackBottomNormalOct(Payload.BottomNormalOct16bits); OutNormal = SubstrateGetSimpleCoatBottomNormal(BottomNormalOct, Payload.WorldNormal); OutBasis = Data.BaseBasis; OutV = Data.BaseV; } else { RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.y, Data.LobeCdf.z); OutRoughness = Payload.RoughnessData.y; OutBasis = Data.CoatBasis; OutV = Data.CoatV; } OutV = bUseBaseSpec ? Data.BaseV : Data.CoatV; OutH = ImportanceSampleVisibleGGX(RandSample.xy, bUseBaseSpec ? Data.BaseAlpha : Data.CoatAlpha, OutV).xyz; OutL = reflect(-OutV, OutH); if (OutL.z <= 0) { // invalid output direction, exit early return NullMaterialSample(); } } else { // cloth lobe RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.z, 1.0); OutV = Data.CoatV; OutL = Data.Fuzz.Sample(RandSample.xy); OutH = normalize(OutL + OutV); OutBasis = Data.CoatBasis; } // transform to world space float3 L = OutL; float3 V = OutV; float3 H = OutH; const float3 L_World = normalize(mul(L, OutBasis)); const float3 N_World = OutNormal; const float NoV = saturate(V.z); const float NoL = saturate(L.z); const float VoH = saturate(dot(V, H)); const float NoH = saturate(H.z); FMaterialSample Result = CreateMaterialSample(L_World, 0.0, 0.0, 1.0, OutRoughness, PATHTRACER_SCATTER_DIFFUSE); const float Metallic = F0RGBToMetallic(Payload.SpecularColor); const float3 BaseColor = lerp(Payload.DiffuseColor, Payload.SpecularColor, Metallic); const float3 Transmission = lerp(1.0, SimpleClearCoatTransmittance(NoL, NoV, Metallic, BaseColor), Payload.RoughnessData.z); const float DiffPdf = NoL / PI; const float2 BaseGGXResult = GGXEvalReflection(L, V, H, Data.BaseAlpha); const float2 CoatGGXResult = GGXEvalReflection(L, V, H, Data.CoatAlpha); const float BaseSpecPdf = BaseGGXResult.y; const float CoatSpecPdf = CoatGGXResult.y; const float4 ClothResult = Data.Fuzz.Eval(L, V, H, Payload.FuzzColor); const float ClothPdf = ClothResult.w; const float ShadowTerminator = ShadowTerminatorTerm(L_World, N_World, Payload.WorldSmoothNormal); if (bSampledDiffuse) { const float3 Diffuse = Transmission * Data.DiffuseWeight * GetPathTracingDiffuseModel(Payload.DiffuseColor, Payload.RoughnessData.x, NoV, NoL, VoH, NoH); Result.AddLobeWithMIS(Diffuse * ShadowTerminator, NoL / PI, Data.LobePdf.x); Result.Pdf += Data.LobePdf.y * BaseSpecPdf; Result.Pdf += Data.LobePdf.z * CoatSpecPdf; Result.Pdf += Data.LobePdf.w * ClothPdf; } else if (bSampledSpecular) { const float3 FTint = SubstrateSpecularTint(Payload, NoV, NoL, VoH, NoH); #if SUBSTRATE_FRESNEL_F82 // SUBSTRATE_TODO: Need to tune the logic for specular micro-occlusion ... const float3 FBase = Data.Fuzz.Attenuation * F_AdobeF82(Data.F0, Data.F90, VoH) * FTint; #else const float3 FBase = Data.Fuzz.Attenuation * F_Schlick(Data.F0, Data.F90, VoH) * FTint; #endif const float3 FCoat = Payload.RoughnessData.z * ClearCoatFresnel(VoH) * FTint; Result.AddLobeWithMIS(Data.CoatAttenuation * FBase * BaseGGXResult.x * Data.BaseSpec.W * Transmission, BaseSpecPdf, Data.LobePdf.y); Result.AddLobeWithMIS(Data.Fuzz.Attenuation * FCoat * CoatGGXResult.x * Data.CoatSpec.W , CoatSpecPdf, Data.LobePdf.z); Result.Pdf += Data.LobePdf.x * DiffPdf; Result.Pdf += Data.LobePdf.w * ClothPdf; Result.ScatterType = PATHTRACER_SCATTER_SPECULAR; } else { // Cloth Lobe Result.AddLobeWithMIS(ClothResult.xyz * ShadowTerminator, ClothPdf, Data.LobePdf.w); Result.ScatterType = PATHTRACER_SCATTER_SPECULAR; Result.Pdf += Data.LobePdf.x * DiffPdf; Result.Pdf += Data.LobePdf.y * BaseSpecPdf; Result.Pdf += Data.LobePdf.z * CoatSpecPdf; } Result.Weight *= SubstrateLobeWeight(Payload, NoL); return Result; } FMaterialEval SubstrateClearCoat_EvalMaterial( float3 V_World, float3 L_World, FPathTracingPayload Payload, float2 DiffuseSpecularScale ) { FSubstrateClearCoatData Data = (FSubstrateClearCoatData)0; Data.PrepareBSDF(V_World, Payload); // move vectors into right shading frame const float3 BaseV = Data.BaseV; const float3 BaseL = mul(Data.BaseBasis, L_World); const float3 BaseH = normalize(BaseV + BaseL); const float BaseNoV = saturate(BaseV.z); const float BaseNoL = saturate(BaseL.z); const float BaseVoH = saturate(dot(BaseV, BaseH)); const float BaseNoH = saturate(BaseH.z); const float3 CoatV = Data.CoatV; const float3 CoatL = mul(Data.CoatBasis, L_World); const float3 CoatH = normalize(CoatV + CoatL); const float CoatNoV = saturate(CoatV.z); const float CoatNoL = saturate(CoatL.z); const float CoatVoH = saturate(dot(CoatV, CoatH)); const float CoatNoH = saturate(CoatH.z); FMaterialEval Result = NullMaterialEval(); const float Metallic = F0RGBToMetallic(Payload.SpecularColor); const float3 BaseColor = lerp(Payload.DiffuseColor, Payload.SpecularColor, Metallic); const float3 Transmission = lerp(1.0, SimpleClearCoatTransmittance(CoatNoL, CoatNoV, Metallic, BaseColor), Payload.RoughnessData.z); const float BasehadowTerminator = ShadowTerminatorTerm(L_World, Data.BaseBasis[2], Payload.WorldSmoothNormal); const float CoatShadowTerminator = ShadowTerminatorTerm(L_World, Data.CoatBasis[2], Payload.WorldSmoothNormal); { const float3 Diffuse = Transmission * Data.DiffuseWeight * GetPathTracingDiffuseModel(Payload.DiffuseColor, Payload.RoughnessData.x, BaseNoV, BaseNoL, BaseVoH, BaseNoH); Result.AddLobeWithMIS(DiffuseSpecularScale.x * Diffuse * BasehadowTerminator, BaseNoL / PI, Data.LobePdf.x); } { const float2 BaseGGXResult = GGXEvalReflection(BaseL, BaseV, BaseH, Data.BaseAlpha); const float2 CoatGGXResult = GGXEvalReflection(CoatL, CoatV, CoatH, Data.CoatAlpha); const float BaseSpecPdf = BaseGGXResult.y; const float CoatSpecPdf = CoatGGXResult.y; const float3 FTint = SubstrateSpecularTint(Payload, CoatNoV, CoatNoL, CoatVoH, CoatNoH); #if SUBSTRATE_FRESNEL_F82 // SUBSTRATE_TODO: Need to tune the logic for specular micro-occlusion ... const float3 FBase = Data.Fuzz.Attenuation * F_AdobeF82(Data.F0, Data.F90, CoatVoH) * FTint; #else const float3 FBase = Data.Fuzz.Attenuation * F_Schlick(Data.F0, Data.F90, CoatVoH) * FTint; #endif const float3 FCoat = Payload.RoughnessData.z * ClearCoatFresnel(CoatVoH) * FTint; Result.AddLobeWithMIS(Data.CoatAttenuation * FBase * BaseGGXResult.x * Data.BaseSpec.W * Transmission, BaseSpecPdf, Data.LobePdf.y); Result.AddLobeWithMIS(Data.Fuzz.Attenuation * FCoat * CoatGGXResult.x * Data.CoatSpec.W , CoatSpecPdf, Data.LobePdf.z); } { // Cloth Lobe const float4 ClothResult = Data.Fuzz.Eval(CoatL, CoatV, CoatH, Payload.FuzzColor); const float ClothPdf = ClothResult.w; Result.AddLobeWithMIS(DiffuseSpecularScale.y * ClothResult.xyz * CoatShadowTerminator, ClothPdf, Data.LobePdf.w); } Result.Weight *= SubstrateLobeWeight(Payload, CoatNoL); return Result; }