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

201 lines
6.8 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingMaterialCommon.usf: Brdf utility functions
===============================================================================================*/
#pragma once
#include "/Engine/Private/MonteCarlo.ush"
#include "/Engine/Private/BRDF.ush"
float LobeColorToWeight(float3 C)
{
// TODO: What is the best heuristic to use here?
// Sum seems to perform slightly better sometimes, while max3 is better in others. Need to look at more cases to be sure
// Also tried Luminance, but the result was very close to the plain sum
#if 1
return C.x + C.y + C.z;
#else
return max3(C.x, C.y, C.z);
#endif
}
// Given two lobes that will roughly contribute colors A and B to the total (estimated for example using directional albedo)
// return the probability of choosing lobe A
float LobeSelectionProb(float3 A, float3 B)
{
const float Aw = LobeColorToWeight(A);
const float Bw = LobeColorToWeight(B);
// use MIS routine to robustly compute Aw / (Aw + Bw) even when both are 0, or overflow, etc ...
return MISWeightBalanced(Aw, Bw);
}
// Given three lobes that will roughly contribute colors A, B and C to the total (estimated for example using directional albedo)
// return a CDF for choosing lobe A, B or C
// The individual probabilities of each lobe can be estimated using LobeSelectionPdf.
float2 LobeSelectionCdf(float3 A, float3 B, float3 C)
{
const float Aw = LobeColorToWeight(A);
const float Bw = LobeColorToWeight(B);
const float Cw = LobeColorToWeight(C);
// use MIS routine to robustly compute the ratios even when both are 0, or overflow, etc ...
const float ProbAB = MISWeightBalanced(Aw, Bw); // A or B
const float ProbLR = MISWeightBalanced(Aw + Bw, Cw); // AB or C
// Now build CDF
return float2(ProbAB * ProbLR, ProbLR);
}
// Same as above for choosing among 4 colored lobes
float3 LobeSelectionCdf(float3 A, float3 B, float3 C, float3 D)
{
const float Aw = LobeColorToWeight(A);
const float Bw = LobeColorToWeight(B);
const float Cw = LobeColorToWeight(C);
const float Dw = LobeColorToWeight(D);
// use MIS routine to robustly compute the ratios even when both are 0, or overflow, etc ...
const float ProbAB = MISWeightBalanced(Aw, Bw); // A or B
const float ProbCD = MISWeightBalanced(Cw, Dw); // C or D
const float ProbLR = MISWeightBalanced(Aw + Bw, Cw + Dw); // AB or CD
// Now build CDF
return float3(ProbAB * ProbLR, ProbLR, (ProbLR + ProbCD) - ProbLR * ProbCD);
}
// Given the CDF for a choice between three lobes (as returned by LobeSelectionCdf above), compute the Pdf for each individual lobe
float3 LobeSelectionPdf(float2 LobeCdf)
{
return float3(LobeCdf.x, LobeCdf.y - LobeCdf.x, 1.0 - LobeCdf.y);
}
// Given the CDF for a choice between four lobes (as returned by LobeSelectionCdf above), compute the Pdf for each individual lobe
float4 LobeSelectionPdf(float3 LobeCdf)
{
return float4(LobeCdf.x, LobeCdf.y - LobeCdf.x, LobeCdf.z - LobeCdf.y, 1.0 - LobeCdf.z);
}
// The following structs are used as return types for the Eval/Sample methods
#define PATHTRACER_SCATTER_CAMERA 0 // this is used to mark camera rays until we have actually scattered
#define PATHTRACER_SCATTER_DIFFUSE 1
#define PATHTRACER_SCATTER_SPECULAR 2
#define PATHTRACER_SCATTER_REFRACT 3
#define PATHTRACER_SCATTER_VOLUME 4
struct FMaterialSample
{
float3 Direction;
float3 Weight;
float Pdf;
float PositionBiasSign;
float Roughness;
int ScatterType;
void AddLobeWithMIS(float3 LobeWeight, float LobePdf, float LobeProb)
{
::AddLobeWithMIS(Weight, Pdf, LobeWeight, LobePdf, LobeProb);
}
};
FMaterialSample NullMaterialSample()
{
// return a zero initialized sample, for cases where we couldn't sample the material (such as rays below the horizon, etc..)
return (FMaterialSample)0;
}
FMaterialSample CreateMaterialSample(float3 Direction, float3 Weight, float Pdf, float PositionBiasSign, float Roughness, int ScatterType)
{
FMaterialSample Result;
Result.Direction = Direction;
Result.Weight = Weight;
Result.Pdf = Pdf;
Result.PositionBiasSign = PositionBiasSign;
Result.Roughness = Roughness;
Result.ScatterType = ScatterType;
return Result;
}
struct FMaterialEval
{
float3 Weight;
float Pdf;
void AddLobeWithMIS(float3 LobeWeight, float LobePdf, float LobeProb)
{
::AddLobeWithMIS(Weight, Pdf, LobeWeight, LobePdf, LobeProb);
}
};
FMaterialEval NullMaterialEval()
{
return (FMaterialEval)0;
}
FMaterialEval CreateMaterialEval(float3 Weight, float Pdf)
{
FMaterialEval Result;
Result.Weight = Weight;
Result.Pdf = Pdf;
return Result;
}
#define PATH_TRACER_SHADOW_TERMINATOR 1 /// 0: off 1: imageworks 2: disney 3: dreamworks (see paper references below)
float ShadowTerminatorTerm(float3 L, float3 N, float3 Ns)
{
// #jira UE-121401
#if PATH_TRACER_SHADOW_TERMINATOR == 1
// Imageworks terminator softening:
// "A Microfacet-Based Shadowing Function to Solve the Bump Terminator Problem"
// Alejandro Conty Estevez, Pascal Lecocq, and Clifford Stein.
// Ray Tracing Gems(2019), 149 - 158.
// http://www.aconty.com/pdf/bump-terminator-nvidia2019.pdf
const float Epsilon = 1e-6; // avoid division by 0
const float CosD = saturate(abs(dot(Ns, N)));
const float Tan2D = (1.0 - CosD * CosD) / (CosD * CosD + Epsilon);
const float Alpha2 = saturate(0.125 * Tan2D);
const float CosI = saturate(dot(Ns, L));
const float Tan2I = (1.0f - CosI * CosI) / (CosI * CosI + Epsilon);
return CosI > 0 ? 2.0f / (1.0f + sqrt(1.0f + Alpha2 * Tan2I)) : 0.0;
#elif PATH_TRACER_SHADOW_TERMINATOR == 2
// Disney terminator softening:
// "Taming the Shadow Terminator"
// Matt Jen-Yuan Chiang, Yining Karl Li, and Brent Burley
// SIGGRAPH 2019 Talks
// https://www.yiningkarlli.com/projects/shadowterminator.html
const float NoL = saturate(dot(N, L));
const float NgoL = saturate(dot(Ns, L));
const float NgoN = saturate(dot(Ns, N));
const float G = saturate(NgoL / (NoL * NgoN + 1e-6));
return G + G * (G - G * G); // smooth
#elif PATH_TRACER_SHADOW_TERMINATOR == 3
// Dreamworks terminator softening:
// "Predictable and Targeted Softening of the Shadow Terminator"
// Priyamvad Deshmukh, Brian Green
// SIGGRAPH 2020 Talks
// https://research.dreamworks.com/wp-content/uploads/2020/08/talk_shadow_terminator.pdf
const float CosD = saturate(abs(dot(Ns, N)));
const float d = lerp(0.15, 0.05, CosD);
const float t = saturate(dot(Ns, L) / d);
return t * t * (3.0 - 2.0 * t);
#else
// no special handling of the terminator, don't do anything
return 1.0;
#endif
}
float3 GetPathTracingDiffuseModel(float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH, float NoH)
{
#if MATERIAL_ROUGHDIFFUSE
// Clamp to [0,1] as the model is ever so slightly non-energy conserving at grazing angles which can cause unwanted noise
return saturate(PI * Diffuse_Chan(DiffuseColor, Pow4(Roughness), NoV, NoL, VoH, NoH, 1.0f));
#else
return DiffuseColor;
#endif
}