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

193 lines
6.0 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingCloth.ush: Path tracing BRDF model for cloth materials
===============================================================================================*/
#pragma once
#include "PathTracingFresnel.ush"
#include "PathTracingEnergyConservation.ush"
struct FClothData
{
float3x3 Basis;
float2 Alpha;
float3 V;
FBxDFEnergyTermsRGB Spec;
FBxDFEnergyTermsA Fuzz;
float3 DiffWeight;
float2 LobeCdf;
float3 LobePdf;
};
FClothData PrepareClothData(FPathTracingPayload Payload, float3 V_World)
{
FClothData Data = (FClothData)0;
Data.Basis = GetGGXBasis(Payload.Roughness, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, Data.Alpha);
Data.V = mul(Data.Basis, V_World);
const float ClothBlend = Payload.GetClothAmount();
const float NoV = saturate(Data.V.z);
Data.Spec = ComputeGGXSpecEnergyTermsRGB(Payload.Roughness, NoV, Payload.SpecularColor);
Data.Fuzz = ComputeClothEnergyTermsA(Payload.Roughness, NoV);
const float SpecEp = ComputeEnergyPreservation(Data.Spec);
const float FuzzEp = ComputeEnergyPreservation(Data.Fuzz);
Data.DiffWeight = lerp(SpecEp, FuzzEp, ClothBlend) * Payload.DiffuseColor;
Data.LobeCdf = LobeSelectionCdf(Data.DiffWeight, ClothBlend * Data.Fuzz.E, (1.0 - ClothBlend) * Data.Spec.E);
Data.LobePdf = LobeSelectionPdf(Data.LobeCdf);
return Data;
}
FMaterialEval Cloth_EvalMaterial(
float3 V_World,
float3 L_World,
FPathTracingPayload Payload,
float2 DiffuseSpecularScale
)
{
const FClothData Data = PrepareClothData(Payload, V_World);
// 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 NoL = saturate(L.z);
const float NoV = saturate(V.z);
const float VoH = saturate(dot(V, H));
const float NoH = saturate(H.z);
FMaterialEval Result = NullMaterialEval();
// Diffuse Lobe
const float ShadowTerminator = ShadowTerminatorTerm(L_World, Payload.WorldNormal, Payload.WorldSmoothNormal);
Result.AddLobeWithMIS(Data.DiffWeight * ShadowTerminator * DiffuseSpecularScale.x, NoL / PI, Data.LobePdf.x);
const float ClothBlend = Payload.GetClothAmount();
// Cloth Lobe (see ClothBxDF - it is counted as specular)
const float DCloth = D_InvGGX(Pow4(Payload.Roughness), NoH);
const float3 FCloth = F_Schlick(Payload.GetClothColor(), VoH);
const float3 ClothWeight = NoL > 0 && NoV > 0 ? (PI * ClothBlend * DCloth * Vis_Cloth(NoV, NoL)) * FCloth * ComputeEnergyConservation(Data.Fuzz) : 0.0;
Result.AddLobeWithMIS(ClothWeight * ShadowTerminator * DiffuseSpecularScale.y, NoL / PI, Data.LobePdf.y);
// Specular lobe
const float2 GGXResult = GGXEvalReflection(L, V, H, Data.Alpha);
const float3 F = F_Schlick(Payload.SpecularColor, VoH);
const float3 SpecWeight = (1 - ClothBlend) * F * GGXResult.x * Data.Spec.W;
const float SpecPdf = GGXResult.y;
Result.AddLobeWithMIS(SpecWeight * DiffuseSpecularScale.y, SpecPdf, Data.LobePdf.z);
Result.Weight *= Payload.BSDFOpacity;
return Result;
}
FMaterialSample Cloth_SampleMaterial(
float3 V_World,
FPathTracingPayload Payload,
float3 RandSample
)
{
const FClothData Data = PrepareClothData(Payload, V_World);
const float3 V = Data.V;
const float NoV = saturate(Data.V.z);
// Randomly choose to sample diffuse+cloth or specular lobe
// The cloth lobe is low enough frequency that sampling with cosine weighting is sufficient
float3 L = 0.0, H = 0.0;
float OutRoughness = 1.0;
const bool bSampledDiffuse = RandSample.x < Data.LobeCdf.x;
const bool bSampledCloth = RandSample.x < Data.LobeCdf.y;
if (bSampledDiffuse || bSampledCloth)
{
if (bSampledDiffuse)
{
RandSample.x = RescaleRandomNumber(RandSample.x, 0.0, Data.LobeCdf.x);
}
else
{
RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.x, Data.LobeCdf.y);
}
// Lambert
L = CosineSampleHemisphere(RandSample.xy).xyz;
H = normalize(L + Data.V);
}
else
{
RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeCdf.y, 1.0);
H = ImportanceSampleVisibleGGX(RandSample.xy, Data.Alpha, Data.V).xyz;
L = reflect(-Data.V, H);
if (L.z <= 0)
{
// invalid output direction, exit early
return NullMaterialSample();
}
OutRoughness = Payload.Roughness;
}
// With a valid direction in hand -- now evaluate the BxDF (taking advantage of already computed terms)
// transform to world space
const float3 L_World = normalize(mul(L, Data.Basis));
const float NoL = saturate(L.z);
const float VoH = saturate(dot(V, H));
const float NoH = saturate(H.z);
const float ClothBlend = Payload.GetClothAmount();
const float2 GGXResult = GGXEvalReflection(L, V, H, Data.Alpha);
const float SpecPdf = GGXResult.y;
const float DiffPdf = NoL / PI;
const float ClothPdf = DiffPdf;
FMaterialSample Result = CreateMaterialSample(L_World, 0.0, 0.0, 1.0, OutRoughness, PATHTRACER_SCATTER_DIFFUSE);
const float ShadowTerminator = ShadowTerminatorTerm(L_World, Payload.WorldNormal, Payload.WorldSmoothNormal);
if (bSampledDiffuse)
{
Result.AddLobeWithMIS(Data.DiffWeight * ShadowTerminator, DiffPdf, Data.LobePdf.x);
// Cloth and Spec PDF
Result.Pdf += Data.LobePdf.y * ClothPdf;
Result.Pdf += Data.LobePdf.z * SpecPdf;
}
else
{
Result.ScatterType = PATHTRACER_SCATTER_SPECULAR;
// Cloth Lobe
const float DCloth = D_InvGGX(Pow4(Payload.Roughness), NoH);
const float3 FCloth = F_Schlick(Payload.GetClothColor(), VoH);
const float3 ClothWeight = NoL > 0 && NoV > 0 ? (PI * ClothBlend * DCloth * Vis_Cloth(NoV, NoL)) * FCloth * ComputeEnergyConservation(Data.Fuzz) : 0.0;
Result.AddLobeWithMIS(ClothWeight * ShadowTerminator, ClothPdf, Data.LobePdf.y);
// Specular lobe
const float3 F = F_Schlick(Payload.SpecularColor, VoH);
const float3 SpecWeight = (1 - ClothBlend) * F * GGXResult.x * Data.Spec.W;
Result.AddLobeWithMIS(SpecWeight, SpecPdf, Data.LobePdf.z);
// Diffuse PDF
Result.Pdf += Data.LobePdf.x * DiffPdf;
}
Result.Weight *= Payload.BSDFOpacity;
return Result;
}