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

127 lines
4.1 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
Glossy.usf: Microfacet GGX BRDF sampling functions
===============================================================================================*/
#pragma once
#include "../../BRDF.ush"
#include "../../MonteCarlo.ush"
// be conservative since the GPU may be aggressive with floating point optimizations
#define GGX_MIN_ROUGHNESS 0.01
#define GGX_MIN_ANISOTROPY 0.001 // ignore anisotropy below this amount
float2 GetGGXAlpha(float Roughness)
{
Roughness = max(Roughness, GGX_MIN_ROUGHNESS);
return Roughness * Roughness;
}
float2 GetGGXAlpha(float Roughness, float Anisotropy)
{
Roughness = max(Roughness, GGX_MIN_ROUGHNESS);
float2 Alpha = 0.0;
if (abs(Anisotropy) >= GGX_MIN_ANISOTROPY)
{
GetAnisotropicRoughness(Roughness * Roughness, Anisotropy, Alpha.x, Alpha.y);
}
else
{
Alpha = Roughness * Roughness;
}
return Alpha;
}
float3x3 GetGGXBasis(float Roughness, float Anisotropy, float3 WorldNormal, float3 WorldTangent, out float2 Alpha)
{
Roughness = max(Roughness, GGX_MIN_ROUGHNESS);
if (abs(Anisotropy) >= GGX_MIN_ANISOTROPY)
{
// Material is anisotropic, our shading frame should use the tangent vector
GetAnisotropicRoughness(Roughness * Roughness, Anisotropy, Alpha.x, Alpha.y);
float3 Y = cross(WorldNormal, WorldTangent);
float LenY2 = length2(Y);
if (LenY2 > 0)
{
// Tangent vector is valid
Y *= rsqrt(LenY2);
return float3x3(cross(Y, WorldNormal), Y, WorldNormal);
}
// Fallthrough to build a default basis if we couldn't build one
}
else
{
// No anisotropy, use simpler formula
Alpha = Roughness * Roughness;
}
// pick an arbitrary basis (no anisotropy, or invalid tangent vector)
return GetTangentBasis(WorldNormal);
}
float3x3 GetGGXBasis(float Roughness, float3 WorldNormal, out float2 Alpha)
{
// No anisotropy, just pick an arbitrary basis
Roughness = max(Roughness, GGX_MIN_ROUGHNESS);
Alpha = Roughness * Roughness;
return GetTangentBasis(WorldNormal);
}
// Compute GGX lobe Weight and Pdf (without Fresnel term) given a set of vectors in local space (Z up)
float2 GGXEvalReflection(float3 L, float3 V, float3 H, float2 Alpha, bool bLimitVDNFToReflection = true)
{
const float NoL = saturate(L.z);
const float NoV = saturate(V.z);
if (NoL > 0 && NoV > 0)
{
const float D = D_GGXaniso(Alpha.x, Alpha.y, H.z, H.x, H.y);
// See implementation in Vis_SmithJointAniso for G2/(4*NoV*NoL)
// We can simplify a bit further since we need both the weight G2/G1 and the pdf
const float LenL = length(float3(L.xy * Alpha, NoL));
const float LenV = length(float3(V.xy * Alpha, NoV));
float k = 1.0;
#if GGX_BOUNDED_VNDF_SAMPLING
if (bLimitVDNFToReflection)
{
float a = saturate(min(Alpha.x, Alpha.y));
float s = 1.0f + length(V.xy);
float a2 = a * a, s2 = s * s;
k = (s2 - a2 * s2) / (s2 + a2 * NoV * NoV); // Eq. 5
}
#endif
const float Weight = NoL * (LenV + k * NoV) / (NoV * LenL + NoL * LenV);
const float Pdf = 0.5 * D * rcp(LenV + k * NoV);
return float2(Weight, Pdf);
}
return 0;
}
// Compute GGX lobe Weight and Pdf (without Fresnel term) given a set of vectors in local space (Z up)
float2 GGXEvalRefraction(float3 L, float3 V, float3 H, float2 Alpha, float Eta)
{
const float NoV = saturate(V.z);
const float NoL = saturate(-L.z); // NOTE: L should point down
// This checks that V and L point in opposite sides and that the half-vector is valid
if (NoL > 0 && NoV > 0 && H.z > 0)
{
const float D = D_GGXaniso(Alpha.x, Alpha.y, H.z, H.x, H.y);
const float VoH = saturate( dot(V, H));
const float LoH = saturate(-dot(L, H));
// Same as logic above, only the pdf math differs slightly
const float LenL = length(float3(L.xy * Alpha, NoL));
const float LenV = length(float3(V.xy * Alpha, NoV));
const float Weight = NoL * (LenV + NoV) / (NoV * LenL + NoL * LenV);
const float Pdf = (LoH * VoH * Eta * Eta * 2.0 * D) / (Pow2(VoH - Eta * LoH) * (LenV + NoV));
// NOTE: filter out potential NaNs from Pdf
return float2(Weight, max(Pdf, 0.0));
}
return 0;
}