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

374 lines
14 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Private/Common.ush"
#define SUBSTRATE_INLINE_SHADING 0
#define SUBSTRATE_SSS_MATERIAL_OVERRIDE 0
#define SUBSTRATE_COMPLEXSPECIALPATH 1
#include "/Engine/Private/Substrate/Substrate.ush"
#include "/Engine/Private/Substrate/SubstrateEvaluation.ush"
#include "/Engine/Private/Substrate/SubstrateTile.ush"
////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if SHADER_SAMPLE_MATERIAL
#include "SubstrateMaterialSampling.ush"
#include "../Lumen/LumenPosition.ush"
#include "../BlueNoise.ush"
#define DEBUG_ENABLED 0
#if DEBUG_ENABLED
#include "../ShaderPrint.ush"
#endif
uint MaxBytesPerPixel;
uint MaxClosurePerPixel;
uint bRoughDiffuse;
uint PeelLayersAboveDepth;
uint bRoughnessTracking;
uint MegaLightsStateFrameIndex;
Texture2D<SUBSTRATE_TOP_LAYER_TYPE> TopLayerTexture;
Texture2DArray<uint> MaterialTextureArray;
Texture2D<uint> ClosureOffsetTexture;
RWTexture2D<uint4> RWMaterialData;
///////////////////////////////////////////////////////////////////////////////////////////////////
// TODO merge these helper functions with path-tracer
float3 GetSimpleVolumeDiffuseColor(float3 DiffuseColor, float3 MeanFreePath)
{
// See reference in SubstrateEvaluation.ush
// SUBSTRATE_TODO: In the case of thin materials, we should also include a backscattering diffuse portion
FParticipatingMedia PM = SubstrateSlabCreateParticipatingMedia(DiffuseColor, MeanFreePath);
const float DiffuseToVolumeBlend = SubstrateSlabDiffuseToVolumeBlend(PM);
const float3 SlabDirectionalAlbedo = IsotropicMediumSlabEnvDirectionalAlbedoALU(PM);
return lerp(DiffuseColor, SlabDirectionalAlbedo, DiffuseToVolumeBlend);
}
FBxDFEnergyTermsA ComputeSheenEnergyTerms(float NoV, float SheenRoughness)
{
#if SUBSTRATE_SHEEN_QUALITY == 1
FBxDFEnergyTermsA Result;
Result.E = SheenLTC_DirectionalAlbedo(NoV, SheenRoughness, View.SheenLTCTexture, View.SheenLTCSampler);
Result.W = Result.E; // overall weight is the directional albedo
return Result;
#else
return ComputeClothEnergyTermsA(SheenRoughness, NoV);
#endif
}
float LobeColorToWeight(float3 C)
{
// TODO: What is the best heuristic to use here?
// Sum seems to perform slightly better sometimes, while max3 is better in others. Need to look at more cases to be sure
// Also tried Luminance, but the result was very close to the plain sum
#if 1
return C.x + C.y + C.z;
#else
return max3(C.x, C.y, C.z);
#endif
}
float GetSlabPDF(FSubstrateAddressing SubstrateAddressing, FSubstratePixelHeader SubstratePixelHeader, FSubstrateBSDF BSDF, float3 V_World)
{
float Pdf = 0.0; // The goal is to predict the weight of each BSDF
switch (BSDF_GETTYPE(BSDF))
{
case SUBSTRATE_BSDF_TYPE_SLAB:
{
const float3 WeightV = BSDF.LuminanceWeightV;
const float3 TransmittanceN = BSDF.TransmittanceAboveAlongN;
const float CoverageAboveAlongN = BSDF.CoverageAboveAlongN;
const float3 MaxLobeWeight = WeightV * lerp(1.0f, TransmittanceN, CoverageAboveAlongN); // largest value LobeWeight() could return
// Is the slab even visible at all?
if (any(MaxLobeWeight > 0.0))
{
const float3 N = SubstrateGetWorldNormal(SubstratePixelHeader, BSDF, SubstrateAddressing);
const float NoV = saturate(dot(N, V_World));
float3 DiffuseColor = SLAB_DIFFUSEALBEDO(BSDF);
float3 F0 = SLAB_F0(BSDF);
float3 F90 = SLAB_F90(BSDF) * F0RGBToMicroOcclusion(F0);
float Roughness0 = MakeRoughnessSafe(SLAB_ROUGHNESS(BSDF));
// SUBSTRATE_TODO: Support Fresnel82?
float3 Spec0E = ComputeGGXSpecEnergyTermsRGB(Roughness0, NoV, F0, F90).E;
float3 Spec1E = Spec0E;
float Roughness1 = Roughness0;
float SpecBlend = 0.0;
bool bUseClearCoat = false;
if (BSDF_GETHASHAZINESS(BSDF))
{
const FHaziness Haziness = UnpackHaziness(SLAB_HAZINESS(BSDF));
SpecBlend = Haziness.Weight;
Roughness1 = MakeRoughnessSafe(Haziness.Roughness);
if (Haziness.bSimpleClearCoat)
{
const float CLEAR_COAT_F0 = 0.04f;
Spec1E = ComputeGGXSpecEnergyTermsRGB(Roughness1, NoV, CLEAR_COAT_F0).E;
bUseClearCoat = true;
}
else
{
Spec1E = ComputeGGXSpecEnergyTermsRGB(Roughness1, NoV, F0, F90).E;
}
}
if (BSDF_GETSSSTYPE(BSDF) == SSS_TYPE_SIMPLEVOLUME)
{
DiffuseColor = GetSimpleVolumeDiffuseColor(DiffuseColor, SLAB_SSSMFP(BSDF));
}
float FuzzAmount = 0.0;
float FuzzRoughness = 1.0;
float3 FuzzColor = 0.0;
if (BSDF_GETHASFUZZ(BSDF))
{
FuzzAmount = SLAB_FUZZ_AMOUNT(BSDF);
FuzzColor = SLAB_FUZZ_COLOR(BSDF);
FuzzRoughness = SLAB_FUZZ_ROUGHNESS(BSDF);
FuzzRoughness = MakeRoughnessSafe(FuzzRoughness, SUBSTRATE_MIN_FUZZ_ROUGHNESS);
}
const float FuzzE = ComputeSheenEnergyTerms(FuzzRoughness, NoV).E;
const float FuzzAttenuation = 1.0 - FuzzAmount * FuzzE;
float DiffuseWeight = FuzzAttenuation;
float3 Spec0Albedo = FuzzAttenuation * Spec0E;
float3 Spec1Albedo = FuzzAttenuation * Spec1E;
if (bUseClearCoat)
{
// clearcoat slab
float CoatAttenuation = 1.0 - SpecBlend * Spec1E.x;
DiffuseWeight *= CoatAttenuation * (1.0 - Luminance(Spec0E));
Spec0Albedo *= CoatAttenuation;
Spec1Albedo *= SpecBlend;
}
else
{
// dual-specular slab
DiffuseWeight *= 1.0 - Luminance(lerp(Spec0E, Spec1E, SpecBlend));
Spec0Albedo *= 1.0 - SpecBlend;
Spec1Albedo *= SpecBlend;
}
// SUBSTRATE_TODO: Need to account for glass case here for lobe selection ....
const float3 FuzzAlbedo = FuzzAmount * FuzzE * FuzzColor;
const float3 SlabAlbedo = MaxLobeWeight * (DiffuseWeight * DiffuseColor + Spec0Albedo + Spec1Albedo + FuzzAlbedo);
Pdf = LobeColorToWeight(SlabAlbedo);
}
break;
}
default:
{
// In theory none of the other slab types can be layered
break;
}
}
return Pdf;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
FSubstrateSampledMaterial GetSampledMaterial(uint2 InPixelCoord, uint ClosureIndex, float3 V, float InPDF)
{
const FSubstrateIntegrationSettings Settings = InitSubstrateIntegrationSettings(false /*bForceFullyRough*/, bRoughDiffuse, PeelLayersAboveDepth, bRoughnessTracking);
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(InPixelCoord, uint2(View.BufferSizeAndInvSize.xy), MaxBytesPerPixel);
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(MaterialTextureArray, SubstrateAddressing, TopLayerTexture);
const uint ClosureCount = min(SubstratePixelHeader.ClosureCount, SUBSTRATE_MATERIAL_CLOSURE_COUNT);
// * Force single closure evaluation
#if SUBSTRATE_MATERIAL_CLOSURE_COUNT > 1
if (ClosureCount > 1)
{
const uint AddressOffset = UnpackClosureOffsetAtIndex(ClosureOffsetTexture[SubstrateAddressing.PixelCoords], ClosureIndex, SubstratePixelHeader.ClosureCount);
SubstrateSeekClosure(SubstrateAddressing, AddressOffset);
}
#endif
SubstratePixelHeader.ClosureCount = 1;
FSubstrateBSDF BSDF = UnpackSubstrateBSDFIn(MaterialTextureArray, SubstrateAddressing, SubstratePixelHeader);
// Create the BSDF context
FSubstrateBSDFContext SubstrateBSDFContext = SubstrateCreateBSDFContext(SubstratePixelHeader, BSDF, SubstrateAddressing, V);
const float3 BSDFThroughput = LuminanceWeight(SubstrateBSDFContext, BSDF);
// Evaluate environment lighting
FSubstrateEnvLightResult SubstrateEnvLight = SubstrateEvaluateForEnvLight(SubstrateBSDFContext, true /*bEnableSpecular*/, Settings);
const uint BSDFType = SubstratePixelHeader.SubstrateGetBSDFType();
FSubstrateSampledMaterial Out = (FSubstrateSampledMaterial)0;
Out.bAllowSpatialFilter = true;
Out.bIsValid = true;
Out.WorldNormal = SubstrateGetWorldNormal(SubstratePixelHeader, BSDF, SubstrateAddressing);
Out.WorldTangent = SubstrateGetWorldTangent(SubstratePixelHeader, BSDF, SubstrateAddressing);
Out.Roughness = SubstrateGetBSDFRoughness(BSDF);
Out.bIsSimple = SubstratePixelHeader.IsSimpleMaterial();
Out.bIsSingle = SubstratePixelHeader.IsSingleMaterial();
Out.bIsHair = BSDFType == SUBSTRATE_BSDF_TYPE_HAIR;
Out.bIsSLW = BSDFType == SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER;
Out.bIsFirstPerson = SubstratePixelHeader.IsFirstPerson();
Out.bHasSecondSpecularLobe= BSDF_GETHASHAZINESS(BSDF);
Out.bHasBackScattering = BSDFType == SUBSTRATE_BSDF_TYPE_SLAB && SubstratePixelHeader.HasSubsurface();
Out.ClosureIndex = ClosureIndex;
Out.PDF = InPDF;
Out.Anisotropy = SubstrateGetBSDFAnisotropy(BSDF);
Out.IrradianceAO = SubstrateGetPackedIrradianceAndAO(SubstratePixelHeader);
float3 OutDiffuseColor = BSDFThroughput * SubstrateEnvLight.DiffuseColor; //SubstrateEnvLight.DiffuseWeight;
float3 OutSpecularColor = BSDFThroughput * SubstrateEnvLight.SpecularColor; //SubstrateEnvLight.SpecularWeight;
//#if SUBSTRATE_FASTPATH==0
if (any(SubstrateEnvLight.SpecularHazeWeight > 0.0f))
{
OutSpecularColor += BSDFThroughput * SubstrateEnvLight.SpecularHazeWeight;
}
//#endif
if (SubstrateEnvLight.bPostProcessSubsurface)
{
Out.bNeedsSeparateSubsurfaceLightAccumulation = true;
}
//#if SUBSTRATE_FASTPATH==0
if (SubstratePixelHeader.IsComplexSpecialMaterial() && BSDF_GETHASGLINT(BSDF))
{
Out.bAllowSpatialFilter = false;
}
//#endif
Out.DiffuseAlbedo = OutDiffuseColor;
Out.SpecularAlbedo = OutSpecularColor;
return Out;
}
#if DEBUG_ENABLED
void Print(inout FShaderPrintContext Ctx, FSubstrateSampledMaterial Out)
{
Newline(Ctx);
PrintLineN(Ctx, Out.WorldNormal);
PrintLineN(Ctx, Out.WorldTangent);
PrintLineN(Ctx, Out.Roughness);
PrintLineN(Ctx, Out.DiffuseAlbedo);
PrintLineN(Ctx, Out.SpecularAlbedo);
PrintLineN(Ctx, Out.Anisotropy);
PrintLineN(Ctx, Out.PDF);
PrintLineN(Ctx, Out.ClosureIndex);
PrintLineN(Ctx, Out.IrradianceAO);
PrintLineN(Ctx, Out.bIsSimple);
PrintLineN(Ctx, Out.bIsSingle);
PrintLineN(Ctx, Out.bIsValid);
PrintLineN(Ctx, Out.bIsHair);
PrintLineN(Ctx, Out.bIsSLW);
PrintLineN(Ctx, Out.bAllowSpatialFilter);
PrintLineN(Ctx, Out.bNeedsSeparateSubsurfaceLightAccumulation);
PrintLineN(Ctx, Out.bIsFirstPerson);
PrintLineN(Ctx, Out.bHasSecondSpecularLobe);
PrintLineN(Ctx, Out.bHasBackScattering);
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
[numthreads(SUBSTRATE_TILE_SIZE, SUBSTRATE_TILE_SIZE, 1)]
void SubstrateMaterialSamplingCS(uint2 DispatchThreadId : SV_DispatchThreadID)
{
const uint2 PixelCoord = DispatchThreadId + View.ViewRectMinAndSize.xy;
const bool bIsInViewRect = all(PixelCoord < uint2(View.ViewRectMinAndSize.zw));
if (bIsInViewRect)
{
const FSubstrateIntegrationSettings Settings = InitSubstrateIntegrationSettings(false /*bForceFullyRough*/, bRoughDiffuse, PeelLayersAboveDepth, bRoughnessTracking);
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelCoord, uint2(View.BufferSizeAndInvSize.xy), MaxBytesPerPixel);
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(MaterialTextureArray, SubstrateAddressing, TopLayerTexture);
const uint ClosureCount = min(SubstratePixelHeader.ClosureCount, SUBSTRATE_MATERIAL_CLOSURE_COUNT);
#if DEBUG_ENABLED
FShaderPrintContext Ctx = InitShaderPrintContextAtCursor(PixelCoord, uint2(50, 50));
Print(Ctx, TEXT("DEBUG"), FontRed); Newline(Ctx);
PrintLineN(Ctx, PixelCoord);
PrintLineN(Ctx, ClosureCount);
#endif
FSubstrateSampledMaterial Out = (FSubstrateSampledMaterial)0;
if (ClosureCount > 0)
{
const float2 ScreenUV = (PixelCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
const float SceneDepth = ConvertFromDeviceZ(SceneTexturesStruct.SceneDepthTexture.Load(int3(PixelCoord, 0)).r);
const float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth);
const float3 V = -GetCameraVectorFromTranslatedWorldPosition(TranslatedWorldPosition);
float SlabPdf = 1.0;
uint ClosureIndex = 0;
#if SUBSTRATE_MATERIAL_CLOSURE_COUNT > 1
if (ClosureCount > 1)
{
// Uniform sampling rather directional albedo sampling seems to give better results as we can reuse neighborhood
// results more easily for resolving lower/less probably layers
#define USE_UNIFORM_SAMPLING 1
#if USE_UNIFORM_SAMPLING
const float RandSample = ClosureCount * BlueNoiseScalar(PixelCoord, MegaLightsStateFrameIndex);
ClosureIndex = clamp(RandSample, 0, ClosureCount-1);
SlabPdf = 1.f / float(ClosureCount);
#else
float SlabCDF[SUBSTRATE_MATERIAL_CLOSURE_COUNT];
float SlabCDFSum = 0.0;
// Build Slabs' CDF
{
UNROLL_N(SUBSTRATE_MATERIAL_CLOSURE_COUNT)
for (uint ClosureIndex = 0; ClosureIndex < ClosureCount; ++ClosureIndex)
{
const FSubstrateBSDF BSDF = UnpackSubstrateBSDFIn(MaterialTextureArray, SubstrateAddressing, SubstratePixelHeader);
const float Pdf = GetSlabPDF(SubstrateAddressing, SubstratePixelHeader, BSDF, V);
SlabCDFSum += Pdf;
SlabCDF[ClosureIndex] = SlabCDFSum;
}
}
// Select Slab
if (SlabCDFSum > 0.0)
{
// linear search
float PreviousCdfValue = 0;
float RandSample = SlabCDFSum * BlueNoiseScalar(PixelCoord, MegaLightsStateFrameIndex);
for (ClosureIndex = 0; ClosureIndex < ClosureCount - 1; ++ClosureIndex)
{
if (RandSample < SlabCDF[ClosureIndex])
{
break;
}
PreviousCdfValue = SlabCDF[ClosureIndex];
}
SlabPdf = (SlabCDF[ClosureIndex] - PreviousCdfValue) / SlabCDFSum;
#if DEBUG_ENABLED
PrintLineN(Ctx, SlabPdf);
for (uint CDFIndex = 0; CDFIndex < ClosureCount; ++CDFIndex)
{
PrintLineN(Ctx, SlabCDF[CDFIndex]);
}
#endif
}
#endif // USE_UNIFORM_SAMPLING
}
else
#endif // SUBSTRATE_MATERIAL_CLOSURE_COUNT
{
ClosureIndex = 0;
SlabPdf = 1.0;
}
Out = GetSampledMaterial(PixelCoord, ClosureIndex, V, SlabPdf);
#if DEBUG_ENABLED
Print(Ctx, Out);
#endif
}
RWMaterialData[PixelCoord] = PackSampledMaterial(Out);
}
}
#endif // SHADER_SAMPLE_MATERIAL