Files
UnrealEngine/Engine/Shaders/Private/Substrate/SubstrateTree.ush
2025-05-18 13:04:45 +08:00

431 lines
21 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "/Engine/Private/Substrate/SubstrateEvaluation.ush"
#include "/Engine/Private/ShadingCommon.ush"
#ifndef SUBSTRATE_OPAQUE_MATERIAL
#error SUBSTRATE_OPAQUE_MATERIAL not defined
#endif
// SUBSTRATE_TODO We assume layers all have the same IOR. But we should take into account the material later.
// For now, we suppose no IOR changes at interface until they are tracked properly
#define WATER_IOR 1.0f
#define AIR_IOR 1.0f
///////////////////////////////////////////////////////////////////////////////
// Tree processing functions
#if SUBSTRATE_COMPILER_SUPPORTS_STRUCT_FORWARD_DECLARATION
void FSubstrateTree::UpdateSingleBSDFOperatorCoverageTransmittance(
FSubstratePixelHeader SubstratePixelHeader,
int BSDFIndex,
FSubstrateIntegrationSettings Settings,
float3 V)
#else
void UpdateSingleBSDFOperatorCoverageTransmittance(
inout FSubstrateTree InSubstrateTree,
FSubstratePixelHeader SubstratePixelHeader,
int BSDFIndex,
FSubstrateIntegrationSettings Settings,
float3 V)
#endif
{
#if SUBSTRATE_INLINE_SHADING
#if SUBSTRATE_COMPILER_SUPPORTS_STRUCT_FORWARD_DECLARATION
#define CurrentBSDF this.BSDFs[BSDFIndex]
#define Op this.Operators[CurrentBSDF.OperatorIndex]
#else
#define CurrentBSDF InSubstrateTree.BSDFs[BSDFIndex]
#define Op InSubstrateTree.Operators[CurrentBSDF.OperatorIndex]
#endif
// this feature is only needed for development/editor - we can compile it out for a shipping build (see r.CompileShadersForDevelopment cvar help)
#if USE_DEVELOPMENT_SHADERS
// We progressively remove top layers but never the bottom one.
CurrentBSDF.Coverage = int(Op.LayerDepth) < Settings.PeelLayersAboveDepth && !CurrentBSDF.bIsBottom ? 0.0f : CurrentBSDF.Coverage;
#endif
float CurrentBSDFCoverage = CurrentBSDF.Coverage;
float Roughness = SubstrateGetBSDFRoughness(CurrentBSDF);
float3x3 TangentBasis = SubstrateGetBSDFSharedBasis_InlineShading(SubstratePixelHeader, CurrentBSDF);
#if USE_DEVELOPMENT_SHADERS
Roughness = Settings.bRoughnessTracking ? Roughness : 0.0;
#endif
// Sanitize BSDF before it is used for forward shading or exported to GBuffer
CurrentBSDF.SubstrateSanitizeBSDF();
// Distortion and translucency passes accumnulate pass calls for colored transmittance, coverage and TopDownRefractionLobeStat to be setup correctly.
#if (SUBSTRATE_FASTPATH==0 || MATERIALBLENDING_ANY_TRANSLUCENT) && !SUBSTRATE_OPTIMIZED_UNLIT // !Fast path or rendering translucent materials
// Extract the true thickness of the BSDF before it is cast into the normalised Slab thickness we used.
// for opaque rough refraction, we are only interested in thickness of non bottom layers.
Op.OpaqueRoughRefractThicknessCm = CurrentBSDF.bIsBottom ? 0.0f : CurrentBSDF.ThicknessCm;
Op.OpaqueRoughRefractCoverage = CurrentBSDF.bIsTop ? CurrentBSDF.Coverage : 0.0f;
Op.OpaqueRoughRefractTopRoughness = CurrentBSDF.bIsTop ? Roughness : 0.0f;
const bool bIsSubstrateOpaqueMaterial = SUBSTRATE_OPAQUE_MATERIAL > 0;
CurrentBSDF.PostProcessBSDFBeforeLighting(bIsSubstrateOpaqueMaterial);
// Compute current BSDF transmittance
float3 LEqualN = TangentBasis[2];
FSubstrateAddressing NullSubstrateAddressing = (FSubstrateAddressing)0;
FSubstrateBSDFContext SubstrateBSDFContext = SubstrateCreateBSDFContext(SubstratePixelHeader, CurrentBSDF, NullSubstrateAddressing, V, LEqualN);
FSubstrateEvaluateResult BSDFEvaluate = SubstrateEvaluateBSDF(SubstrateBSDFContext, Settings);
// We multiply this weight with coverage:
// - Because when coming from parameter blending, we need to scale the output down according to coverage resulting from parameter blending.
// - That will be applied on emissive and reflected light, so we do not need to apply that anywhere else.
// - When coming from non parameter blending, this is equal identity 1 and the operator parsing will continue to correctly weight the BSDF according to the tree.
CurrentBSDF.LuminanceWeightV = float3(1.0f, 1.0f, 1.0f) * CurrentBSDFCoverage;
// Initialise to "no matter above"
CurrentBSDF.CoverageAboveAlongN = 0.0f;
CurrentBSDF.TransmittanceAboveAlongN = float3(1.0f, 1.0f, 1.0f);
// All BSDF are considered for contribution as the top BSDF can have their coverage dip to 0 and then another BSDF need to be considered for the top layer representation.
CurrentBSDF.TopLayerDataWeight = 1.0f;
Op.Coverage = CurrentBSDFCoverage;
Op.ThroughputAlongV = BSDFEvaluate.ThroughputV;
Op.TransmittanceAlongN = BSDFEvaluate.TransmittanceAlongN;
// Evaluate refraction lobes.
const FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(Roughness, SubstrateBSDFContext.SatNoV, SubstrateGetBSDFSpecularF0(CurrentBSDF), SubstrateGetBSDFSpecularF90(CurrentBSDF));
{
const float TopDownInterfaceEta = CurrentBSDF.bIsTop ? AIR_IOR / WATER_IOR : 1.0f;
FSubstrateLobeStatistic RefractedLobe = SubstrateGetRefractedLobe(SubstrateGetDiracLobe(V), EnergyTerms.E, Roughness, TopDownInterfaceEta);
// Account for transmittance of the medium (but ignoring scattering for now)
RefractedLobe.E *= BSDFEvaluate.ThroughputV;
// Reduce according to coverage. It is a approximation to handle rough refraction reduction according to coverage when relying on a single lob.
RefractedLobe = SubstrateWeightLobe(RefractedLobe, CurrentBSDFCoverage);
Op.TopDownRefractionLobeStat = RefractedLobe;
}
// Same operation but for a bottom up lob
{
const float BottomUpInterfaceEta = CurrentBSDF.bIsTop ? WATER_IOR / AIR_IOR : 1.0f;
const float3 NormalDown = float3(0, 0, -1);
FSubstrateLobeStatistic RefractedLobe = SubstrateGetRefractedLobe(SubstrateGetDiracLobe(NormalDown), EnergyTerms.E, Roughness, BottomUpInterfaceEta);
// Account for transmittance of the medium (but ignoring scattering for now)
RefractedLobe.E *= BSDFEvaluate.ThroughputV;
// Reduce according to coverage. It is a approximation to handle rough refraction reduction according to coverage when relying on a single lob.
RefractedLobe = SubstrateWeightLobe(RefractedLobe, CurrentBSDFCoverage);
Op.BottomUpRefractionLobeStat = RefractedLobe;
}
#elif SUBSTRATE_OPTIMIZED_UNLIT
CurrentBSDF.LuminanceWeightV = CurrentBSDFCoverage;
CurrentBSDF.CoverageAboveAlongN = 0.0f;
CurrentBSDF.TransmittanceAboveAlongN= float3(1.0f, 1.0f, 1.0f);
CurrentBSDF.TopLayerDataWeight = 1.0f;
Op.OpaqueRoughRefractThicknessCm = 0.0;
Op.OpaqueRoughRefractCoverage = CurrentBSDFCoverage;
Op.OpaqueRoughRefractTopRoughness = Roughness;
Op.Coverage = CurrentBSDFCoverage;
Op.ThroughputAlongV = UNLIT_TRANSMITTANCE(CurrentBSDF);
Op.TransmittanceAlongN = UNLIT_TRANSMITTANCE(CurrentBSDF);
Op.TopDownRefractionLobeStat = SubstrateGetNullLobe();
Op.BottomUpRefractionLobeStat = SubstrateGetNullLobe();
#else //
CurrentBSDF.LuminanceWeightV = CurrentBSDFCoverage;
CurrentBSDF.CoverageAboveAlongN = 0.0f;
CurrentBSDF.TransmittanceAboveAlongN= float3(1.0f, 1.0f, 1.0f);
CurrentBSDF.TopLayerDataWeight = 1.0f;
Op.OpaqueRoughRefractThicknessCm = 0.0;
Op.OpaqueRoughRefractCoverage = CurrentBSDFCoverage;
Op.OpaqueRoughRefractTopRoughness = Roughness;
Op.Coverage = CurrentBSDFCoverage;
Op.ThroughputAlongV = 1.0f;
Op.TransmittanceAlongN = 1.0f;
Op.TopDownRefractionLobeStat = SubstrateGetNullLobe();
Op.BottomUpRefractionLobeStat = SubstrateGetNullLobe();
#endif // SUBSTRATE_COMPLEXPATH
Op.TopDownRefractionWorldNormal = TangentBasis[2];
#undef Op
#undef CurrentBSDF
#endif // SUBSTRATE_INLINE_SHADING
}
void FSubstrateTree::UpdateSingleOperatorCoverageTransmittance(int OpIndex)
{
#if SUBSTRATE_INLINE_SHADING
#define Op this.Operators[OpIndex]
#define OpA this.Operators[this.Operators[OpIndex].LeftIndex]
#define OpB this.Operators[this.Operators[OpIndex].RightIndex]
// Propagate bIsTop:
// - horizontal, add, weight operations: Any of the operand bIsTop should be enough to determine this operator depth
// - vertical: takes the top layer depth information (operand A)
// => we always take the operand A information.
Op.LayerDepth = OpA.LayerDepth;
switch (Op.Type)
{
case SUBSTRATE_OPERATOR_WEIGHT:
{
const float Weight = saturate(Op.Weight);
Op.Coverage = Weight * OpA.Coverage;
Op.ThroughputAlongV = OpA.ThroughputAlongV;
Op.TransmittanceAlongN = OpA.TransmittanceAlongN;
Op.TopDownRefractionLobeStat = SubstrateWeightLobe(OpA.TopDownRefractionLobeStat, Weight);
Op.TopDownRefractionWorldNormal = OpA.TopDownRefractionWorldNormal * Weight;
Op.BottomUpRefractionLobeStat = SubstrateWeightLobe(OpA.BottomUpRefractionLobeStat, Weight);
// Coverage does not affect rough refraction data
Op.OpaqueRoughRefractThicknessCm = OpA.OpaqueRoughRefractThicknessCm;
Op.OpaqueRoughRefractCoverage = OpA.OpaqueRoughRefractCoverage * Weight;
Op.OpaqueRoughRefractTopRoughness = OpA.OpaqueRoughRefractTopRoughness;
break;
}
#if !SUBSTRATE_OPTIMIZED_UNLIT
case SUBSTRATE_OPERATOR_VERTICAL:
{
const float3 TopThroughputAlongV = OpA.ThroughputAlongV;
const float3 TopTransmittanceAlongN = OpA.TransmittanceAlongN;
const float3 BotThroughputAlongV = OpB.ThroughputAlongV;
const float3 BotTransmittanceAlongN = OpB.TransmittanceAlongN;
const float TopCoverage = OpA.Coverage;
const float BotCoverage = OpB.Coverage;
#if 0
// This is coverage assuming correlation
const float LayerMaxCoverage = max(TopCoverage, BotCoverage);
const float LayerMaxCoverageInv = LayerMaxCoverage <= 0.0f ? 1.0f : 1.0f / LayerMaxCoverage;
// If a layer has a lower coverage than the other, we account for that when compuing the Transmittance.
const float TransmittanceOne = 1.0f;
const float3 TopTransmittanceAdjustedV = lerp(TransmittanceOne, TopThroughputAlongV, TopCoverage * LayerMaxCoverageInv);
const float3 TopTransmittanceAdjustedL = lerp(TransmittanceOne, TopTransmittanceAlongN, TopCoverage * LayerMaxCoverageInv);
const float3 BotTransmittanceAdjustedV = lerp(TransmittanceOne, BotThroughputAlongV, BotCoverage * LayerMaxCoverageInv);
const float3 BotTransmittanceAdjustedL = lerp(TransmittanceOne, BotTransmittanceAlongN, BotCoverage * LayerMaxCoverageInv);
// Now we update the material transmittance and coverage
Op.Coverage = LayerMaxCoverage;
Op.ThroughputAlongV = TopTransmittanceAdjustedV * BotTransmittanceAdjustedV;
Op.TransmittanceAlongN = TopTransmittanceAdjustedL * BotTransmittanceAdjustedL;
#else
FVerticalLayeringInfo Info = GetVerticalLayeringInfo(TopCoverage, BotCoverage);
Op.Coverage = Info.Coverage;
Op.ThroughputAlongV = Info.TransmittanceOnlyTop * TopThroughputAlongV + Info.TransmittanceOnlyBottom * BotThroughputAlongV + Info.TransmittanceTopAndBottom * TopThroughputAlongV * BotThroughputAlongV;
Op.TransmittanceAlongN = Info.TransmittanceOnlyTop* TopTransmittanceAlongN + Info.TransmittanceOnlyBottom * BotTransmittanceAlongN + Info.TransmittanceTopAndBottom * TopTransmittanceAlongN * BotTransmittanceAlongN;
#endif
Op.VerticalTop_Coverage = OpA.Coverage;
Op.VerticalTop_ThroughputAlongV = OpA.ThroughputAlongV;
Op.VerticalTop_TransmittanceAlongN = OpA.TransmittanceAlongN;
Op.VerticalBot_Coverage = OpB.Coverage;
Op.VerticalBot_ThroughputAlongV = OpB.ThroughputAlongV;
Op.VerticalBot_TransmittanceAlongN = OpB.TransmittanceAlongN;
// Project top lob through bottom layer assuming medium have same IOR.
{
const float TopDownInterfaceEta = 1.0f;
Op.TopDownRefractionLobeStat = SubstrateGetRefractedLobe(SubstrateOppositeLobe(OpA.TopDownRefractionLobeStat),
/*InterfaceFDG*/0.0f, SubstrateLobeVarianceToRoughness(OpB.TopDownRefractionLobeStat.Sigma), TopDownInterfaceEta);
Op.TopDownRefractionLobeStat.E *= saturate(OpB.ThroughputAlongV + (1.0f - OpB.Coverage)); // Combine with botton layer medium and specular transmittance
// The ratio of BottomSurfaceOnlyVisibile and visible through top layer (with a scale of 0.5 because at maximum both surfaces will be visible) as a fonction of the total coverage.
const float RefractionNormalMix = saturate((Info.SurfaceBottom + 0.5f * Info.TransmittanceTopAndBottom * dot(TopThroughputAlongV, 0.33.xxx)) / max(SUBSTRATE_EPSILON, Info.Coverage));
Op.TopDownRefractionWorldNormal = lerp(OpA.TopDownRefractionWorldNormal, OpB.TopDownRefractionWorldNormal, RefractionNormalMix);
}
// Project bottom lob through top layer.
{
const bool bIsTop = Op.LayerDepth == 0;
const float BottomUpInterfaceEta = bIsTop ? WATER_IOR / AIR_IOR : 1.0f;
Op.BottomUpRefractionLobeStat = SubstrateGetRefractedLobe(SubstrateOppositeLobe(OpB.BottomUpRefractionLobeStat),
/*InterfaceFDG*/0.0f, SubstrateLobeVarianceToRoughness(OpA.BottomUpRefractionLobeStat.Sigma), BottomUpInterfaceEta);
Op.BottomUpRefractionLobeStat.E *= saturate(OpA.ThroughputAlongV + (1.0f - OpA.Coverage)); // Combine with top layer medium and specular transmittance
}
// Vertical layering accumulates both operators thickness and only keep the top layer roughness and coverage, see OpaqueRoughRefractTopRoughness declaration for the details.
Op.OpaqueRoughRefractThicknessCm = OpA.OpaqueRoughRefractThicknessCm + OpB.OpaqueRoughRefractThicknessCm;
Op.OpaqueRoughRefractCoverage = OpA.OpaqueRoughRefractCoverage;
Op.OpaqueRoughRefractTopRoughness = OpA.OpaqueRoughRefractTopRoughness;
Op.VerticalTop_TopDownRefractionLobeStat = OpA.TopDownRefractionLobeStat;
Op.VerticalTop_BottomUpRefractionLobeStat = OpA.BottomUpRefractionLobeStat;
break;
}
case SUBSTRATE_OPERATOR_HORIZONTAL:
{
const float Mix = saturate(Op.Weight);
const float AMix = 1.0 - Mix;
const float BMix = Mix;
Op.Coverage = AMix * OpA.Coverage + BMix * OpB.Coverage;
Op.ThroughputAlongV = (AMix * OpA.Coverage * OpA.ThroughputAlongV + BMix * OpB.Coverage * OpB.ThroughputAlongV) / max(SUBSTRATE_EPSILON, AMix * OpA.Coverage + BMix * OpB.Coverage);
Op.TransmittanceAlongN = (AMix * OpA.Coverage * OpA.TransmittanceAlongN + BMix * OpB.Coverage * OpB.TransmittanceAlongN) / max(SUBSTRATE_EPSILON, AMix * OpA.Coverage + BMix * OpB.Coverage);
Op.TopDownRefractionLobeStat = SubstrateHorizontalMixLobes(OpA.TopDownRefractionLobeStat, OpB.TopDownRefractionLobeStat, BMix);
Op.TopDownRefractionWorldNormal = lerp(OpA.TopDownRefractionWorldNormal, OpB.TopDownRefractionWorldNormal, BMix);
Op.BottomUpRefractionLobeStat = SubstrateHorizontalMixLobes(OpA.BottomUpRefractionLobeStat, OpB.BottomUpRefractionLobeStat, BMix);
// Horizontal mixes both operators thickness
Op.OpaqueRoughRefractThicknessCm = lerp(OpA.OpaqueRoughRefractThicknessCm, OpB.OpaqueRoughRefractThicknessCm, BMix);
Op.OpaqueRoughRefractCoverage = lerp(OpA.OpaqueRoughRefractCoverage, OpB.OpaqueRoughRefractCoverage, BMix);
Op.OpaqueRoughRefractTopRoughness = lerp(OpA.OpaqueRoughRefractTopRoughness, OpB.OpaqueRoughRefractTopRoughness, BMix);
break;
}
case SUBSTRATE_OPERATOR_ADD:
{
const float SafeABSDFCoverage = saturate(OpA.Coverage);
const float SafeBBSDFCoverage = saturate(OpB.Coverage);
const float AMixFactor = SafeABSDFCoverage / max(SUBSTRATE_EPSILON, SafeABSDFCoverage + SafeBBSDFCoverage);
Op.Coverage = saturate(OpA.Coverage + OpB.Coverage);
Op.ThroughputAlongV = lerp(OpB.ThroughputAlongV, OpA.ThroughputAlongV, AMixFactor);
Op.TransmittanceAlongN = lerp(OpB.TransmittanceAlongN, OpA.TransmittanceAlongN, AMixFactor);
Op.TopDownRefractionLobeStat = SubstrateHorizontalMixLobes(OpA.TopDownRefractionLobeStat, OpB.TopDownRefractionLobeStat, 0.5f);
Op.TopDownRefractionWorldNormal = lerp(OpA.TopDownRefractionWorldNormal, OpB.TopDownRefractionWorldNormal, 0.5f);
Op.BottomUpRefractionLobeStat = SubstrateHorizontalMixLobes(OpA.BottomUpRefractionLobeStat, OpB.BottomUpRefractionLobeStat, 0.5f);
Op.OpaqueRoughRefractThicknessCm = lerp(OpA.OpaqueRoughRefractThicknessCm, OpB.OpaqueRoughRefractThicknessCm, 0.5f);
Op.OpaqueRoughRefractCoverage = lerp(OpA.OpaqueRoughRefractCoverage, OpB.OpaqueRoughRefractCoverage, 0.5f);
Op.OpaqueRoughRefractTopRoughness = lerp(OpA.OpaqueRoughRefractTopRoughness, OpB.OpaqueRoughRefractTopRoughness, 0.5f);
break;
}
#endif // !SUBSTRATE_OPTIMIZED_UNLIT
}
#undef Op
#undef OpA
#undef OpB
#endif // SUBSTRATE_INLINE_SHADING
}
#define CurrentBSDF this.BSDFs[BSDFIndex]
#define Op this.Operators[OpIndex]
// Post-update visit operators
void FSubstrateTree::UpdateAllBSDFWithBottomUpOperatorVisit_Weight(
int BSDFIndex,
int OpIndex,
int PreviousIsInputA)
{
#if SUBSTRATE_INLINE_SHADING
const float Weight = saturate(Op.Weight);
CurrentBSDF.LuminanceWeightV *= Weight;
CurrentBSDF.Coverage *= Weight;
CurrentBSDF.TopLayerDataWeight *= Weight;
#endif // SUBSTRATE_INLINE_SHADING
}
void FSubstrateTree::UpdateAllBSDFWithBottomUpOperatorVisit_Horizontal(
int BSDFIndex,
int OpIndex,
int PreviousIsInputA)
{
#if SUBSTRATE_INLINE_SHADING
const float Mix = saturate(Op.Weight);
const float AMix = 1.0 - Mix;
const float BMix = Mix;
const float Weight = PreviousIsInputA > 0 ? AMix : BMix;
CurrentBSDF.LuminanceWeightV *= Weight;
CurrentBSDF.Coverage *= Weight;
CurrentBSDF.TopLayerDataWeight *= Weight;
#endif // SUBSTRATE_INLINE_SHADING
}
void FSubstrateTree::UpdateAllBSDFWithBottomUpOperatorVisit_Vertical(
int BSDFIndex,
int OpIndex,
int PreviousIsInputA)
{
#if SUBSTRATE_INLINE_SHADING
// PreviousIsInputA > 0 means it is a BSDF affecting the top layer so we do not affect it by anything for this vertical layering.
const bool bBSDFComesFromTopLayer = PreviousIsInputA > 0;
// Otherise, the BSDF comes from the bottom part so we affect it weights from the gathered top layer coverage/transmittance
CurrentBSDF.LuminanceWeightV *= bBSDFComesFromTopLayer ? 1.0f : saturate(1.0f - Op.VerticalTop_Coverage) + saturate(Op.VerticalTop_Coverage) * Op.VerticalTop_ThroughputAlongV;
// Now we are going to combine the coverage/transmittance above this slab into a new one according to the vertical operator we traverse now, if PreviousIsInputA > 0
const float BotCover = CurrentBSDF.CoverageAboveAlongN;
const float TopCover = Op.VerticalTop_Coverage;
const float3 BotTrans = CurrentBSDF.TransmittanceAboveAlongN;
const float3 TopTrans = Op.VerticalTop_TransmittanceAlongN;
FVerticalLayeringInfo Info = GetVerticalLayeringInfo(Op.VerticalTop_Coverage, CurrentBSDF.CoverageAboveAlongN);
// Pre coverage transmittance is the surface transmitance as if coverage=1, so the final combined transmittance is renormalized accordingly.
const float3 PreCoverageTransmittance = saturate((Info.TransmittanceOnlyTop * TopTrans + Info.TransmittanceOnlyBottom * BotTrans + Info.TransmittanceTopAndBottom * (TopTrans * BotTrans)) / max(SUBSTRATE_EPSILON, Info.Coverage));
CurrentBSDF.CoverageAboveAlongN = bBSDFComesFromTopLayer ? CurrentBSDF.CoverageAboveAlongN : Info.Coverage; // Info.Coverage combines the incoming operator Top part coverage and the BSDF current CoverageAboveAlongN
CurrentBSDF.TransmittanceAboveAlongN = bBSDFComesFromTopLayer ? CurrentBSDF.TransmittanceAboveAlongN : PreCoverageTransmittance; // PreCoverageTransmittance is the combined trnasmittance for the BSDF TransmittanceAboveAlongN computed so far and the top layer of this new vertical operator.
// If the BSDF is from the bottom, we reduce its contribution according to the top layer coverage only.
CurrentBSDF.TopLayerDataWeight *= bBSDFComesFromTopLayer ? 1.0 : saturate(1.0f - Op.VerticalTop_Coverage);
#if SUBSTRATE_MATERIAL_ROUGHNESS_TRACKING
if (!bBSDFComesFromTopLayer) // Only apply to BSDF from the bottom layer
{
// We propagate the bottom layer lob up after the top layer top layer simulate the fact that light rays are potentially heavily scattered/refracted by the top layer.
// The outcome is that we affect the bottom layer roughness to aggregate that fact.
// SUBSTRATE_TODO: take into account thickness (far-field for now).
// Evaluate the reflected lobe, as an approximation we only evaluate along the normal.
const float3 DummyWi = float3(0, 0, 1);
const float DummyInterfaceDFG = 0.5f;
FSubstrateLobeStatistic ReflectedLobe = SubstrateGetReflectedLobe(SubstrateGetDiracLobe(DummyWi), DummyInterfaceDFG, SubstrateGetBSDFRoughness(CurrentBSDF));
// Evaluate the reflected lobe after refraction through the top layer of the vertical operator.
// => This tells how light should be integrated when traveling up thorugh that layer.
// However, light should only refract and spread from roughness according to the amount of coverage, and only if transmittance is > 0.
const float DiracLobeSigma = 0.0f;
const float TopLayerSigma = lerp(DiracLobeSigma, Op.VerticalTop_BottomUpRefractionLobeStat.Sigma, Op.VerticalTop_Coverage * dot(Op.VerticalTop_TransmittanceAlongN, (1.0f/3.0f).xxx));
const bool bIsTop = Op.LayerDepth == 0;
const float BottomUpInterfaceEta = bIsTop ? WATER_IOR / AIR_IOR : 1.0f;
FSubstrateLobeStatistic RefractedLobe = SubstrateGetRefractedLobe(SubstrateOppositeLobe(ReflectedLobe), DummyInterfaceDFG, SubstrateLobeVarianceToRoughness(TopLayerSigma), BottomUpInterfaceEta);
CurrentBSDF.SubstrateSetBSDFRoughness(SubstrateLobeVarianceToRoughness(RefractedLobe.Sigma));
}
#endif // SUBSTRATE_MATERIAL_ROUGHNESS_TRACKING
#endif // SUBSTRATE_INLINE_SHADING
}
#undef Op
#undef CurrentBSDF