290 lines
10 KiB
HLSL
290 lines
10 KiB
HLSL
// 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 FSubstrateThinGlassSlabData
|
|
{
|
|
float3x3 Basis;
|
|
float3 V;
|
|
|
|
float2 RoughnessT;
|
|
|
|
float DiffuseWeight;
|
|
float F0, F90;
|
|
float Eta;
|
|
float3 TransmissionColor;
|
|
float2 AlphaR0, AlphaT0;
|
|
float2 AlphaR1, AlphaT1;
|
|
|
|
float SpecR0W, SpecR1W;
|
|
float SpecT0W, SpecT1W;
|
|
|
|
FSubstrateSheenData Fuzz;
|
|
|
|
float3 LobeCdf;
|
|
float4 LobePdf;
|
|
|
|
void PrepareBSDF(float3 V_World, FPathTracingPayload Payload)
|
|
{
|
|
float2 RoughnessR = Payload.RoughnessData.xy;
|
|
RoughnessT.x = ComputeThinTransmittedRoughness(RoughnessR.x, Payload.Ior);
|
|
RoughnessT.y = ComputeThinTransmittedRoughness(RoughnessR.y, Payload.Ior);
|
|
|
|
Basis = GetGGXBasis(RoughnessR.x, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, AlphaR0);
|
|
AlphaR1 = GetGGXAlpha(RoughnessR.y, Payload.Anisotropy);
|
|
|
|
AlphaT0 = GetGGXAlpha(RoughnessT.x, Payload.Anisotropy);
|
|
AlphaT1 = GetGGXAlpha(RoughnessT.y, Payload.Anisotropy);
|
|
|
|
V = mul(Basis, V_World);
|
|
|
|
F0 = F0RGBToF0(Payload.SpecularColor);
|
|
F90 = F0ToMicroOcclusion(F0);
|
|
TransmissionColor = Payload.GetTransmittanceColor();
|
|
|
|
const float NoV = saturate(V.z);
|
|
FBxDFEnergyTermsA SpecR0 = ComputeGGXSpecEnergyTermsA(RoughnessR.x, NoV, F0, F90);
|
|
FBxDFEnergyTermsA SpecR1 = ComputeGGXSpecEnergyTermsA(RoughnessR.y, NoV, F0, F90);
|
|
FBxDFEnergyTermsA SpecT0 = ComputeGGXSpecEnergyTermsA(RoughnessT.x, NoV, 1.0 - F0, 1.0 - F90);
|
|
FBxDFEnergyTermsA SpecT1 = ComputeGGXSpecEnergyTermsA(RoughnessT.y, NoV, 1.0 - F0, 1.0 - F90);
|
|
float2 NudgeE = float2(
|
|
rcp(SpecR0.E + SpecT0.E),
|
|
rcp(SpecR1.E + SpecT1.E));
|
|
|
|
Fuzz.Prepare(V, Payload.FuzzRoughness, Payload.FuzzAmount);
|
|
|
|
|
|
|
|
const float3 GlassR = Fuzz.Attenuation * lerp(SpecR0.E * NudgeE.x, SpecR1.E * NudgeE.y, Payload.RoughnessData.z);
|
|
const float3 GlassT = Fuzz.Attenuation * lerp(SpecT0.E * NudgeE.x, SpecT1.E * NudgeE.y, Payload.RoughnessData.z);
|
|
const float3 FuzzAlbedo = Fuzz.Scale * Payload.FuzzColor;
|
|
|
|
DiffuseWeight = Fuzz.Attenuation * (1.0 - Luminance(GlassR));
|
|
|
|
SpecR0W = SpecR0.W * NudgeE.x;
|
|
SpecR1W = SpecR1.W * NudgeE.y;
|
|
SpecT0W = SpecT0.W * NudgeE.x;
|
|
SpecT1W = SpecT1.W * NudgeE.y;
|
|
|
|
// Now prepare a cdf/pdf for lobe selection
|
|
// NOTE: we put T first so that the reflected lobe (R and fuzz) are consecutive in the PDF
|
|
float3 MaxLobeWeight = Payload.GetMaxLobeWeight();
|
|
LobeCdf = LobeSelectionCdf(
|
|
MaxLobeWeight * DiffuseWeight * Payload.DiffuseColor,
|
|
MaxLobeWeight * FuzzAlbedo,
|
|
MaxLobeWeight * GlassR,
|
|
Payload.Ior > 0.0 ? MaxLobeWeight * GlassT : 0.0);
|
|
LobePdf = LobeSelectionPdf(LobeCdf);
|
|
}
|
|
};
|
|
|
|
|
|
FMaterialSample SubstrateThinGlass_SampleMaterial(
|
|
float3 V_World,
|
|
FPathTracingPayload Payload,
|
|
float3 RandSample
|
|
)
|
|
{
|
|
FSubstrateThinGlassSlabData Data = (FSubstrateThinGlassSlabData)0;
|
|
Data.PrepareBSDF(V_World, Payload);
|
|
|
|
float3 L = 0, H = 0, V = Data.V;
|
|
float OutRoughness = 1;
|
|
|
|
bool bSampleDiffuse = false, bSampleCloth = false, bSampleGlass = false, bIsReflection = RandSample.x < Data.LobeCdf.z;
|
|
|
|
if (RandSample.x < Data.LobeCdf.x)
|
|
{
|
|
bSampleDiffuse = true;
|
|
RandSample.x = RescaleRandomNumber(RandSample.x, 0.0, Data.LobeCdf.x);
|
|
// diffuse lobe
|
|
L = CosineSampleHemisphere(RandSample.xy).xyz;
|
|
H = normalize(L + V);
|
|
}
|
|
else if (RandSample.x < Data.LobeCdf.y)
|
|
{
|
|
bSampleCloth = true;
|
|
// cloth lobe
|
|
RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.x, Data.LobeCdf.y);
|
|
L = Data.Fuzz.Sample(RandSample.xy);
|
|
H = normalize(L + V);
|
|
}
|
|
else
|
|
{
|
|
bSampleGlass = true;
|
|
// Two-phase lobe selection:
|
|
// First use the overall CDF to pick between transmission and reflection ...
|
|
RandSample.x = RescaleRandomNumber(RandSample.x,
|
|
bIsReflection ? Data.LobeCdf.y : Data.LobeCdf.z,
|
|
bIsReflection ? Data.LobeCdf.z : 1.0);
|
|
|
|
// Then use the roughness blend to pick one of the two possible roughness values
|
|
const bool bUseRoughness1 = RandSample.x < Payload.RoughnessData.z;
|
|
RandSample.x = RescaleRandomNumber(RandSample.x,
|
|
bUseRoughness1 ? 0.0 : Payload.RoughnessData.z,
|
|
bUseRoughness1 ? Payload.RoughnessData.z : 1.0);
|
|
|
|
//
|
|
float2 Alpha = bIsReflection ? (bUseRoughness1 ? Data.AlphaR1 : Data.AlphaR0) : (bUseRoughness1 ? Data.AlphaT1 : Data.AlphaT0);
|
|
OutRoughness = bIsReflection ?
|
|
(bUseRoughness1 ? Payload.RoughnessData.y : Payload.RoughnessData.x) :
|
|
(bUseRoughness1 ? Data.RoughnessT.y : Data.RoughnessT.x);
|
|
|
|
H = ImportanceSampleVisibleGGX(RandSample.xy, Alpha, V).xyz;
|
|
L = reflect(-V, H);
|
|
if (L.z <= 0)
|
|
{
|
|
// invalid output direction, exit early
|
|
return NullMaterialSample();
|
|
}
|
|
|
|
}
|
|
|
|
// transform to world space
|
|
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);
|
|
|
|
const float PositionBiasSign = bIsReflection ? 1.0 : -1.0;
|
|
const float3 L_World = normalize(mul(float3(L.xy, PositionBiasSign * L.z), Data.Basis)); // flip reflection to other side if we are refracting
|
|
FMaterialSample Result = CreateMaterialSample(L_World, 0.0, 0.0, PositionBiasSign, OutRoughness, bSampleDiffuse ? PATHTRACER_SCATTER_DIFFUSE : (bIsReflection ? PATHTRACER_SCATTER_SPECULAR : PATHTRACER_SCATTER_REFRACT));
|
|
|
|
// Specular Lobes (either reflected or transmitted)
|
|
const float2 GGXResult0 = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR0 : Data.AlphaT0);
|
|
const float2 GGXResult1 = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR1 : Data.AlphaT1);
|
|
const float4 ClothResult = Data.Fuzz.Eval(L, V, H, Payload.FuzzColor);
|
|
const float ClothPdf = ClothResult.w;
|
|
const float DiffusePdf = NoL / PI;
|
|
if (bSampleGlass)
|
|
{
|
|
FThinSlabWeights SlabResult = ComputeThinSlabWeights(Data.TransmissionColor, VoH, Payload.Ior, Data.F0);
|
|
SlabResult.Reflected *= SubstrateSpecularTint(Payload, NoV, NoL, VoH, NoH);
|
|
|
|
const float3 SlabTint = bIsReflection ? SlabResult.Reflected : SlabResult.Transmitted;
|
|
const float SlabW0 = bIsReflection ? Data.SpecR0W : Data.SpecT0W;
|
|
const float SlabW1 = bIsReflection ? Data.SpecR1W : Data.SpecT1W;
|
|
const float LobePdf = bIsReflection ? Data.LobePdf.z : Data.LobePdf.w;
|
|
|
|
Result.AddLobeWithMIS(Data.Fuzz.Attenuation * (1 - Payload.RoughnessData.z) * GGXResult0.x * SlabW0 * SlabTint, GGXResult0.y, LobePdf * (1 - Payload.RoughnessData.z));
|
|
Result.AddLobeWithMIS(Data.Fuzz.Attenuation * ( Payload.RoughnessData.z) * GGXResult1.x * SlabW1 * SlabTint, GGXResult1.y, LobePdf * ( Payload.RoughnessData.z));
|
|
|
|
if (bIsReflection)
|
|
{
|
|
Result.Pdf += Data.LobePdf.x * DiffusePdf;
|
|
Result.Pdf += Data.LobePdf.y * ClothPdf;
|
|
}
|
|
}
|
|
else if (bSampleDiffuse)
|
|
{
|
|
// Diffuse Lobe
|
|
Result.AddLobeWithMIS(Data.DiffuseWeight * Payload.DiffuseColor, DiffusePdf, Data.LobePdf.x);
|
|
Result.Pdf += Data.LobePdf.y * ClothPdf;
|
|
Result.Pdf += Data.LobePdf.z * (1.0 - Payload.RoughnessData.z) * GGXResult0.y;
|
|
Result.Pdf += Data.LobePdf.z * ( Payload.RoughnessData.z) * GGXResult1.y;
|
|
}
|
|
else
|
|
{
|
|
// Cloth Lobe
|
|
const float ShadowTerminator = ShadowTerminatorTerm(L_World, N_World, Payload.WorldSmoothNormal);
|
|
Result.AddLobeWithMIS(ClothResult.xyz * ShadowTerminator, ClothPdf, Data.LobePdf.y);
|
|
|
|
Result.Pdf += Data.LobePdf.x * DiffusePdf;
|
|
Result.Pdf += Data.LobePdf.z * (1.0 - Payload.RoughnessData.z) * GGXResult0.y;
|
|
Result.Pdf += Data.LobePdf.z * ( Payload.RoughnessData.z) * GGXResult1.y;
|
|
}
|
|
|
|
|
|
if (bIsReflection)
|
|
{
|
|
Result.Weight *= SubstrateLobeWeight(Payload, NoL);
|
|
}
|
|
else
|
|
{
|
|
Result.Weight *= Payload.BSDFOpacity * Payload.WeightV;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
|
|
FMaterialEval SubstrateThinGlass_EvalMaterial(
|
|
float3 V_World,
|
|
float3 L_World,
|
|
FPathTracingPayload Payload,
|
|
float2 DiffuseSpecularScale
|
|
)
|
|
{
|
|
FSubstrateThinGlassSlabData Data = (FSubstrateThinGlassSlabData)0;
|
|
Data.PrepareBSDF(V_World, Payload);
|
|
|
|
const float3 N_World = Payload.WorldNormal;
|
|
|
|
// move vectors into right shading frame
|
|
const float3 V = Data.V;
|
|
float3 L = mul(Data.Basis, L_World);
|
|
|
|
const bool bIsReflection = L.z >= 0;
|
|
L.z = abs(L.z); // push L to the same side as V
|
|
|
|
const float NoL = saturate(L.z);
|
|
const float NoV = saturate(V.z);
|
|
const float3 H = normalize(L + V);
|
|
const float VoH = saturate(dot(V, H));
|
|
const float NoH = saturate(H.z);
|
|
|
|
|
|
// Diffuse Lobe
|
|
const float DiffusePdf = NoL / PI;
|
|
|
|
// Specular Lobes (either reflected or transmitted)
|
|
const float2 GGXResult0 = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR0 : Data.AlphaT0);
|
|
const float2 GGXResult1 = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR1 : Data.AlphaT1);
|
|
FThinSlabWeights SlabResult = ComputeThinSlabWeights(Data.TransmissionColor, VoH, Payload.Ior, Data.F0);
|
|
SlabResult.Reflected *= SubstrateSpecularTint(Payload, NoV, NoL, VoH, NoH);
|
|
|
|
const float3 SlabTint = bIsReflection ? SlabResult.Reflected : SlabResult.Transmitted;
|
|
const float SlabW0 = (bIsReflection ? Data.SpecR0W : Data.SpecT0W);
|
|
const float SlabW1 = (bIsReflection ? Data.SpecR1W : Data.SpecT1W);
|
|
|
|
const float LobePdf = bIsReflection ? Data.LobePdf.z : Data.LobePdf.w;
|
|
|
|
FMaterialEval Result = NullMaterialEval();
|
|
|
|
Result.AddLobeWithMIS(DiffuseSpecularScale.y * Data.Fuzz.Attenuation * (1 - Payload.RoughnessData.z) * GGXResult0.x * SlabW0 * SlabTint, GGXResult0.y, LobePdf * (1 - Payload.RoughnessData.z));
|
|
Result.AddLobeWithMIS(DiffuseSpecularScale.y * Data.Fuzz.Attenuation * ( Payload.RoughnessData.z) * GGXResult1.x * SlabW1 * SlabTint, GGXResult1.y, LobePdf * ( Payload.RoughnessData.z));
|
|
|
|
|
|
if (bIsReflection)
|
|
{
|
|
{
|
|
// Diffuse Lobe
|
|
Result.AddLobeWithMIS(DiffuseSpecularScale.x * Data.DiffuseWeight * Payload.DiffuseColor, DiffusePdf, Data.LobePdf.x);
|
|
}
|
|
{
|
|
// Cloth Lobe
|
|
const float ShadowTerminator = ShadowTerminatorTerm(L_World, N_World, Payload.WorldSmoothNormal);
|
|
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.y);
|
|
}
|
|
Result.Weight *= SubstrateLobeWeight(Payload, NoL);
|
|
}
|
|
else
|
|
{
|
|
Result.Weight *= Payload.BSDFOpacity * Payload.WeightV;
|
|
}
|
|
return Result;
|
|
}
|