179 lines
6.1 KiB
HLSL
179 lines
6.1 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================================
|
|
PathTracingFresnel.usf: Fresnel utilities for material sampling functions
|
|
===============================================================================================*/
|
|
#pragma once
|
|
|
|
// The functions in this file obey the following convention:
|
|
// Eta = bFrontFacing ? Ior : rcp(Ior);
|
|
// In other words, when we "enter" the material, we use the IOR (a value greater than 1.0), and when we exit, we use the inverse (a value less than 1.0)
|
|
|
|
float FresnelReflectance(float CosI, float Eta)
|
|
{
|
|
// This is a float only version of F_Fresnel that takes the Ior ratio as input directly (and handles TIR)
|
|
|
|
// NOTE: this function tries uses the exact same math as the version below for consistency
|
|
|
|
float g2 = Eta * Eta - 1 + CosI * CosI;
|
|
|
|
if (g2 >= 0)
|
|
{
|
|
float c = abs(CosI);
|
|
float g = sqrt(g2);
|
|
float a2 = Pow2((g - c) / (g + c));
|
|
float b2 = Pow2((c * (g + c) - 1) / (c * (g - c) + 1));
|
|
return 0.5 * a2 * (1.0 + b2);
|
|
}
|
|
// TIR -- must become fully reflective because refraction direction is not defined
|
|
return 1.0;
|
|
}
|
|
|
|
// TIR handled by Eta parameter, reflectivity by F0
|
|
float FresnelReflectance(float CosI, float Eta, float F0)
|
|
{
|
|
// Eta = bFrontFacing ? Ior : rcp(Ior);
|
|
float F90 = saturate(F0 * 50.0);
|
|
|
|
if (Eta >= 1.0)
|
|
{
|
|
// no TIR possible when "entering" (lower ior -> higher ior)
|
|
return lerp(F0, F90, Pow5(1 - CosI));
|
|
}
|
|
float g2 = Eta * Eta - 1 + CosI * CosI;
|
|
if (g2 >= 0.0)
|
|
{
|
|
return lerp(F0, F90, Pow5(1.0 - sqrt(g2) * rcp(Eta)));
|
|
}
|
|
// TIR -- must become fully reflective because refraction direction is not defined
|
|
return 1.0;
|
|
}
|
|
|
|
// returns true if we sampled a refraction, or false if we reflected (or TIR occured)
|
|
bool SampleRefraction(float3 RayDirection, float3 Normal, float Eta, float RandSample, out float3 OutDirection, out float OutFresnel)
|
|
{
|
|
// Compute refracted direction and Fresnel term at the same time to re-use intermediate values
|
|
// and to get matching results for the TIR condition
|
|
float CosI = abs(dot(RayDirection, Normal));
|
|
float g2 = Eta * Eta - 1.0 + CosI * CosI;
|
|
|
|
if (g2 >= 0.0)
|
|
{
|
|
float c = CosI;
|
|
float g = sqrt(g2);
|
|
float a2 = Pow2((g - c) / (g + c));
|
|
float b2 = Pow2((c * (g + c) - 1) / (c * (g - c) + 1));
|
|
float Fr = 0.5 * a2 * (1.0 + b2);
|
|
|
|
// Stochastically decide to transmit or not based on Fr
|
|
// This assumes RandSample is in [0,1)
|
|
if (RandSample >= Fr)
|
|
{
|
|
OutFresnel = 1 - Fr;
|
|
OutDirection = normalize(RayDirection + Normal * (c - g));
|
|
return true;
|
|
}
|
|
OutFresnel = Fr;
|
|
}
|
|
else
|
|
{
|
|
// TIR
|
|
OutFresnel = 1.0;
|
|
}
|
|
// either TIR, or sampled reflection
|
|
OutDirection = RayDirection + (2 * CosI) * Normal;
|
|
return false;
|
|
}
|
|
|
|
// Sample refraction where bending is driven by Eta and reflectivity by F0
|
|
bool SampleRefraction(float3 RayDirection, float3 Normal, float Eta, float F0, float RandSample, out float3 OutDirection, out float OutFresnel)
|
|
{
|
|
float CosI = abs(dot(RayDirection, Normal));
|
|
float g2 = Eta * Eta - 1.0 + CosI * CosI;
|
|
if (g2 >= 0.0)
|
|
{
|
|
// refraction is possible, compute fresnel
|
|
float g = sqrt(g2);
|
|
float c = Eta >= 1.0 ? CosI : g * rcp(Eta);
|
|
float F90 = saturate(F0 * 50.0);
|
|
float Fr = lerp(F0, F90, Pow5(1 - c));
|
|
// Stochastically decide to transmit or not based on Fr
|
|
// This assumes RandSample is in [0,1)
|
|
if (RandSample >= Fr)
|
|
{
|
|
OutFresnel = 1 - Fr;
|
|
OutDirection = normalize(RayDirection + Normal * (CosI - g));
|
|
return true;
|
|
}
|
|
// sampled reflection
|
|
OutFresnel = Fr;
|
|
}
|
|
else
|
|
{
|
|
// TIR (no refraction possible, so everything must be reflected
|
|
OutFresnel = 1.0;
|
|
}
|
|
// either TIR, or sampled reflection
|
|
OutDirection = RayDirection + (2 * CosI) * Normal;
|
|
return false;
|
|
}
|
|
|
|
|
|
#define CLEAR_COAT_F0 0.04 // From hard-coded value in ShadingModels.ush
|
|
|
|
float ClearCoatFresnel(float VoH)
|
|
{
|
|
// from hardcoded logic in ClearCoatBxDF
|
|
return CLEAR_COAT_F0 + (1.0 - CLEAR_COAT_F0) * Pow5(1 - VoH);
|
|
}
|
|
|
|
|
|
struct FThinSlabWeights
|
|
{
|
|
float3 Reflected;
|
|
float3 Transmitted;
|
|
};
|
|
|
|
// return the amount of reflected and transmitted energy for an infinite slab viewed from a particular direction
|
|
// taking into account the (repeated) Fresnel terms and the transmittance through the slab
|
|
FThinSlabWeights ComputeThinSlabWeights(float3 SlabColor, float CosV, float Eta, float F0)
|
|
{
|
|
// Compute Fresnel reflection off the top of the slab
|
|
// The Fresnel refletion at the bottom layer is exactly equal so we can assume energy is split into R and T at each bounce
|
|
// NOTE: we don't need to pass Eta through here because TIR (which could only occur in contrived scenarios) isn't really supported
|
|
const float R = FresnelReflectance(CosV, 1.0, F0);
|
|
|
|
// The slab color is normalized to unit thickness, so we only need to account for the refracted viewing angle
|
|
// Note that we only allow Eta > 1.0 here so that we don't need to worry about TIR
|
|
const float3 A = pow(SlabColor, Eta > 1.0 ? Eta * rsqrt(Eta * Eta - 1.0 + CosV * CosV) : rcp(abs(CosV)));
|
|
|
|
// figure out how much is reflected/transmitted from a single interaction
|
|
float3 SlabR = R;
|
|
float3 SlabT = A - R * A; // (1 - R) * A
|
|
|
|
#if 1
|
|
// Enable bounces within the slab
|
|
if (R < 1.0)
|
|
{
|
|
// If there was some transmission we must also account for the infinite series of bounces within the slab
|
|
// which can be computed exactly with a geometric series. Note that this causes a bit of energy loss because we don't
|
|
// account for this non-linear fresnel term in the energy conservation calculation. However, this is fairly minimal in
|
|
// practice, and the added visual richness seems worth it.
|
|
float3 InvDenom = rcp(1.0 - Pow2(R * A));
|
|
SlabR += R * Pow2(SlabT) * InvDenom;
|
|
SlabT *= InvDenom - R * InvDenom; // (1 - R) * InvDenom
|
|
}
|
|
#endif
|
|
|
|
FThinSlabWeights Result = (FThinSlabWeights)0;
|
|
Result.Reflected = SlabR;
|
|
Result.Transmitted = SlabT;
|
|
return Result;
|
|
}
|
|
|
|
float ComputeThinTransmittedRoughness(float Roughness, float Ior)
|
|
{
|
|
// Transmitted lobe should have slightly less roughness due to the squeeze implied by the IOR (eventually converging to 0.0 as Ior approaches 1.0)
|
|
return Ior > 0.0 ? Roughness * sqrt(1.0 - min(Ior, rcp(Ior))) : Roughness;
|
|
}
|