Files
2025-05-18 13:04:45 +08:00

170 lines
5.0 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
PathTracingEye.usf: Path tracing BRDF model for the eye material
===============================================================================================*/
#pragma once
#include "PathTracingMaterialCommon.ush"
#include "PathTracingFresnel.ush"
#include "PathTracingEnergyConservation.ush"
struct FEyeData
{
float3x3 Basis;
float2 Alpha;
float3 V;
FBxDFEnergyTermsRGB Spec;
float3 DiffWeight;
float LobeProb;
};
FEyeData PrepareEyeData(FPathTracingPayload Payload, float3 V_World)
{
FEyeData Data = (FEyeData)0;
Data.Basis = GetTangentBasis(Payload.WorldNormal);
Data.Alpha = GetGGXAlpha(Payload.GetEyeRoughness());
Data.V = mul(Data.Basis, V_World);
const float NoV = saturate(Data.V.z);
Data.Spec = ComputeGGXSpecEnergyTermsRGB(Payload.GetEyeRoughness(), NoV, Payload.SpecularColor);
Data.DiffWeight = (1 - Luminance(Data.Spec.E)) * Payload.DiffuseColor;
// Probability of picking diffuse lobe vs. specular lobe
Data.LobeProb = LobeSelectionProb(Data.DiffWeight, Data.Spec.E);
return Data;
}
FMaterialEval Eye_EvalMaterial(
float3 V_World,
float3 L_World,
FPathTracingPayload Payload,
float2 DiffuseSpecularScale
)
{
const FEyeData Data = PrepareEyeData(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 NoV = saturate(V.z);
const float NoL = saturate(L.z);
const float VoH = saturate(dot(V, H));
FMaterialEval Result = NullMaterialEval();
const float IrisMask = Payload.GetEyeIrisMask();
const float3 IrisNormal = Payload.GetEyeIrisNormal();
const float3 CausticNormal = Payload.GetEyeCausticNormal();
const float IrisNoL = saturate(dot(IrisNormal, L_World));
const float Power = lerp(12, 1, IrisNoL);
const float Caustic = 0.8 + 0.2 * (Power + 1) * pow(saturate(dot(CausticNormal, L_World)), Power);
const float Iris = IrisNoL * Caustic;
const float Sclera = NoL;
const float EyeDiffuseTweak = 2 * lerp(Sclera, Iris, IrisMask);
const float DiffPdf = 1 / (2 * PI);
// Diffuse Lobe
Result.AddLobeWithMIS(Data.DiffWeight * EyeDiffuseTweak * DiffuseSpecularScale.x, DiffPdf, Data.LobeProb);
// Specular lobe
const float2 GGXResult = GGXEvalReflection(L, V, H, Data.Alpha);
const float3 F = F_Schlick(Payload.SpecularColor, VoH);
const float3 SpecWeight = F * GGXResult.x * Data.Spec.W;
const float SpecPdf = GGXResult.y;
Result.AddLobeWithMIS(SpecWeight * DiffuseSpecularScale.y, SpecPdf, 1.0 - Data.LobeProb);
Result.Weight *= Payload.BSDFOpacity;
return Result;
}
FMaterialSample Eye_SampleMaterial(
float3 V_World,
FPathTracingPayload Payload,
float3 RandSample
)
{
const FEyeData Data = PrepareEyeData(Payload, V_World);
const float3 V = Data.V;
const float NoV = saturate(V.z);
// Randomly choose to sample diffuse or specular
float3 L = 0, H = 0;
const bool bSampledDiffuse = RandSample.x < Data.LobeProb;
if (bSampledDiffuse)
{
RandSample.x = RescaleRandomNumber(RandSample.x, 0.0, Data.LobeProb);
L = UniformSampleHemisphere(RandSample.xy).xyz;
H = normalize(L + V);
}
else
{
RandSample.x = RescaleRandomNumber(RandSample.x, Data.LobeProb, 1.0);
H = ImportanceSampleVisibleGGX(RandSample.xy, Data.Alpha, V).xyz;
L = reflect(-V, H);
if (L.z <= 0)
{
// invalid output direction, exit early
return NullMaterialSample();
}
}
// 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 float2 GGXResult = GGXEvalReflection(L, V, H, Data.Alpha);
const float SpecPdf = GGXResult.y;
const float DiffPdf = 1 / (2 * PI);
FMaterialSample Result = CreateMaterialSample(L_World, 0.0, 0.0, 1.0, 1.0, PATHTRACER_SCATTER_DIFFUSE);
if (bSampledDiffuse)
{
const float IrisMask = Payload.GetEyeIrisMask();
const float3 IrisNormal = Payload.GetEyeIrisNormal();
const float3 CausticNormal = Payload.GetEyeCausticNormal();
const float IrisNoL = saturate(dot(IrisNormal, L_World));
const float Power = lerp(12, 1, IrisNoL);
const float Caustic = 0.8 + 0.2 * (Power + 1) * pow(saturate(dot(CausticNormal, L_World)), Power);
const float Iris = IrisNoL * Caustic;
const float Sclera = NoL;
const float EyeDiffuseTweak = 2 * lerp(Sclera, Iris, IrisMask);
// Diffuse Lobe
Result.AddLobeWithMIS(Data.DiffWeight * EyeDiffuseTweak, DiffPdf, Data.LobeProb);
Result.Pdf += (1 - Data.LobeProb) * SpecPdf;
}
else
{
// Specular lobe
const float3 F = F_Schlick(Payload.SpecularColor, VoH);
const float3 SpecWeight = F * GGXResult.x * Data.Spec.W;
Result.AddLobeWithMIS(SpecWeight, SpecPdf, 1.0 - Data.LobeProb);
Result.Pdf += Data.LobeProb * DiffPdf;
Result.Roughness = Payload.GetEyeRoughness();
Result.ScatterType = PATHTRACER_SCATTER_SPECULAR;
}
Result.Weight *= Payload.BSDFOpacity;
return Result;
}