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

1167 lines
49 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// Do not include that on mobile since FMobileBasePassInterpolantsVSToPS is not the same structure as FBasePassInterpolantsVSToPS
#if SUBSTRATE_ENABLED && !defined(FMobileBasePassInterpolantsVSToPS)
// This is used to evaluate some precomputed lighting data while we pack out Substrate material.
// It is also used to remove reference to unwanted resourced when not needed.
#ifndef MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
#error MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING must be defined before including SubstrateExport.ush
#endif
#ifndef SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE
#error SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE must be defined from Substrate.ush before including SubstrateExport.ush
#endif
#if MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
// Forward declaration
FShadingOcclusion ApplyBentNormal(
in half3 CameraVector,
in half3 WorldNormal,
in float3 WorldBentNormal0,
in half Roughness,
in half MaterialAO);
void GetPrecomputedIndirectLightingAndSkyLight(
FMaterialPixelParameters MaterialParameters,
FVertexFactoryInterpolantsVSToPS Interpolants,
FBasePassInterpolantsVSToPS BasePassInterpolants,
VTPageTableResult LightmapVTPageTableResult,
bool bEvaluateBackface,
float3 DiffuseDir,
float3 VolumetricLightmapBrickTextureUVs,
out float3 OutDiffuseLighting,
out float3 OutSubsurfaceLighting,
out float OutIndirectIrradiance);
float3 SubstrateGetBSDFPrecomputedLighting(
in FSubstrateIntegrationSettings Settings,
in FSubstratePixelHeader SubstratePixelHeader,
in FSubstrateBSDF CurrentBSDF,
in float3 V,
in float3 WorldBentNormal0,
in FMaterialPixelParameters MaterialParameters,
in FVertexFactoryInterpolantsVSToPS Interpolants,
in FBasePassInterpolantsVSToPS BasePassInterpolants,
in VTPageTableResult LightmapVTPageTableResult,
in float3 VolumetricLightmapBrickTextureUVs,
inout float2 SpecMultiBounceAO_IndirectIrradiance)
{
FSubstrateAddressing NullSubstrateAddressing = (FSubstrateAddressing)0;
FSubstrateBSDFContext SubstrateBSDFContext = SubstrateCreateBSDFContext(SubstratePixelHeader, CurrentBSDF, NullSubstrateAddressing, V);
// Evaluate shading occlusion data and bent normal
FShadingOcclusion ShadingOcclusion = ApplyBentNormal(
V,
SubstrateBSDFContext.N,
WorldBentNormal0,
SubstrateGetBSDFRoughness(CurrentBSDF),
SubstratePixelHeader.IrradianceAO.MaterialAO);
// Update a context specific for environment lighting
FSubstrateBSDFContext EnvBSDFContext = SubstrateBSDFContext;
EnvBSDFContext.N = ShadingOcclusion.BentNormal;
EnvBSDFContext.SubstrateUpdateBSDFContext(EnvBSDFContext.L);
// For diffuse, we specify a perpendicular to the surface light direction for the transmittance to light to not be view dependent.
float DiffuseEnvLightingNoL = 1.0f;
float3 LuminanceWeightFinal = LuminanceWeight(DiffuseEnvLightingNoL, CurrentBSDF);
// Evaluate material environment ligthing parameters
const bool bEnableSpecular = false;
FSubstrateEnvLightResult SubstrateEnvLight = SubstrateEvaluateForEnvLight(EnvBSDFContext, bEnableSpecular, Settings);
// Fetch precomputed lighting
float3 DiffuseLighting = 0.0f;
float3 SubsurfaceLighting = 0.0f;
float IndirectIrradiance = 0.0f;
const bool bEvaluateBackface = any(SubstrateEnvLight.DiffuseBackFaceWeight > 0.0); // With Substrate we have no other way to know if backface lighting will be needed
GetPrecomputedIndirectLightingAndSkyLight(MaterialParameters, Interpolants, BasePassInterpolants, LightmapVTPageTableResult, bEvaluateBackface, SubstrateEnvLight.DiffuseNormal, VolumetricLightmapBrickTextureUVs,
DiffuseLighting, SubsurfaceLighting, IndirectIrradiance);
const float3 DiffMultiBounceAO = AOMultiBounce(SubstrateEnvLight.DiffuseColor, ShadingOcclusion.DiffOcclusion);
SpecMultiBounceAO_IndirectIrradiance.x = AOMultiBounce(Luminance(SubstrateEnvLight.SpecularColor), ShadingOcclusion.SpecOcclusion).g;
SpecMultiBounceAO_IndirectIrradiance.y = IndirectIrradiance;
// Evaluate precomputed lighting according to material parameters matching environment lighting.
float3 OutLuminance = (DiffuseLighting * SubstrateEnvLight.DiffuseWeight + SubsurfaceLighting * SubstrateEnvLight.DiffuseBackFaceWeight) * LuminanceWeightFinal * DiffMultiBounceAO;
return OutLuminance;
}
#endif // MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
// If this is changed, please update the compiler side material size evaluation in SubstrateMaterial.cpp
void PackSubstrateOut(
inout FRWSubstrateMaterialContainer SubstrateBuffer,
#if SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE
RWTexture2DArray<uint> ExtraMaterialDataUAV,
#endif
float Dither,
FSubstrateIntegrationSettings Settings,
FSubstrateAddressing SubstrateAddressing,
FSubstratePixelHeader SubstratePixelHeader,
FSubstrateData Substrate,
float3 V,
float3 WorldBentNormal0,
inout bool bSubstrateSubsurfaceEnable,
inout float3 OutEmissiveLuminance,
inout FSubstrateSubsurfaceData SSSData,
inout FSubstrateTopLayerData TopLayerData,
inout FSubstrateOpaqueRoughRefractionData OpaqueRoughRefractionData
#if MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
,in FMaterialPixelParameters MaterialParameters
,in FVertexFactoryInterpolantsVSToPS Interpolants
,in FBasePassInterpolantsVSToPS BasePassInterpolants
,in VTPageTableResult LightmapVTPageTableResult
,in float3 VolumetricLightmapBrickTextureUVs
,inout float3 OutScatteredPrecomputedLuminance
#endif
)
{
// This only exists with inline shading and when we are going to write out BSDFs (UpdateAllXXX functions needs to be defined)
#if SUBSTRATE_INLINE_SHADING && SUBSTRATE_CLAMPED_CLOSURE_COUNT > 0
bSubstrateSubsurfaceEnable = false;
OutEmissiveLuminance = 0.0f;
#if MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
OutScatteredPrecomputedLuminance = 0.0f;
#endif
const float FullyRough = 1.0f;
// While packing Substrate layer data, Classification/SSS/TopLayer data are extracted & stored for dedicated passes
// This avoid to run a post-basepass which would re-read all the material data
SSSData = (FSubstrateSubsurfaceData)0;
TopLayerData = (FSubstrateTopLayerData)0;
OpaqueRoughRefractionData = (FSubstrateOpaqueRoughRefractionData)0;
// The SSSData we accumulate to allow blending of Wrap and SubstrateDiffusion types.
uint SSSDataType = SSS_TYPE_NONE;
float SSSDataAniso = 0.0f;
float SSSDataProfileID = 0.0f;
float SSSDataRadiusScale = 0.0f;
float3 SSSDataMFP = 0.0f;
float3 SSSDataBaseColor = 0.0f;
uint BSDFVisibleCount = 0;
if (SubstratePixelHeader.SubstrateTree.BSDFCount > 0)
{
// Update tree (coverage/transmittance/luminace weights)
SubstratePixelHeader.SubstrateUpdateTree(V, Settings);
const uint RootOperatorIndex = Substrate.OperatorIndex;
#if SUBSTRATE_ADVANCED_DEBUG_ENABLED && SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE
if (Settings.SliceStoringDebugSubstrateTreeData > -1)
{
SubstratePackOutSubstrateTree(ExtraMaterialDataUAV, RootOperatorIndex, SubstratePixelHeader.SubstrateTree, Settings.SliceStoringDebugSubstrateTreeData);
}
#endif
// Process the rough refraction data (this code is only used, and not culled out, if effectively used in base pass for instance)
{
const FSubstrateOperator RootOperator = SubstratePixelHeader.SubstrateTree.Operators[Substrate.OperatorIndex];
OpaqueRoughRefractionData.Coverage = RootOperator.OpaqueRoughRefractCoverage;
const float StandardDeviationCm = sqrt(GetVarianceFromRoughness(RootOperator.OpaqueRoughRefractTopRoughness));
const float StandardDeviationCmForThickness = StandardDeviationCm * RootOperator.OpaqueRoughRefractThicknessCm;
OpaqueRoughRefractionData.VarianceCm = StandardDeviationCmForThickness * StandardDeviationCmForThickness;
}
uint OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_NONE;
bool bIsSimpleMaterial = true;
bool bIsSingleMaterial = true;
bool bIsOnlySlab = true;
int OneBSDFMaterial_Index = 0; // Use for simple and single material modes. Represent the last visible BSDF index.
float TopLayerTotalWeight = 0.0f;
{
Substrate_for_unroll(int BSDFIdx = 0, BSDFIdx < SubstratePixelHeader.SubstrateTree.BSDFCount, ++BSDFIdx)
{
#define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx]
const bool bIsVisible = SubstrateIsBSDFVisible(BSDF);
if (bIsVisible)
{
BSDFVisibleCount++;
switch (BSDF_GETTYPE(BSDF))
{
case SUBSTRATE_BSDF_TYPE_SLAB:
{
OutEmissiveLuminance += BSDF_GETEMISSIVE(BSDF) * BSDF.LuminanceWeightV;
// Simple and single bsdf do not allow weights other than 1
const bool LuminanceWeightEqualOne = all(BSDF.LuminanceWeightV == 1.0f);
#if SUBSTRATE_COMPLEXSPECIALPATH
const bool bForceComplexSpecialPath = (SUBSTRATE_GLINTS_ENABLED && BSDF_GETHASGLINT(BSDF));
#else
const bool bForceComplexSpecialPath = false;
#endif
const bool bForceComplexMaterial = BSDF_GETHASANISOTROPY(BSDF) || (SUBSTRATE_SPECPROFILE_ENABLED && BSDF_GETHASSPECPROFILE(BSDF)) || bForceComplexSpecialPath || !LuminanceWeightEqualOne;
// Update simple material compatibility
bIsSimpleMaterial = bIsSimpleMaterial && IsSubstrateSlabCompatible_SimplePath(BSDF) && !bForceComplexMaterial;
// Update single material compatibility. For now, single materials don't support anisotropy,
// they use the complex pass, as the toplayer data does not contain the full frame basis, only the top normal.
bIsSingleMaterial = bIsSingleMaterial && !bForceComplexMaterial;
TopLayerTotalWeight += BSDF.TopLayerDataWeight;
float TopLayerRoughnessContribution = BSDF.TopLayerDataWeight * SLAB_ROUGHNESS(BSDF);
float3 TopLayerBaseColorContribution = BSDF.TopLayerDataWeight * SubstrateGetBSDFBaseColor(BSDF);
if (BSDF_GETHASHAZINESS(BSDF))
{
FHaziness Haziness = UnpackHaziness(SLAB_HAZINESS(BSDF));
if (Haziness.bSimpleClearCoat)
{
// Transfert SSR from clear coat to bottom layer according to coverage.
TopLayerRoughnessContribution = BSDF.TopLayerDataWeight * lerp(SLAB_ROUGHNESS(BSDF), Haziness.Roughness, Haziness.Weight);
//TopLayerBaseColorContribution = BSDF.TopLayerDataWeight * lerp(0.0, 0.04f, F0RGBToMetallic(0.04f)); Skip because, as for the legacy path, we only show the bottom BaseColor of Clear Coat materials.
}
}
TopLayerData.Roughness += TopLayerRoughnessContribution;
TopLayerData.UnlitViewBaseColor += TopLayerBaseColorContribution;
TopLayerData.Material = TOP_LAYER_MATERIAL_VALID;
const bool bHasSSS = BSDF_GETSSSTYPE(BSDF) != SSS_TYPE_NONE;
const bool bIsSimpleVolume = BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_SIMPLEVOLUME;
bSubstrateSubsurfaceEnable = bSubstrateSubsurfaceEnable || (bHasSSS && BSDF.Coverage > 0.0f && !bIsSimpleVolume); // Should it be coverage>0 or any(LuminanceWeight>0) ?
const bool bSSSMask = BSDF.bIsBottom; // SSS and Thin can only be on the bottom layer, so ignoring bIsSimpleVolume here.
SubstratePixelHeader.SetHasSubsurface(bSSSMask && bHasSSS && !bIsSimpleVolume);
SubstratePixelHeader.SetIsComplexSpecialMaterial(bForceComplexSpecialPath);
break;
}
case SUBSTRATE_BSDF_TYPE_HAIR:
{
bIsOnlySlab = false;
bIsSimpleMaterial = false;
bIsSingleMaterial = false;
OutEmissiveLuminance += BSDF_GETEMISSIVE(BSDF) * BSDF.LuminanceWeightV;
TopLayerTotalWeight += BSDF.TopLayerDataWeight;
TopLayerData.Roughness += BSDF.TopLayerDataWeight * FullyRough;
TopLayerData.UnlitViewBaseColor += BSDF.TopLayerDataWeight * SubstrateGetBSDFBaseColor(BSDF);
TopLayerData.Material = TOP_LAYER_MATERIAL_VALID;
SubstratePixelHeader.SetMaterialMode(HEADER_MATERIALMODE_HAIR);
break;
}
case SUBSTRATE_BSDF_TYPE_EYE:
{
bIsOnlySlab = false;
bIsSimpleMaterial = false;
bIsSingleMaterial = false;
TopLayerTotalWeight += BSDF.TopLayerDataWeight;
TopLayerData.Roughness += BSDF.TopLayerDataWeight * FullyRough;
TopLayerData.Material = TOP_LAYER_MATERIAL_VALID;
const bool bHasSSS = BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE;
SubstratePixelHeader.SetHasSubsurface(bHasSSS);
SubstratePixelHeader.SetMaterialMode(HEADER_MATERIALMODE_EYE);
break;
}
case SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER:
{
bIsOnlySlab = false;
bIsSimpleMaterial = false;
bIsSingleMaterial = false;
OutEmissiveLuminance += BSDF_GETEMISSIVE(BSDF) * BSDF.LuminanceWeightV;
TopLayerTotalWeight += BSDF.TopLayerDataWeight;
TopLayerData.Roughness += BSDF.TopLayerDataWeight * SLW_ROUGHNESS(BSDF);
TopLayerData.UnlitViewBaseColor += BSDF.TopLayerDataWeight * SubstrateGetBSDFBaseColor(BSDF);
TopLayerData.Material = TOP_LAYER_MATERIAL_SLWATER;
SubstratePixelHeader.SetMaterialMode(HEADER_MATERIALMODE_SLWATER);
break;
}
}
float3x3 TangentBasis = SubstrateGetBSDFSharedBasis_InlineShading(SubstratePixelHeader, BSDF);
float3 N = TangentBasis[2];
TopLayerData.WorldNormal += N * BSDF.TopLayerDataWeight;
OneBSDFMaterial_Index = BSDFIdx;
// Notify that the BSDF is at the top for SSR to only affect reflection there and not on the lower layers
BSDF_SETISTOPLAYER(BSDF, BSDF.bIsTop ? 1 : 0);
}
#undef BSDF
}
}
// Finalize top layer data
TopLayerData.WorldNormal = TopLayerTotalWeight > 0.0f ? normalize(TopLayerData.WorldNormal) : 0.0f;
TopLayerData.Roughness = TopLayerTotalWeight > 0.0f ? TopLayerData.Roughness / TopLayerTotalWeight : 0.0f;
TopLayerData.UnlitViewBaseColor = TopLayerTotalWeight > 0.0f ? TopLayerData.UnlitViewBaseColor / TopLayerTotalWeight : 0.0f;
// Set storage layout as either: fast(0), single(1), or complex(2)
// We check BSDFVisibleCount to make sure we do not set a MATERIALMODE that would enforce a BSDFVisibleCount==1.
if (bIsOnlySlab && BSDFVisibleCount > 0)
{
bIsSimpleMaterial = bIsSimpleMaterial && BSDFVisibleCount == 1;
bIsSingleMaterial = bIsSingleMaterial && BSDFVisibleCount == 1;
SubstratePixelHeader.SetMaterialMode(bIsSimpleMaterial ? HEADER_MATERIALMODE_SLAB_SIMPLE : bIsSingleMaterial ? HEADER_MATERIALMODE_SLAB_SINGLE : HEADER_MATERIALMODE_SLAB_COMPLEX);
if (bIsSingleMaterial)
{
#define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[0]
const uint SingleHasHaziness = 0x01;
const uint SingleHasClearCoat = 0x02;
const uint SingleHasCloth = 0x04;
const uint SingleIsSimpleVolume = 0x10;
const uint SingleIsWrap = 0x20;
const uint SingleIsTwoSidedWrap = 0x40;
const uint SingleIsSSSProfile = 0x80;
// No need to add anisotropy since it is not supported by the simple mode.
// If a surface has IsThin bit, opt-out legacy format.
// Legacy mode doesn't need support for IsThin, and their storage format does not have space for storing IsThin bit.
const bool bIsThinSurface = BSDF_GETISTHIN(BSDF);
uint OptimisedModeMask = 0;
if (BSDF_GETHASHAZINESS(BSDF))
{
OptimisedModeMask |= SingleHasHaziness; // Regular haziness should disable non haziness/clearcoat fast path
FHaziness Haziness = UnpackHaziness(SLAB_HAZINESS(BSDF));
if (Haziness.bSimpleClearCoat)
{
OptimisedModeMask |= SingleHasClearCoat;
}
}
if (BSDF_GETHASFUZZ(BSDF) && SLAB_ROUGHNESS(BSDF) == SLAB_FUZZ_ROUGHNESS(BSDF))
{
OptimisedModeMask |= SingleHasCloth;
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_SIMPLEVOLUME)
{
OptimisedModeMask |= SingleIsSimpleVolume;
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_WRAP)
{
OptimisedModeMask |= SingleIsWrap;
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_TWO_SIDED_WRAP)
{
OptimisedModeMask |= SingleIsTwoSidedWrap;
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE)
{
OptimisedModeMask |= SingleIsSSSProfile;
}
if (((OptimisedModeMask & SingleHasClearCoat) == SingleHasClearCoat) || ((OptimisedModeMask & SingleHasClearCoat) == (SingleHasHaziness | SingleHasClearCoat)))
{
OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_CLEARCOAT;
}
else if ((OptimisedModeMask & SingleHasCloth) == SingleHasCloth && BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_NONE)
{
OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_CLOTH;
}
else if ((OptimisedModeMask & SingleIsTwoSidedWrap) == SingleIsTwoSidedWrap && !BSDF_GETHASFUZZ(BSDF) && !bIsThinSurface)
{
OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_TWO_SIDED_SSSWRAP;
SSSData.Header.Bytes = 0; // Force to skip SSSData writing
}
else if ((OptimisedModeMask & SingleIsWrap) == SingleIsWrap && !BSDF_GETHASFUZZ(BSDF) && !bIsThinSurface)
{
OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_SSSWRAP;
SSSData.Header.Bytes = 0; // Force to skip SSSData writing
}
else if ((OptimisedModeMask & SingleIsSSSProfile) == SingleIsSSSProfile && !bIsThinSurface)
{
OptimisedLegacyMode = SINGLE_OPTLEGACYMODE_SSSPROFILE;
SSSData.Header.Bytes = 0; // Force to skip SSSData writing
}
#undef BSDF
}
}
// Now evaluate Specular Occlusion and Indirect Irradiance.
#if MATERIAL_SUBSTRATE_OPAQUE_PRECOMPUTED_LIGHTING
if(BSDFVisibleCount > 0)
{
float BSDFCount = 0;
float2 SpecMultiBounceAO_IndirectIrradiance_Acc = 0.0f;
SUBSTRATE_UNROLL_N(SUBSTRATE_CLAMPED_CLOSURE_COUNT)
for (int BSDFIdx = 0; BSDFIdx < SubstratePixelHeader.SubstrateTree.BSDFCount; ++BSDFIdx)
{
#define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx]
const bool bIsVisible = SubstrateIsBSDFVisible(BSDF);
// Only write visible BSDF
BRANCH
if (bIsVisible)
{
// Convert BSDF to data ready to evaluate precomputed lighting
float2 SpecMultiBounceAO_IndirectIrradiance = 0.0f;
float3 PrecomputedLighting = SubstrateGetBSDFPrecomputedLighting(
Settings,
SubstratePixelHeader,
BSDF,
V,
WorldBentNormal0,
MaterialParameters,
Interpolants,
BasePassInterpolants,
LightmapVTPageTableResult,
VolumetricLightmapBrickTextureUVs,
SpecMultiBounceAO_IndirectIrradiance);
SpecMultiBounceAO_IndirectIrradiance_Acc += SpecMultiBounceAO_IndirectIrradiance;
// Selectively add PrecomputedLighting to either OutScatteredPrecomputedLuminance or OutEmissiveLuminance
// depending if the light will be processed or not by SSS postprocess
if (bSubstrateSubsurfaceEnable)
{
OutScatteredPrecomputedLuminance = PrecomputedLighting;
}
else
{
OutEmissiveLuminance += PrecomputedLighting;
}
// Stop writing if above budget.
BSDFCount++;
}
#undef BSDF
}
// Write the combined SpecMultiBounceAO for all BSDFs as a single AO value. This is a trade off to have a mix of all the interactions, and it is correct for a single BSDF.
float2 SpecMultiBounceAO_IndirectIrradiance = SpecMultiBounceAO_IndirectIrradiance_Acc * rcp(max(1.0f, BSDFCount));
SubstratePixelHeader.IrradianceAO.MaterialAO = SpecMultiBounceAO_IndirectIrradiance.x;
SubstratePixelHeader.IrradianceAO.IndirectIrradiance = SpecMultiBounceAO_IndirectIrradiance.y;
}
#endif
// Store irradiance and AO into the state bits of the header so that they can be written later as part of the common header state bits.
#if SUBSTRATE_INLINE_SHADING
HEADER_SETIRRADIANCE_AO(SubstratePixelHeader.State, SubstratePackIrradianceAndOcclusion(SubstratePixelHeader.IrradianceAO, 0));
#endif
// Now write out Substrate data
///////////////////////////////////////////////////////////////////////////
// 3 types of encodings
// * A. Layout0: Simple encoding (use top layer normal)
// * B. Layout1: Single encoding (use top layer normal)
// * C. Eye : Custom encoding (use top layer normal)
// * D. Hair : Custom encoding (use top layer normal)
// * E. Layout2: Complex encoding (use basis)
const bool bHasFastEncoding = SubstratePixelHeader.IsSimpleMaterial();
const bool bHasFastWaterEncoding = SubstratePixelHeader.IsSingleLayerWater();
const bool bHasSingleEncoding = SubstratePixelHeader.IsSingleMaterial();
const bool bCustomEncoding = SubstratePixelHeader.IsHair() || SubstratePixelHeader.IsEye();
// (Layout2)
if (!bHasFastEncoding && !bHasSingleEncoding && !bCustomEncoding && !bHasFastWaterEncoding)
{
// 1. the header (Regular/Complex encoding)
const uint PackedHeader = PackSubstrateHeader(BSDFVisibleCount, SubstratePixelHeader);
SUBSTRATE_STORE_UINT1(PackedHeader);
// 1.1 tangent basis (Regular/Complex encoding)
#if SUBSTRATE_INLINE_SHADING
UNROLL
for (uint i = 0; i < SubstratePixelHeader.SharedLocalBases.Count; ++i)
{
const uint BasisType = SubstrateGetSharedLocalBasisType(SubstratePixelHeader.SharedLocalBases.Types, i);
if (BasisType == SUBSTRATE_BASIS_TYPE_NORMAL)
{
SUBSTRATE_STORE_UINT1(SubstratePackNormal(SubstratePixelHeader.SharedLocalBases.Normals[i]));
}
else // if (BasisType == SUBSTRATE_BASIS_TYPE_TANGENT)
{
SUBSTRATE_STORE_UINT1(SubstratePackNormalAndTangent(SubstratePixelHeader.SharedLocalBases.Normals[i], SubstratePixelHeader.SharedLocalBases.Tangents[i]));
}
}
#endif
}
{
int BSDFCount = 0;
Substrate_for_unroll(int BSDFIdx = 0, BSDFIdx < SubstratePixelHeader.SubstrateTree.BSDFCount, ++BSDFIdx)
{
#define BSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx]
const bool bIsVisible = SubstrateIsBSDFVisible(BSDF);
// Only write visible BSDF
BRANCH
if (bIsVisible)
{
const uint GreyScaleThroughputV = SubstrateHasGreyScaleWeight(BSDF.LuminanceWeightV) ? 1 : 0;
BSDF_SETHASGREYWEIGHT_V(BSDF, GreyScaleThroughputV);
const bool bTransmittanceAboveAlongNRequired = any(BSDF.TransmittanceAboveAlongN < 1.0f);
BSDF_SETHASTRANSABOVE(BSDF, bTransmittanceAboveAlongNRequired);
// A. Layout0 - Simple encoding (aka. fast-path): store header & data
const bool bFastEncodedBSDF = bHasFastEncoding && BSDFIdx == OneBSDFMaterial_Index;
if (bFastEncodedBSDF)
{
// Now we pack diffuse and F0 in a special way: both encoded as R7G7B6 with gamma 2.0. The low bit cound will be hidden by dithering and TAA.
// We pack 32 bits in the first uint and the remaining bits are pack is the lowest significant bit of the second uint.
const uint PackedDiffuse20Bits = PackR7G7B6Gamma2(SLAB_DIFFUSEALBEDO(BSDF), Dither);
const uint PackedDiffuse12Bits = PackedDiffuse20Bits & 0xFFF;
const uint PackedDiffuse8Bits = (PackedDiffuse20Bits >> 12) & 0xFF;
const uint PackedF020Bits = PackR7G7B6Gamma2(SLAB_F0(BSDF), Dither);
const uint PackedRoughness8bits = PackR8(SLAB_ROUGHNESS(BSDF));
// Data0 (Header_State|Header_AO|Roughness|Diffuse8bits)
{
uint Out = 0;
HEADER_SETCOMMONSTATES(Out, SubstratePixelHeader.State);
uint Packed16 = (PackedDiffuse8Bits << 8) | PackedRoughness8bits;
Out = (Out & HEADER_FASTENCODING_MASK) | (Packed16 << HEADER_FASTENCODING_BIT_COUNT);
SUBSTRATE_STORE_UINT1(Out);
#if (HEADER_FASTENCODING_BIT_COUNT + 16) > 32
#error Substrate fast path header is > 32bits
#endif
}
// Data1 (F0|Diffuse12bits)
{
uint Out = PackedF020Bits | (PackedDiffuse12Bits << 20);
SUBSTRATE_STORE_UINT1(Out);
}
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
// B. Layout1 - Simple encoding: single BSDF, whose header & BSDF state are merged
else if (bHasSingleEncoding && BSDFIdx == OneBSDFMaterial_Index)
{
// Enforce BSDF to be flagged as 'top', since it is single-encoded (which ensure there is a single visible slab).
// This is needed when material contains other slabs which are dynamically culled based on coverage
BSDF_SETISTOPLAYER(BSDF, 1);
// Data0 (Header_State|Header_AO|Header_BSDFTypes|BSDF_State)
uint Out = 0;
HEADER_SETCOMMONSTATES(Out, SubstratePixelHeader.State);
Out = Out & HEADER_SINGLEENCODING_MASK;
Out = Out | ((OptimisedLegacyMode & HEADER_SINGLE_OPTLEGACYMODE_BIT_MASK) << (HEADER_SINGLEENCODING_BIT_COUNT));
BRANCH
if (OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_NONE)
{
Out = Out | ((BSDF.State & BSDF_SINGLEENCODING_MASK) << (HEADER_SINGLE_TOTAL_BIT_COUNT));
SUBSTRATE_STORE_UINT1(Out);
}
// With this special path, there is 32-8-8-3= 13bits available for packing on the header.
// Because we use 3 UINT MRT, this means that a total of 13+32+32 = 77 bits.
else if(OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_CLEARCOAT)
{
// 77 bits with more aggressive bit reduction
const uint PackedRoughness7Bits = PackR7(SLAB_ROUGHNESS(BSDF), Dither);
const uint PackedDiffuse19Bits = PackR6G7B6Gamma2(SLAB_DIFFUSEALBEDO(BSDF), Dither);
const uint PackedF019Bits = PackR6G7B6Gamma2(SLAB_F0(BSDF), Dither);
const uint PackedHaziness32Bits = SLAB_HAZINESS(BSDF);
// Out has 13bits free
uint Data0 = Out;
Data0 |= ( PackedRoughness7Bits << (HEADER_SINGLE_TOTAL_BIT_COUNT+0)); // Append 7 bits of roughness
Data0 |= ((PackedDiffuse19Bits & 0x3F) << (HEADER_SINGLE_TOTAL_BIT_COUNT+7)); // Append 6 low bits of Diffuse
uint Data1 = PackedDiffuse19Bits >> 6; // Remaining 13bits of Diffuse
Data1|= PackedF019Bits << 13; // 19 bits of F0
uint Data2 = PackedHaziness32Bits;
SUBSTRATE_STORE_UINT1(Data0);
SUBSTRATE_STORE_UINT1(Data1);
SUBSTRATE_STORE_UINT1(Data2);
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
else if (OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_CLOTH)
{
const uint PackedDiffuse20Bits = PackR7G7B6Gamma2( SLAB_DIFFUSEALBEDO(BSDF) , Dither);
const uint PackedF020Bits = PackR7G7B6Gamma2( SLAB_F0(BSDF) , Dither);
const uint PackedFuzzColor20bits= PackR7G7B6Gamma2( SLAB_FUZZ_COLOR(BSDF) , Dither);
const uint PackedFuzzAmount8bits= PackR8( SLAB_FUZZ_AMOUNT(BSDF));
// Roughness is stored in the TopLayer texture. Fuzz roughness is identical to slab roughness in this path.
// MRT0
#if (32 - (HEADER_SINGLE_TOTAL_BIT_COUNT)) < 8
#error Not Enough space to store fuzz amount in slab in Slab - Single - Header.
#endif
SUBSTRATE_STORE_UINT1(Out | ((PackedFuzzAmount8bits & 0xFF) << (HEADER_SINGLE_TOTAL_BIT_COUNT)));
// MRT1 20 PackedDiffuse and 8 high bits from PackedClearCoat
SUBSTRATE_STORE_UINT1(PackedDiffuse20Bits | ((PackedFuzzColor20bits & 0xFFC00) << 10));
// MRT2 24 PackedF0 and 8 low bits from PackedClearCoat
SUBSTRATE_STORE_UINT1(PackedF020Bits | ((PackedFuzzColor20bits & 0x3FF) << 20));
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
else if (OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_SSSWRAP || OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_TWO_SIDED_SSSWRAP)
{
const float SSSWOpacity = SubstrateSubSurfaceGetWrapOpacityFromAnisotropy(SLAB_SSSPHASEANISOTROPY(BSDF));
const uint PackedDiffuse20Bits = PackR7G7B6Gamma2( SLAB_DIFFUSEALBEDO(BSDF) , Dither);
const uint PackedF020Bits = PackR7G7B6Gamma2( SLAB_F0(BSDF) , Dither);
const uint PackedSSSWOpacity7bits = PackR7( SSSWOpacity , 0.5); // We should not dither here because it would have a large scale effect.
const uint PackedSSSMFP30bits = PackR10G10B10F( SLAB_SSSMFP(BSDF));
// Roughness is stored in the TopLayer texture.
const uint PackedSSSMFP12BitsA = (PackedSSSMFP30bits ) & 0xFFF; // 12 lower bits
const uint PackedSSSMFP12BitsB = (PackedSSSMFP30bits >> 12) & 0xFFF; // next 12 lower bits
const uint PackedSSSMFP6BitsC = (PackedSSSMFP30bits >> 24) & 0x03F; // 6 higher bits
// MRT0
#if (32 - (HEADER_SINGLEENCODING_BIT_COUNT + HEADER_SINGLE_OPTLEGACYMODE_BIT_COUNT)) < (7+6)
#error Not Enough space to store wrapped SSS opacity in slab in Slab - Single - Header.
#endif
SUBSTRATE_STORE_UINT1(Out | ((PackedSSSWOpacity7bits & 0x7F) << (HEADER_SINGLEENCODING_BIT_COUNT + HEADER_SINGLE_OPTLEGACYMODE_BIT_COUNT)) | (PackedSSSMFP6BitsC << (7 + HEADER_SINGLEENCODING_BIT_COUNT + HEADER_SINGLE_OPTLEGACYMODE_BIT_COUNT)));
// MRT1 20 PackedDiffuse and 8 high bits from PackedClearCoat
SUBSTRATE_STORE_UINT1(PackedDiffuse20Bits | (PackedSSSMFP12BitsA << 20));
// MRT2 24 PackedF0 and 8 low bits from PackedClearCoat
SUBSTRATE_STORE_UINT1(PackedF020Bits | (PackedSSSMFP12BitsB << 20));
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
else if (OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_SSSPROFILE)
{
const uint PackedDiffuse20Bits = PackR7G7B6Gamma2( SLAB_DIFFUSEALBEDO(BSDF) , Dither);
const uint PackedF020Bits = PackR7G7B6Gamma2( SLAB_F0(BSDF) , Dither);
// When creating a slab with GetSubstrateSlabBSDF(), the SSS profile's dual roughness is expanded into haziness.
// But when storing data with SINGLE_OPTLEGACYMODE_SSSPROFILE we need to store the original roughness.
// The dual roughnesses will be recomputed based on the SSS profile data at load time. To retrieve the
// original roughness, we need to invert the computation done into GetSubstrateSlabBSDF()
const uint SubsurfaceProfileUInt = SubstrateSubsurfaceProfileIdTo8bits(SLAB_SSSPROFILEID(BSDF));
const float OriginalRoughness = GetSubsurfaceProfileOriginalRoughness(SubsurfaceProfileUInt, SLAB_ROUGHNESS(BSDF), SLAB_SSSPROFILERADIUSSCALE(BSDF));
// MRT0
SUBSTRATE_STORE_UINT1(Out | (PackR8(OriginalRoughness) << 24));
// MRT1 20 PackedDiffuse and Profile radius scale
SUBSTRATE_STORE_UINT1(PackedDiffuse20Bits | (PackR8(SLAB_SSSPROFILERADIUSSCALE(BSDF)) << 24));
// MRT2 24 PackedF0 and Profile ID
SUBSTRATE_STORE_UINT1(PackedF020Bits | (PackR8(SLAB_SSSPROFILEID(BSDF)) << 24));
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
#if (HEADER_SIMPLEENCODING_BIT_COUNT) > 32
#error Substrate fast path header is > 32bits
#endif
}
// C. Layout X (Eye)
else if (SubstratePixelHeader.IsEye())
{
// Data0 (Header_State|Header_AO|Data)
uint Out = 0;
HEADER_SETCOMMONSTATES(Out, SubstratePixelHeader.State);
Out = Out & HEADER_EYEENCODING_MASK;
Out = Out | PackRGBA8(float4(0, 0, EYE_IRISMASK(BSDF), EYE_IRISDISTANCE(BSDF)));
SUBSTRATE_STORE_UINT1(Out);
#if (HEADER_EYEENCODING_BIT_COUNT) > 32
#error Substrate eye path header is > 32bits
#endif
}
// D. Layout X (Hair)
else if (SubstratePixelHeader.IsHair())
{
// Data0 (Header_State|Header_AO|Data)
uint Out = 0;
HEADER_SETCOMMONSTATES(Out, SubstratePixelHeader.State);
Out = Out & HEADER_HAIRENCODING_MASK;
SUBSTRATE_STORE_UINT1(Out);
#if (HEADER_HAIRENCODING_BIT_COUNT) > 32
#error Substrate hair path header is > 32bits
#endif
}
// E. Layout X (SingleLayerWater)
else if (bHasFastWaterEncoding)
{
#if (HEADER_SLWENCODING_BIT_COUNT) > 16
#error Substrate single layer water header is > 16bits
#endif
// Now we pack diffuse and F0 in a special way: both encoded as R7G7B6 with gamma 2.0. The low bit cound will be hidden by dithering and TAA.
// We pack 8 of the 32 bits in the first header uint and the remaining bits are pack is the lowest significant bit of the second uint.
const uint PackedBaseColor20Bits = PackR7G7B6Gamma2(SLW_BASECOLOR(BSDF), Dither);
const uint PackedBaseColor12Bits = PackedBaseColor20Bits & 0xFFF;
const uint PackedBaseColor8Bits = (PackedBaseColor20Bits >> 12) & 0xFF;
// Packed into the header
const uint Roughness8Bits = PackR8(SLW_ROUGHNESS(BSDF));
const uint PackedOpacityMetalSpec20Bits = PackR7G7B6Linear(float3(SLW_TOPMATERIALOPACITY(BSDF), SLW_METALLIC(BSDF), SLW_SPECULAR(BSDF)), Dither);
// UINT0 (Header_State|Header_AO|BaseColor8bits|Roughness)
{
uint Out = 0;
HEADER_SETCOMMONSTATES(Out, SubstratePixelHeader.State);
uint Packed16 = (PackedBaseColor8Bits << 8) | Roughness8Bits;
Out = (Out & HEADER_SLWENCODING_MASK) | (Packed16 << HEADER_SLWENCODING_BIT_COUNT);
SUBSTRATE_STORE_UINT1(Out);
}
// UINT1 (BaseColor12bits|Opacity|Metallic|Specular)
{
uint Out = (PackedBaseColor12Bits << 20) | PackedOpacityMetalSpec20Bits;
SUBSTRATE_STORE_UINT1(Out);
}
// Ensure the rest of the BSDF is not stored with the regular path
BSDF.State = 0;
}
// F. Layout2 - Weight for Regular/Complex path
else if (GreyScaleThroughputV > 0)
{
BSDF_SETWEIGHT10F(BSDF, Pack10F(BSDF.LuminanceWeightV.x));
SUBSTRATE_STORE_UINT1(BSDF.State);
}
else
{
SUBSTRATE_STORE_UINT1(BSDF.State);
SUBSTRATE_STORE_UINT1(PackR11G11B10F(BSDF.LuminanceWeightV));
}
// Layout1 & Layout2
if (!bFastEncodedBSDF && !bHasFastWaterEncoding && OptimisedLegacyMode == SINGLE_OPTLEGACYMODE_NONE)
{
const uint BSDFType = BSDF_GETTYPE(BSDF);
switch (BSDF_GETTYPE(BSDF))
{
case SUBSTRATE_BSDF_TYPE_SLAB:
{
// Now we pack diffuse and F0 in a special way: both encoded as R7G7B6 with gamma 2.0. The low bit cound will be hidden by dithering and TAA.
// We pack 32 bits in the first uint and the remaining bits are pack is the lowest significant bit of the second uint.
const uint PackedDiffuse20Bits = PackR7G7B6Gamma2(SLAB_DIFFUSEALBEDO(BSDF), Dither);
const uint PackedF020Bits = PackR7G7B6Gamma2(SLAB_F0(BSDF), Dither);
const uint PackedData32Bits = ((PackedDiffuse20Bits << 12) & 0xFFFFF000) | (PackedF020Bits & 0xFFF);
const uint PackedData8Bits = (PackedF020Bits >> 12) & 0xFF;
SUBSTRATE_STORE_UINT1(PackedData32Bits);
SUBSTRATE_STORE_UINT1(PackedData8Bits | PackRGBA8(float4(0.0f, SLAB_ROUGHNESS(BSDF), (SLAB_ANISOTROPY(BSDF) + 1.f) * 0.5f, (SLAB_SSSPHASEANISOTROPY(BSDF) + 1.f) * 0.5f)));
if (BSDF_GETHASF90(BSDF) || BSDF_GETHASHAZINESS(BSDF))
{
// What is important is to maintain the hue and saturation, so we scale the color by the maximum of its components
float3 F90 = saturate(SLAB_F90(BSDF));
const float Divisor = max(F90.r, max(F90.g, F90.b));
F90 = Divisor > 0.0f ? F90 / Divisor : 1.0f;
float3 F90YCoCg = LinearRGB_2_NormalisedYCoCg(F90);
uint F90Data = PackRGBA8(float4(F90YCoCg.y, F90YCoCg.z, 0, 0));
uint HazinessData = SLAB_HAZINESS(BSDF);
SUBSTRATE_STORE_UINT1(HazinessData << 16 | F90Data);
}
if (BSDF_GETSSSTYPE(BSDF) != SSS_TYPE_NONE)
{
// All SSS types store side data (SSSHeader/SSSData), apart from SIMPLEVOLUME
if (BSDF_GETSSSTYPE(BSDF) != SSS_TYPE_SIMPLEVOLUME)
{
// Always override the type, we can only blend similar types of subsurface lighting effects.
SSSDataType = BSDF_GETSSSTYPE(BSDF);
// Now we are going to blend all the SSS attributes in order to be able to lerp legacy Subsurface and SubstrateDiffusion.
// Legacy SSSProfile ID is still set by the last encountered Slab.
// Attributes are blended using opacity only, no need to use the throughput to the view (SSS post process wants raw source data like BaseColor and the BSDF will otherwise run the correct lighting math).
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_WRAP || BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_TWO_SIDED_WRAP)
{
SSSDataAniso += SLAB_SSSPHASEANISOTROPY(BSDF) * BSDF.Coverage;
}
else if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE)
{
SSSDataProfileID = SLAB_SSSPROFILEID(BSDF);
SSSDataRadiusScale = SLAB_SSSPROFILERADIUSSCALE(BSDF);
}
else
{
SSSDataMFP += SLAB_SSSMFP(BSDF) * BSDF.Coverage;
}
SSSDataBaseColor += SLAB_DIFFUSEALBEDO(BSDF) * BSDF.Coverage;
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE)
{
// Simple volume overrides SSS profile because it takes over for when not at the bottom of the BSDF layer, or during forward rendering.
// Note: Since it is a profile, we can't rescale its MFP value. So we store the 'thin' thickness value for computing the correct transmission during evaluation
SUBSTRATE_STORE_UINT1(PackSSSProfile(SLAB_SSSPROFILEID(BSDF), SLAB_SSSPROFILERADIUSSCALE(BSDF), BSDF_GETTHICKNESSCM(BSDF)));
}
else
{
// If using non-profile SSS diffusion, rescale SSS so that we take into account the slab thickness.
// Note: This is done 'after' storing the SSSHeader/SSSData, as the original MFP needs to be preserved for correct post-process diffusion.
// This rescaled MFP value is only used for transmission evaluation, not the diffusion
if (BSDF_GETISTHIN(BSDF) && BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION)
{
SLAB_SSSMFP(BSDF)= RescaleMFPToComputationSpace(SLAB_SSSMFP(BSDF), BSDF_GETTHICKNESSCM(BSDF), SUBSTRATE_SIMPLEVOLUME_THICKNESS_CM);
}
// Path used for bottom most layer SSS, simple volume and two sided lighting.
SUBSTRATE_STORE_UINT1(PackR11G11B10F(SLAB_SSSMFP(BSDF)));
}
}
if (BSDF_GETHASFUZZ(BSDF))
{
SUBSTRATE_STORE_UINT1(PackFuzz(SLAB_FUZZ_COLOR(BSDF), SLAB_FUZZ_AMOUNT(BSDF), SLAB_FUZZ_ROUGHNESS(BSDF), Dither));
}
if (BSDF_GETHASTRANSABOVE(BSDF))
{
SUBSTRATE_STORE_UINT1(PackColorLinearToGamma2AlphaLinear(float4(BSDF.TransmittanceAboveAlongN, BSDF.CoverageAboveAlongN)));
}
#if SUBSTRATE_GLINTS_ENABLED
if (BSDF_GETHASGLINT(BSDF))
{
const uint2 PackedGlintData = PackGlints(SLAB_GLINT_VALUE(BSDF), SLAB_GLINT_UV(BSDF));
SUBSTRATE_STORE_UINT1(PackedGlintData.x);
SUBSTRATE_STORE_UINT1(PackedGlintData.y);
// We have to store derivative when rasterizing the mesh.
// That cannot be reconstructed in a deferred fashion (e.g. edge of a mesh overlaping with backgroud would look wrongly aliased)
SUBSTRATE_STORE_UINT1(PackFloat2ToUInt(SLAB_GLINT_UVDDX(BSDF)));
SUBSTRATE_STORE_UINT1(PackFloat2ToUInt(SLAB_GLINT_UVDDY(BSDF)));
}
#endif // SUBSTRATE_GLINTS_ENABLED
#if SUBSTRATE_SPECPROFILE_ENABLED
if (BSDF_GETHASSPECPROFILE(BSDF))
{
SUBSTRATE_STORE_UINT1(PackSpecularProfile(SLAB_SPECPROFILEID(BSDF)));
}
#endif // SUBSTRATE_SPECPROFILE_ENABLED
// 8-28 bytes
}
break;
case SUBSTRATE_BSDF_TYPE_HAIR:
{
SUBSTRATE_STORE_UINT1(PackColorLinearToGamma2AlphaLinear(float4(HAIR_BASECOLOR(BSDF), HAIR_ROUGHNESS(BSDF))));
SUBSTRATE_STORE_UINT1(PackRGBA8(float4(HAIR_SCATTER(BSDF), HAIR_SPECULAR(BSDF), HAIR_BACKLIT(BSDF), HAIR_COMPLEXTRANSMITTANCE(BSDF))));
// 8 bytes
}
break;
case SUBSTRATE_BSDF_TYPE_EYE:
{
const float2 EncodedIrisNormal = UnitVectorToOctahedron(EYE_IRISNORMAL(BSDF));
const float2 EncodedIrisPlaneNormal = UnitVectorToOctahedron(EYE_IRISPLANENORMAL(BSDF));
SUBSTRATE_STORE_UINT1(PackColorLinearToGamma2AlphaLinear(float4(EYE_DIFFUSEALBEDO(BSDF), EYE_ROUGHNESS(BSDF))));
SUBSTRATE_STORE_UINT1(PackRGBA8(float4(EncodedIrisNormal * 0.5f + 0.5f, EncodedIrisPlaneNormal * 0.5f + 0.5f)));
// 8 bytes
// Note: we don't store the SSS profile ID into the Eye data, as it is not needed there. The profile is only stored into the SSS data.
const bool bHasSSS = BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_DIFFUSION_PROFILE;
if (bHasSSS)
{
SSSDataType = SSS_TYPE_DIFFUSION_PROFILE;
SSSDataProfileID = EYE_SSSPROFILEID(BSDF);
SSSDataRadiusScale = 1.0f;
SSSDataBaseColor = EYE_DIFFUSEALBEDO(BSDF) * BSDF.Coverage;
}
}
break;
//case SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER:
//{
// // SLW always use the path defined by bHasFastWaterEncoding.
//}
//break;
}
}
// Stop writing if above budget.
BSDFCount++;
}
#undef BSDF
}
checkSlow(BSDFCount == BSDFVisibleCount);
}
FinalizeWrites(SubstrateBuffer, SubstrateAddressing);
// Now generate the final SSSData structure from the parameter blended attributes
if (SSSDataType != SSS_TYPE_NONE)
{
// All SSS types store side data (SSSHeader/SSSData), apart from SIMPLEVOLUME
if (SSSDataType != SSS_TYPE_SIMPLEVOLUME)
{
// Always override the type, we can only blend similar types of subsrface
SubstrateSubSurfaceHeaderSetSSSType(SSSData.Header, SSSDataType);
if (SSSDataType == SSS_TYPE_WRAP || SSSDataType == SSS_TYPE_TWO_SIDED_WRAP)
{
SubstrateSubSurfaceHeaderSetWrap(SSSData.Header, SSSDataAniso);
}
else if (SSSDataType == SSS_TYPE_DIFFUSION_PROFILE)
{
SubstrateSubSurfaceHeaderSetProfile(SSSData.Header, SSSDataRadiusScale, SubstrateSubsurfaceProfileIdTo8bits(SSSDataProfileID));
}
else
{
SubstrateSubSurfaceHeaderSetNonProfile(SSSData.Header, SSSDataMFP);
}
SubstrateSubsurfaceExtrasSetBaseColor(SSSData.Extras, SSSDataBaseColor);
}
}
}
#endif // SUBSTRATE_INLINE_SHADING && SUBSTRATE_CLAMPED_CLOSURE_COUNT > 0
}
#endif // SUBSTRATE_ENABLED && !defined(FMobileBasePassInterpolantsVSToPS)
#ifndef SUBSTRATE_GPU_LIGHTMASS
#define SUBSTRATE_GPU_LIGHTMASS 0
#endif
#if SUBSTRATE_ENABLED && (SUBSTRATE_MATERIAL_EXPORT_TYPE > 0 || SUBSTRATE_GPU_LIGHTMASS > 0 || SUBTRATE_GBUFFER_FORMAT==0 || defined(SUBSTRATE_MATERIAL_EXPORT_REQUESTED))
struct FExportResult
{
float Coverage;
float3 TransmittancePreCoverage;
float3 BaseColorPostCoverage;
float3 BaseColor;
float3 WorldNormal;
float3 EmissiveLuminance;
float Specular;
float Roughness;
float Anisotropy;
float Metallic;
float4 CustomData;
float3 SubsurfaceColor;
uint SubsurfaceProfileID;
float SubsurfaceProfileRadiusScale;
float3 WorldTangent;
float ShadingModelID;
float IndirectIrradianceScale;
float Curvature;
};
// This is used to export very simple data for lightmaps, material baking or preview on editor node.
// In this case we force full simplification and only recover a single BSDF in the end/
FExportResult SubstrateMaterialExportOut(
in FSubstrateIntegrationSettings Settings,
in FSubstratePixelHeader SubstratePixelHeader,
in FSubstrateData SubstrateData,
in float3 SurfaceWorldNormal,
in float3 WorldPosition_CamRelative,
in float MobileShadingPathCurvature)
{
FExportResult Out = (FExportResult)0;
Out.IndirectIrradianceScale = 1.0;
Out.SubsurfaceProfileRadiusScale = 1.0;
// Align V with N to not have any view dependent effect when exporting material data.
const float3 FakeV = SurfaceWorldNormal;
if (SubstratePixelHeader.SubstrateTree.BSDFCount > 0)
{
// Update tree (coverage/transmittance/luminace weights)
SubstratePixelHeader.SubstrateUpdateTree(SubstrateData, FakeV, Settings, Out.Coverage, Out.TransmittancePreCoverage);
const int BSDFIdx = 0;
if(SubstratePixelHeader.SubstrateTree.BSDFCount > 0)
{
#define CurrentBSDF SubstratePixelHeader.SubstrateTree.BSDFs[BSDFIdx]
if (SubstrateIsBSDFVisible(CurrentBSDF))
{
const uint BSDFType = BSDF_GETTYPE(CurrentBSDF);
const uint SSSType = BSDF_GETSSSTYPE(CurrentBSDF);
const float3x3 TangentBasis = SubstrateGetBSDFSharedBasis_InlineShading(SubstratePixelHeader, CurrentBSDF);
const float3 N = TangentBasis[2];
const float3 T = TangentBasis[0];
const float GreyScaleWeight = dot(CurrentBSDF.LuminanceWeightV, (1.0 / 3.0).xxx);
Out.BaseColorPostCoverage = SubstrateGetBSDFBaseColor(CurrentBSDF) * CurrentBSDF.LuminanceWeightV;
Out.BaseColor = SubstrateGetBSDFBaseColor(CurrentBSDF);
Out.WorldNormal = N;
Out.EmissiveLuminance = BSDF_GETEMISSIVE(CurrentBSDF) * CurrentBSDF.LuminanceWeightV;
Out.Specular = SubstrateGetBSDFSpecular(CurrentBSDF);
Out.Roughness = SubstrateGetBSDFRoughness(CurrentBSDF);
Out.Anisotropy = SubstrateGetBSDFAnisotropy(CurrentBSDF);
Out.Metallic = SubstrateGetBSDFMetallic(CurrentBSDF);
Out.SubsurfaceColor = SubstrateGetBSDFSubSurfaceColor(CurrentBSDF);
Out.SubsurfaceProfileID = SubstrateGetBSDFSubSurfaceProfileID(CurrentBSDF);
Out.WorldTangent = T;
Out.ShadingModelID = SubstrateGetLegacyShadingModels(CurrentBSDF);
if (BSDFType == SUBSTRATE_BSDF_TYPE_HAIR)
{
Out.Metallic = HAIR_SCATTER(CurrentBSDF);
Out.CustomData = float4(0.0, 0.0, HAIR_BACKLIT(CurrentBSDF), 0.0);
}
else if (SSSType == SSS_TYPE_DIFFUSION_PROFILE)
{
Out.SubsurfaceProfileRadiusScale = SLAB_SSSPROFILERADIUSSCALE(CurrentBSDF);
Out.CustomData.rgb = EncodeSubsurfaceProfile(SLAB_SSSPROFILEID(CurrentBSDF));
Out.CustomData.a = Out.SubsurfaceProfileRadiusScale;
}
else if (SSSType == SSS_TYPE_WRAP || SSSType == SSS_TYPE_TWO_SIDED_WRAP)
{
Out.CustomData.rgb = EncodeSubsurfaceColor(Out.SubsurfaceColor);
Out.CustomData.a = SubstrateSubSurfaceGetWrapOpacityFromAnisotropy(SLAB_SSSPHASEANISOTROPY(CurrentBSDF));
}
else if (BSDF_GETHASFUZZ(CurrentBSDF))
{
Out.CustomData.rgb = EncodeSubsurfaceColor(SLAB_FUZZ_COLOR(CurrentBSDF));
Out.CustomData.a = SLAB_FUZZ_AMOUNT(CurrentBSDF);
//MakeRoughnessSafe(SLAB_FUZZ_ROUGHNESS(CurrentBSDF), SUBSTRATE_MIN_FUZZ_ROUGHNESS);
Out.IndirectIrradianceScale *= 1 - Out.CustomData.a;
}
else if (BSDF_GETHASHAZINESS(CurrentBSDF))
{
FHaziness LocalHaziness = UnpackHaziness(SLAB_HAZINESS(CurrentBSDF));
if (LocalHaziness.bSimpleClearCoat)
{
Out.CustomData.x = LocalHaziness.Weight;
Out.CustomData.y = LocalHaziness.Roughness;
#if CLEAR_COAT_BOTTOM_NORMAL
Out.CustomData.wz = LocalHaziness.BottomNormalOct.xy;
#else
Out.CustomData.wz = DEFAULT_CLEAR_COAT_BOTTOM_NORMAL_OCT;
#endif
}
}
else if (BSDFType == SUBSTRATE_BSDF_TYPE_EYE)
{
const float3 IrisNormal = EYE_IRISNORMAL(CurrentBSDF);
const float3 IrisPlaneNormal= EYE_IRISPLANENORMAL(CurrentBSDF);
const float IrisMask = saturate(EYE_IRISMASK(CurrentBSDF));
const float IrisDistance = saturate(EYE_IRISDISTANCE(CurrentBSDF));
Out.CustomData.x = EncodeSubsurfaceProfile(Out.SubsurfaceProfileID).x;
Out.CustomData.w = 1.0f - IrisMask; // Opacity
const float3 GBufferWorldNormal = N;
#if IRIS_NORMAL
float2 WorldNormalOct = UnitVectorToOctahedron(GBufferWorldNormal);
// CausticNormal stored as octahedron
#if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0
// Blend in the negative intersection normal to create some concavity
// Not great as it ties the concavity to the convexity of the cornea surface
// No good justification for that. On the other hand, if we're just looking to
// introduce some concavity, this does the job.
float3 PlaneNormal = normalize( T /*GetTangentOutput0(MaterialParameters)*/);
float3 CausticNormal = normalize( lerp( PlaneNormal, -GBufferWorldNormal, IrisMask*IrisDistance ) );
float2 CausticNormalOct = UnitVectorToOctahedron( CausticNormal );
float2 CausticNormalDelta = ( CausticNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0);
Out.Metallic = CausticNormalDelta.x;
Out.Specular = CausticNormalDelta.y;
#else
float3 PlaneNormal = GBufferWorldNormal;
Out.Metallic = 128.0/255.0;
Out.Specular = 128.0/255.0;
#endif
// IrisNormal CustomData.yz
//#if NUM_MATERIAL_OUTPUTS_CLEARCOATBOTTOMNORMAL > 0
// float3 IrisNormal = normalize( ClearCoatBottomNormal0(MaterialParameters) );
// #if MATERIAL_TANGENTSPACENORMAL
// IrisNormal = normalize( TransformTangentVectorToWorld( MaterialParameters.TangentToWorld, IrisNormal ) );
// #endif
//#else
// float3 IrisNormal = PlaneNormal;
//#endif
float2 IrisNormalOct = UnitVectorToOctahedron( IrisNormal );
float2 IrisNormalDelta = ( IrisNormalOct - WorldNormalOct ) * 0.5 + (128.0/255.0);
Out.CustomData.yz = IrisNormalDelta;
#else
Out.Metallic = IrisDistance;
#if NUM_MATERIAL_OUTPUTS_GETTANGENTOUTPUT > 0
float3 Tangent = T /*GetTangentOutput0(MaterialParameters)*/;
Out.CustomData.yz = UnitVectorToOctahedron( normalize(Tangent) ) * 0.5 + 0.5;
#endif
#endif
#if SHADING_PATH_MOBILE
#if MATERIAL_SHADINGMODEL_EYE_USE_CURVATURE
Out.Curvature = Metallic;
#else
Out.Curvature = CalculateCurvature(GBufferWorldNormal, WorldPosition_CamRelative);
#endif
Out.Curvature = clamp(Out.Curvature, 0.001f, 1.0f);
#endif
}
else if (SSSType == SSS_TYPE_DIFFUSION_PROFILE) // Thin and not thin, Subsurface and TwoSided Foliage
{
const float3 GBufferWorldNormal = N;
const float Opacity = SLAB_SSSPROFILERADIUSSCALE(CurrentBSDF);
// Optimization: if opacity is 0 then revert to default shading model
#if SUBSURFACE_PROFILE_OPACITY_THRESHOLD
if (Opacity > SSSS_OPACITY_THRESHOLD_EPS)
#endif
{
Out.CustomData.rgb = EncodeSubsurfaceColor(Out.SubsurfaceColor);
Out.CustomData.a = Opacity;
#if SHADING_PATH_MOBILE
#if MATERIAL_SUBSURFACE_PROFILE_USE_CURVATURE
Out.Curvature = MobileShadingPathCurvature;
#else
Out.Curvature = CalculateCurvature(GBufferWorldNormal, WorldPosition_CamRelative);
#endif
Out.Curvature = clamp(Out.Curvature, 0.001f, 1.0f);
#endif
}
#if SUBSURFACE_PROFILE_OPACITY_THRESHOLD
else
{
Out.ShadingModelID = SHADINGMODELID_DEFAULT_LIT;
Out.CustomData = 0;
}
#endif
}
}
#undef CurrentBSDF
}
Out.WorldNormal = all(Out.WorldNormal == 0) ? float3(0.0, 0.0, 0.0) : normalize(Out.WorldNormal);
}
return Out;
}
#endif // SUBSTRATE_ENABLED, SUBSTRATE_MATERIAL_EXPORT_TYPE, etc.