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

162 lines
5.5 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingThinGlass.ush: Microfacet BSDF for thin glass
===============================================================================================*/
#pragma once
#include "PathTracingMaterialCommon.ush"
#include "PathTracingFresnel.ush"
#include "PathTracingGlossy.ush"
#include "PathTracingEnergyConservation.ush"
struct FRoughThinGlassData
{
float F0, F90;
float3x3 Basis;
float2 AlphaR, AlphaT;
float3 V;
FBxDFEnergyTermsA SpecR;
FBxDFEnergyTermsA SpecT;
float NudgeE;
float LobeProb; // R or T
};
FRoughThinGlassData PrepareRoughThinGlassData(FPathTracingPayload Payload, float3 V_World)
{
FRoughThinGlassData Data = (FRoughThinGlassData)0;
Data.F0 = F0RGBToF0(Payload.SpecularColor);
Data.F90 = saturate(50.0 * Data.F0);
const float RoughnessR = Payload.Roughness;
const float RoughnessT = ComputeThinTransmittedRoughness(Payload.Roughness, Payload.Ior);
Data.Basis = GetGGXBasis(RoughnessR, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, Data.AlphaR);
Data.AlphaT = GetGGXAlpha(RoughnessT, Payload.Anisotropy);
Data.V = mul(Data.Basis, V_World);
const float NoV = saturate(Data.V.z);
// because the roughnesses may not match, we need to measure the response for each individually, taking into account the reflected and transmitted fresnel respectively
// NOTE: 1.0 - F_Schlick(NoV, F0, F90) == F_Schlick(NoV, 1.0 - F0, 1.0 - F90)
Data.SpecR = ComputeGGXSpecEnergyTermsA(RoughnessR, NoV, Data.F0, Data.F90);
Data.SpecT = ComputeGGXSpecEnergyTermsA(RoughnessT, NoV, 1.0 - Data.F0, 1.0 - Data.F90);
Data.NudgeE = rcp(Data.SpecR.E + Data.SpecT.E);
// Figure out probability of glass reflection vs glass transmission
// Approximate the transmission as just a plain tint by slab color because we can't depend on the half-vector for lobe selection
// If we don't have any refraction, we will always pick the reflection lobe
Data.LobeProb = Payload.HasRefraction() ? LobeSelectionProb(Data.NudgeE * Data.SpecR.E, Data.NudgeE * Data.SpecT.E * Payload.GetTransmittanceColor()) : 1.0;
return Data;
}
FMaterialEval RoughThinGlass_EvalMaterial(
float3 V_World,
float3 L_World,
FPathTracingPayload Payload,
float2 DiffuseSpecularScale
)
{
const FRoughThinGlassData Data = PrepareRoughThinGlassData(Payload, V_World);
// move vectors into right shading frame
float3 V = Data.V;
float3 L = mul(Data.Basis, L_World);
if (V.z <= 0)
{
// invalid input
return NullMaterialEval();
}
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);
const FThinSlabWeights SlabResult = ComputeThinSlabWeights(Payload.GetTransmittanceColor(), VoH, Payload.Ior, Data.F0);
const float2 GGXResult = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR : Data.AlphaT);
const float GlassPdf = GGXResult.y;
float3 GlassWeight = Payload.BSDFOpacity * Data.NudgeE * GGXResult.x * DiffuseSpecularScale.y;
if (bIsReflection)
{
GlassWeight *= Data.SpecR.W * SlabResult.Reflected;
}
else
{
GlassWeight *= Data.SpecT.W * SlabResult.Transmitted;
}
FMaterialEval Result = NullMaterialEval();
// Transmission lobe will be automatically ignored when there is no refraction because we made LobeProbe==1 in that case
Result.AddLobeWithMIS(GlassWeight, GlassPdf, bIsReflection ? Data.LobeProb : 1.0 - Data.LobeProb);
return Result;
}
FMaterialSample RoughThinGlass_SampleMaterial(
float3 V_World,
FPathTracingPayload Payload,
float3 RandSample)
{
const FRoughThinGlassData Data = PrepareRoughThinGlassData(Payload, V_World);
const float3 V = Data.V;
const float NoV = saturate(V.z);
if (NoV == 0)
{
// invalid grazing angle
return NullMaterialSample();
}
// Specular/Glass lobes
const bool bIsReflection = RandSample.x < Data.LobeProb;
if (bIsReflection)
{
RandSample.x = RescaleRandomNumber(RandSample.x, 0, Data.LobeProb);
}
else
{
RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeProb, 1.0);
}
const float3 H = ImportanceSampleVisibleGGX(RandSample.xy, bIsReflection ? Data.AlphaR : Data.AlphaT, V).xyz;
const float NoH = saturate(H.z);
const float VoH = saturate(dot(V, H));
const float3 L = reflect(-V, H);
if (L.z <= 0)
{
// invalid output direction, exit early
return NullMaterialSample();
}
const float NoL = saturate(L.z);
FThinSlabWeights SlabResult = ComputeThinSlabWeights(Payload.GetTransmittanceColor(), VoH, Payload.Ior, Data.F0);
const float2 GGXResult = GGXEvalReflection(L, V, H, bIsReflection ? Data.AlphaR : Data.AlphaT);
FMaterialSample Result = NullMaterialSample();
Result.Roughness = Payload.Roughness; // use common roughness for all lobes, even though transmission is squeezed
const float3 GlassWeight = Payload.BSDFOpacity * Data.NudgeE * GGXResult.x * (bIsReflection ? Data.SpecR.W * SlabResult.Reflected : Data.SpecT.W * SlabResult.Transmitted);
const float GlassPdf = GGXResult.y;
Result.AddLobeWithMIS(GlassWeight, GlassPdf, bIsReflection ? Data.LobeProb : 1.0 - Data.LobeProb);
Result.PositionBiasSign = bIsReflection ? 1.0 : -1.0;
Result.ScatterType = bIsReflection ? PATHTRACER_SCATTER_SPECULAR : PATHTRACER_SCATTER_REFRACT;
Result.Direction = mul(float3(L.xy, Result.PositionBiasSign * L.z), Data.Basis); // flip reflection to other side for refraction
Result.Direction = normalize(Result.Direction);
return Result;
}