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

133 lines
3.9 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingSolidGlass.ush: Microfacet Refractive BSDF for solid glass
===============================================================================================*/
#pragma once
#include "PathTracingMaterialCommon.ush"
#include "PathTracingFresnel.ush"
#include "PathTracingGlossy.ush"
#include "PathTracingEnergyConservation.ush"
struct FRoughGlassData
{
float3x3 Basis;
float2 Alpha;
float3 V;
// glass lobe data
float Eta;
float F0;
float EGlass;
};
FRoughGlassData PrepareRoughGlassData(FPathTracingPayload Payload, float3 V_World)
{
FRoughGlassData Data = (FRoughGlassData)0;
Data.Basis = GetGGXBasis(Payload.Roughness, Payload.Anisotropy, Payload.WorldNormal, Payload.WorldTangent, Data.Alpha);
Data.V = mul(Data.Basis, V_World);
const float NoV = saturate(Data.V.z);
// NOTE: IsFrontFace() determines polygon orientation, because the normal is always flipped towards in the incoming ray
// TODO: Maintain a refraction stack on the path tracing payload
Data.Eta = Payload.IsFrontFace() ? Payload.Ior : rcp(Payload.Ior);
Data.F0 = F0RGBToF0(Payload.SpecularColor);
// correct for energy loss by scaling the whole BSDF
Data.EGlass = GGXEnergyLookup(Payload.Roughness, NoV, Data.Eta);
return Data;
}
FMaterialSample RoughGlass_SampleMaterial(
float3 V_World,
FPathTracingPayload Payload,
float3 RandSample)
{
const FRoughGlassData Data = PrepareRoughGlassData(Payload, V_World);
const float3 V = Data.V;
const float NoV = saturate(V.z);
if (NoV == 0)
{
// invalid grazing angle
return NullMaterialSample();
}
const float3 H = ImportanceSampleVisibleGGX(RandSample.xy, Data.Alpha, V, false).xyz;
// Glass lobe (reflection and refraction)
float3 L = 0;
float F = 0;
const bool bRefract = SampleRefraction(-V, H, Data.Eta, Data.F0, RandSample.z, L, F);
// transform to world space
const float3 L_World = normalize(mul(L, Data.Basis));
const float2 GGXResult = bRefract ? GGXEvalRefraction(L, V, H, Data.Alpha, Data.Eta) : GGXEvalReflection(L, V, H, Data.Alpha, false);
return CreateMaterialSample(L_World, Payload.BSDFOpacity * GGXResult.x / Data.EGlass, F * GGXResult.y, bRefract ? -1.0 : 1.0, Payload.Roughness, bRefract ? PATHTRACER_SCATTER_REFRACT : PATHTRACER_SCATTER_SPECULAR);
}
FMaterialEval RoughGlass_EvalMaterial(
float3 V_World,
float3 L_World,
FPathTracingPayload Payload,
float2 DiffuseSpecularScale
)
{
const FRoughGlassData Data = PrepareRoughGlassData(Payload, V_World);
const float3 V = Data.V;
const float NoV = saturate(V.z);
if (NoV == 0)
{
// invalid grazing angle
return NullMaterialEval();
}
// move vectors into right shading frame
const float3 L = mul(Data.Basis, L_World);
FMaterialEval Result = NullMaterialEval();
if (L.z > 0)
{
// reflection side
const float3 H = normalize(V + L);
const float NoL = saturate(L.z);
const float VoH = saturate(dot(V, H));
const float NoH = saturate(H.z);
// Glass lobe
const float2 GGXResult = GGXEvalReflection(L, V, H, Data.Alpha, false);
const float Fg = FresnelReflectance(VoH, Data.Eta, Data.F0);
const float GlassWeight = GGXResult.x * Payload.BSDFOpacity / Data.EGlass;
const float GlassPdf = GGXResult.y * Fg;
Result.AddLobeWithMIS(GlassWeight * DiffuseSpecularScale.y, GlassPdf, 1.0);
}
else if (L.z < 0)
{
// refraction side
const float NoL = saturate(-L.z);
float3 Ht = -(Data.Eta * L + V);
Ht = normalize((Data.Eta < 1.0f) ? -Ht : Ht);
const float VoH = dot(V, Ht);
const float Fg = 1.0f - FresnelReflectance(VoH, Data.Eta, Data.F0);
if (Fg > 0)
{
const float2 GGXResult = GGXEvalRefraction(L, V, Ht, Data.Alpha, Data.Eta);
const float GlassWeight = GGXResult.x * Payload.BSDFOpacity / Data.EGlass;
const float GlassPdf = GGXResult.y * Fg;
Result.AddLobeWithMIS(GlassWeight * DiffuseSpecularScale.y, GlassPdf, 1.0);
}
}
return Result;
}