133 lines
3.9 KiB
HLSL
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;
|
|
}
|