Files
UnrealEngineUWP/Engine/Shaders/Private/ShadowProjectionPixelShader.usf
Charles deRousiers 0a30a91970 Add Strata wrap-sub-surface lighting support.
This is the initial version for supporting wrap lighting with Strata, allowing to convert legacy subsurface surface into Strata. This initial version is not fully correct (energy perservation is not correct due to non-normalized phase function, which needs to be revisited).

#rb none
#jira none
#fyi sebastien.hillaire
#preflight shaders

[CL 20345473 by Charles deRousiers in ue5-main branch]
2022-05-24 04:52:51 -04:00

487 lines
18 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ShadowProjectionPixelShader.usf: Pixel shader for projecting a shadow depth buffer onto the scene.
=============================================================================*/
#ifndef USE_FADE_PLANE
#define USE_FADE_PLANE 0
#endif
#ifndef SHADOW_QUALITY
#define SHADOW_QUALITY 6
#endif
#ifndef APPLY_TRANSLUCENCY_SHADOWS
#define APPLY_TRANSLUCENCY_SHADOWS 0
#endif
#ifndef USE_PCSS
#define USE_PCSS 0
#endif
#ifndef SPOT_LIGHT_PCSS
#define SPOT_LIGHT_PCSS 0
#endif
#include "HairStrands/HairStrandsVisibilityCommonStruct.ush"
#include "Common.ush"
#if MODULATED_SHADOWS
#define MobileSceneTextures MobileBasePass.SceneTextures
#endif
#include "ShadowProjectionCommon.ush"
#include "ShadowFilteringCommon.ush"
#if USE_PCSS
#include "ShadowPercentageCloserFiltering.ush"
#endif
#include "DeferredShadingCommon.ush"
#include "DynamicLightingCommon.ush"
float ShadowFadeFraction;
float ShadowSharpen;
float4 LightPositionAndInvRadius;
float PerObjectShadowFadeStart;
float InvPerObjectShadowFadeLength;
#if USE_TRANSMISSION
#include "TransmissionThickness.ush"
#else
float4x4 ScreenToShadowMatrix;
// .x:DepthBias, .y:SlopeDepthBias, .z:ReceiverBias, .w: MaxSubjectZ - MinSubjectZ
float4 ProjectionDepthBiasParameters;
#endif
float4 LightPositionOrDirection;
float ShadowReceiverBias;
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
float FadePlaneOffset;
float InvFadePlaneLength;
uint bCascadeUseFadePlane;
#endif
#if USE_PCSS
// PCSS specific parameters.
// - x: tan(0.5 * Directional Light Angle) in shadow projection space;
// - y: Max filter size in shadow tile UV space.
float4 PCSSParameters;
#endif
float4 ModulatedShadowColor;
float4 ShadowTileOffsetAndSize;
float3 ModulatedShadowBlendOp(float3 Source)
{
half4 Dest = half4(0, 0, 0, 0);
return Source.rgb*Dest.rgb;
}
#if SUBPIXEL_SHADOW
#include "HairStrands/HairStrandsVisibilityCommon.ush"
float2 ShadowNearAndFarDepth;
#endif
#include "Strata/Strata.ush"
/**
* Entry point for uniform manual PCF that supports lights using normal shadows.
*/
// Do not force early_fragment_tests on mobile, as it does not work with depth fetch on MALI
#if (FEATURE_LEVEL > FEATURE_LEVEL_ES3_1)
EARLYDEPTHSTENCIL
#endif
void Main(
in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0
)
{
#if USE_FADE_PLANE
const bool bUseFadePlane = true;
#endif
const FPQMPContext PQMPContext = PQMPInit(SVPos.xy);
float2 ScreenUV = float2(SVPos.xy * View.BufferSizeAndInvSize.zw);
float SceneW = CalcSceneDepth(ScreenUV);
bool bIsSubsurfaceCompatible = true;
const uint3 PixelCoord = uint3(floor(SVPos.xy), 0);
#if SUBPIXEL_SHADOW
const bool bUseFadePlane = bCascadeUseFadePlane > 0;
SceneW = ConvertFromDeviceZ(HairStrands.HairOnlyDepthTexture.Load(PixelCoord));
bIsSubsurfaceCompatible = false;
#endif
float4 ScreenPosition = float4(((ScreenUV.xy - View.ScreenPositionScaleBias.wz ) / View.ScreenPositionScaleBias.xy) * SceneW, SceneW, 1);
float4 ShadowPosition = mul(ScreenPosition, ScreenToShadowMatrix);
float3 TranslateWorldPosition = mul(ScreenPosition, View.ScreenToTranslatedWorld).xyz;
float ShadowZ = 1 - ShadowPosition.z;
ShadowPosition.xyz /= ShadowPosition.w;
#if MODULATED_SHADOWS
// UE-29083 : work around precision issues with ScreenToShadowMatrix on low end devices.
ShadowPosition.xy *= ShadowTileOffsetAndSize.zw;
ShadowPosition.xy += ShadowTileOffsetAndSize.xy;
#endif
#if USE_PCSS
float3 ScreenPositionDDX = DDX(ScreenPosition.xyz);
float3 ScreenPositionDDY = DDY(ScreenPosition.xyz);
float4 ShadowPositionDDX = mul(float4(ScreenPositionDDX, 0), ScreenToShadowMatrix);
float4 ShadowPositionDDY = mul(float4(ScreenPositionDDY, 0), ScreenToShadowMatrix);
#if SPOT_LIGHT_PCSS
// perspective correction for derivatives, could be good enough and way cheaper to just use ddx(ScreenPosition)
ShadowPositionDDX.xyz -= ShadowPosition.xyz * ShadowPositionDDX.w;
ShadowPositionDDY.xyz -= ShadowPosition.xyz * ShadowPositionDDY.w;
#endif
#endif
// Clamp pixel depth in light space for shadowing opaque, because areas of the shadow depth buffer that weren't rendered to will have been cleared to 1
// We want to force the shadow comparison to result in 'unshadowed' in that case, regardless of whether the pixel being shaded is in front or behind that plane
float LightSpacePixelDepthForOpaque = min(ShadowZ, 0.99999f);
// Must not clamp for SSS shadowing, the subsurface gradient must continue past the far plane
float LightSpacePixelDepthForSSS = ShadowZ;
// fade out per-object shadow before cut-off, goes from 1 at start of fade plane to 0 at the end of the fade length.
float PerObjectDistanceFadeFraction = 1.0f - saturate((LightSpacePixelDepthForSSS - PerObjectShadowFadeStart) * InvPerObjectShadowFadeLength);
float Shadow = 1;
float SSSTransmission = 1;
float BlendFactor = 1;
// For debugging
#define UNFILTERED_SHADOW_PROJECTION 0
#if UNFILTERED_SHADOW_PROJECTION
{
Shadow = LightSpacePixelDepthForOpaque < Texture2DSampleLevel(ShadowDepthTexture, ShadowDepthTextureSampler, ShadowPosition.xy, 0).r;
}
#elif APPLY_TRANSLUCENCY_SHADOWS
{
Shadow = CalculateTranslucencyShadowing(ShadowPosition.xy, ShadowZ);
}
#elif USE_PCSS
{
FPCSSSamplerSettings Settings;
#if SPOT_LIGHT_PCSS
{
float CotanOuterCone = DeferredLightUniforms.SpotAngles.x * rsqrt(1. - DeferredLightUniforms.SpotAngles.x * DeferredLightUniforms.SpotAngles.x);
float WorldLightDistance = dot(DeferredLightUniforms.Direction, GetDeferredLightTranslatedWorldPosition() - TranslateWorldPosition);
Settings.ProjectedSourceRadius = 0.5 * DeferredLightUniforms.SourceRadius * CotanOuterCone / WorldLightDistance;
Settings.TanLightSourceAngle = 0;
}
#else
{
Settings.ProjectedSourceRadius = 0;
Settings.TanLightSourceAngle = PCSSParameters.x;
}
#endif
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
Settings.ShadowTileOffsetAndSize = ShadowTileOffsetAndSize;
Settings.SceneDepth = LightSpacePixelDepthForOpaque;
Settings.TransitionScale = SoftTransitionScale.z;
Settings.MaxKernelSize = PCSSParameters.y;
Settings.SvPosition = SVPos.xy;
Settings.PQMPContext = PQMPContext;
Settings.DebugViewportUV = ScreenUV;
Shadow = DirectionalPCSS(Settings, ShadowPosition.xy, ShadowPositionDDX.xyz, ShadowPositionDDY.xyz);
}
#else // !USE_PCSS
{
#if SHADING_PATH_DEFERRED && !FORWARD_SHADING && !SUBPIXEL_SHADOW
// Attenuate soft transition based on the angle with the light and the shading normal (acts as a receiver bias)
const bool bIsDirectional = LightPositionOrDirection.w == 0;
const float3 LightDirection = bIsDirectional ? -LightPositionOrDirection.xyz : normalize(LightPositionOrDirection.xyz - TranslateWorldPosition);
#if STRATA_ENABLED
const FStrataTopLayerData TopLayerData = StrataUnpackTopLayerData(Strata.TopLayerTexture.Load(uint3(SVPos.xy, 0)));
const float3 WorldNormal = TopLayerData.WorldNormal;
#else
FGBufferData GBufferData = GetGBufferData(ScreenUV);
const float3 WorldNormal = GBufferData.WorldNormal;
#endif
const float NoL = saturate(dot(WorldNormal, LightDirection));
#endif
FPCFSamplerSettings Settings;
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
#if SHADING_PATH_DEFERRED && !FORWARD_SHADING && !SUBPIXEL_SHADOW
Settings.TransitionScale = SoftTransitionScale.z * lerp(ProjectionDepthBiasParameters.z, 1.0, NoL);
#else
Settings.TransitionScale = SoftTransitionScale.z;
#endif
Settings.SceneDepth = LightSpacePixelDepthForOpaque;
Settings.bSubsurface = false;
Settings.bTreatMaxDepthUnshadowed = false;
Settings.DensityMulConstant = 0;
Settings.ProjectionDepthBiasParameters = 0;
Shadow = ManualPCF(ShadowPosition.xy, Settings);
}
#endif // !USE_PCSS
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
if (bUseFadePlane)
{
// Create a blend factor which is one before and at the fade plane, and lerps to zero at the far plane.
BlendFactor = 1.0f - saturate((SceneW - FadePlaneOffset) * InvFadePlaneLength);
}
#endif
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && !FORWARD_SHADING && !APPLY_TRANSLUCENCY_SHADOWS && !SUBPIXEL_SHADOW
bool bHasSubsurface = false;
bool bHasSubsurfaceTransmission = false;
bool bIsEyeOrHair = false;
#if STRATA_ENABLED
FStrataAddressing StrataAddressing = GetStrataPixelDataByteOffset(PixelCoord.xy, uint2(View.BufferSizeAndInvSize.xy), Strata.MaxBytesPerPixel);
FStrataPixelHeader StrataHeader = UnpackStrataHeaderIn(Strata.MaterialTextureArray, StrataAddressing, Strata.TopLayerTexture);
if (IsStrataMaterial(StrataHeader))
{
bIsEyeOrHair = StrataHasShadingModel(StrataHeader, STRATA_BSDF_TYPE_HAIR);
bHasSubsurface = HasSubsurface(StrataHeader) || bIsEyeOrHair;
bHasSubsurfaceTransmission = HasSubsurface(StrataHeader);
}
#else // STRATA_ENABLED
FGBufferData GBufferData = GetGBufferData(ScreenUV);
{
bHasSubsurface = IsSubsurfaceModel(GBufferData.ShadingModelID);
bHasSubsurfaceTransmission = GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE;
bIsEyeOrHair = GBufferData.ShadingModelID == SHADINGMODELID_HAIR || GBufferData.ShadingModelID == SHADINGMODELID_EYE;
}
#endif // STRATA_ENABLED
BRANCH
if (bIsSubsurfaceCompatible && bHasSubsurface)
{
#if STRATA_ENABLED
const FStrataSubsurfaceHeader SSSHeader = StrataLoadSubsurfaceHeader(Strata.SSSTexture, SVPos.xy);
const uint SSSType = StrataSubSurfaceHeaderGetSSSType(SSSHeader);
#endif
float Density = 1;
if (!bIsEyeOrHair)
{
#if STRATA_ENABLED
if (SSSType != SSS_TYPE_INVALID)
{
// Disble accurate SSS transmission if SSS type is no using Diffusion or Diffusion Profile
bHasSubsurfaceTransmission = bHasSubsurfaceTransmission && (SSSType >= SSS_TYPE_DIFFUSION);
Density = SubsurfaceDensityFromOpacity(StrataSubSurfaceHeaderGetOpacity(SSSHeader));
}
#else // STRATA_ENABLED
Density = SubsurfaceDensityFromOpacity(GBufferData.CustomData.a);
#endif // STRATA_ENABLED
}
FPCFSamplerSettings Settings;
Settings.ShadowDepthTexture = ShadowDepthTexture;
Settings.ShadowDepthTextureSampler = ShadowDepthTextureSampler;
Settings.ShadowBufferSize = ShadowBufferSize;
Settings.TransitionScale = SoftTransitionScale.z;
Settings.SceneDepth = LightSpacePixelDepthForSSS + ProjectionDepthBiasParameters.x;
Settings.bSubsurface = true;
Settings.bTreatMaxDepthUnshadowed = false;
Settings.DensityMulConstant = Density * ProjectionDepthBiasParameters.w;
Settings.ProjectionDepthBiasParameters = ProjectionDepthBiasParameters.xw;
#if USE_TRANSMISSION
if (bHasSubsurfaceTransmission)
{
#if STRATA_ENABLED
const FStrataTopLayerData TopLayerData = StrataUnpackTopLayerData(Strata.TopLayerTexture.Load(uint3(SVPos.xy, 0)));
const float3 WorldNormal = TopLayerData.WorldNormal;
// For non-profile version, use default transmission parameters (only NormalScale & ExtinctionScale are used)
FTransmissionProfileParams TransmissionParams = InitTransmissionProfileParams();
if (SSSType == SSS_TYPE_DIFFUSION_PROFILE)
{
TransmissionParams = GetTransmissionProfileParams(StrataSubSurfaceHeaderGetProfileId(SSSHeader));
}
#else // STRATA_ENABLED
float3 WorldNormal = GBufferData.WorldNormal;
FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams(GBufferData);
#endif // STRATA_ENABLED
SSSTransmission = CalcTransmissionThickness(ScreenPosition.xyz, LightPositionAndInvRadius.xyz, WorldNormal, TransmissionParams, Settings);
}
else
#endif
{
// ideally we use a larger filter kernel for SSSbut as Gather4 makes that harder
SSSTransmission = ManualPCF(ShadowPosition.xy, Settings);
}
}
#endif
#if !USE_PCSS
// There is no point changing the shadow sharpen on PCSS, can directly reduce the light
// source angle that would actually make the algorithm to run faster.
Shadow = saturate( (Shadow - 0.5) * ShadowSharpen + 0.5 );
#endif
// 0 is shadowed, 1 is unshadowed
// RETURN_COLOR not needed unless writing to SceneColor;
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction * PerObjectDistanceFadeFraction);
#if FORWARD_SHADING || SHADING_PATH_MOBILE
float LightInfluenceMask = GetLightInfluenceMask(TranslateWorldPosition);
// Constrain shadowing from this light to pixels inside the light's influence, since other non-overlapping lights are packed into the same channel
FadedShadow = lerp(1, FadedShadow, LightInfluenceMask);
// Write into all channels, the write mask will constrain to the correct one
OutColor = EncodeLightAttenuation(FadedShadow);
#else
float FadedSSSShadow = lerp(1.0f, Square(SSSTransmission), ShadowFadeFraction * PerObjectDistanceFadeFraction);
// the channel assignment is documented in ShadowRendering.cpp (look for Light Attenuation channel assignment)
OutColor = EncodeLightAttenuation(half4(FadedShadow, FadedSSSShadow, FadedShadow, FadedSSSShadow));
#endif
#if USE_FADE_PLANE || SUBPIXEL_SHADOW
// When the fade plane is in use for CSMs, we output the fade value in the alpha channel for blending.
if (bUseFadePlane)
{
OutColor.a = BlendFactor;
}
#endif
#if MODULATED_SHADOWS
OutColor.rgb = lerp(ModulatedShadowColor.rgb, float3(1, 1, 1), FadedShadow);
OutColor.a = 0;
#endif
}
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4
// .x:DepthBias, y: SlopeDepthBias, z: MaxSlopeDepthBias,
float3 PointLightDepthBias;
// xy: depth projection parameters
float2 PointLightProjParameters;
/** Pixel shader for projecting a one pass point light shadow from a cube map. */
void MainOnePassPointLightPS(
in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0
)
{
float2 ScreenUV = float2( SVPos.xy * View.BufferSizeAndInvSize.zw );
float SceneW = CalcSceneDepth( ScreenUV );
#if SUBPIXEL_SHADOW
const uint3 PixelCoord = uint3(floor(SVPos.xy), 0);
const float HairSampleDepth = HairStrands.HairOnlyDepthTexture.Load(PixelCoord);
if (HairSampleDepth > 0)
{
SceneW = ConvertFromDeviceZ(HairSampleDepth);
}
else
{
discard;
}
#endif
float2 ScreenPosition = ( ScreenUV.xy - View.ScreenPositionScaleBias.wz ) / View.ScreenPositionScaleBias.xy;
float3 TranslateWorldPosition = mul(float4(ScreenPosition.xy * SceneW, SceneW, 1), View.ScreenToTranslatedWorld).xyz;
// For debugging
#define OUTPUT_CUBE_SHADOW_DEPTH_NO_FILTERING 0
float3 WorldSampleToLightVec = LightPositionAndInvRadius.xyz - TranslateWorldPosition.xyz;
#if OUTPUT_CUBE_SHADOW_DEPTH_NO_FILTERING
// Note: point light shadow depth is Z / W, not linear
#if OPENGL_PROFILE || COMPILER_VULKAN || COMPILER_SWITCH
float Shadow = TextureCubeSampleLevel(ShadowDepthCubeTexture2, ShadowDepthTextureSampler, WorldSampleToLightVec, 0);
#else
float Shadow = TextureCubeSampleLevel(ShadowDepthCubeTexture, ShadowDepthTextureSampler, WorldSampleToLightVec, 0);
#endif
#else
// Normal receiver bias: Increase depth based on the angle with the light and the shading normal.
// Unlike spot/rect/direction light, point lights performs depth bias only at projection time.
// This is why the slope bias drives the normal receiver bias here.
float SlopeBias = 0;
#if !SUBPIXEL_SHADOW
{
#if STRATA_ENABLED
const FStrataTopLayerData TopLayerData = StrataUnpackTopLayerData(Strata.TopLayerTexture.Load(uint3(SVPos.xy, 0)));
const float3 WorldNormal = TopLayerData.WorldNormal;
#else // STRATA_ENABLED
FGBufferData GBufferData = GetGBufferData(ScreenUV);
const float3 WorldNormal = GBufferData.WorldNormal;
#endif // STRATA_ENABLED
const float3 LightDirection = normalize(WorldSampleToLightVec);
const float NoL = saturate(dot(WorldNormal, LightDirection));
SlopeBias = (1 - NoL) * PointLightDepthBias.y;
}
#endif
float Shadow = CubemapHardwarePCF(TranslateWorldPosition, LightPositionAndInvRadius.xyz, LightPositionAndInvRadius.w, PointLightDepthBias.x, SlopeBias, PointLightDepthBias.z);
Shadow = saturate( (Shadow - 0.5) * ShadowSharpen + 0.5 );
#endif
float FadedShadow = lerp(1.0f, Square(Shadow), ShadowFadeFraction);
#if FORWARD_SHADING
float LightInfluenceMask = GetLightInfluenceMask(TranslateWorldPosition);
FadedShadow = lerp(1, FadedShadow, LightInfluenceMask);
OutColor = EncodeLightAttenuation(FadedShadow);
#else
// Light attenuation buffer has been remapped.
// Point light shadows now write to the blue channel.
OutColor.b = EncodeLightAttenuation(FadedShadow);
OutColor.rga = 1;
// SSS is not correctly handled but at least it should be shadowed
OutColor.a = OutColor.b;
#endif
#if USE_TRANSMISSION
float Thickness = 1.0f;
#if STRATA_ENABLED
const FStrataTopLayerData TopLayerData = StrataUnpackTopLayerData(Strata.TopLayerTexture.Load(uint3(SVPos.xy, 0)));
const FStrataSubsurfaceHeader SSSHeader = StrataLoadSubsurfaceHeader(Strata.SSSTexture, SVPos.xy);
const bool IsSubsurfaceProfile = StrataSubSurfaceHeaderGetUseDiffusion(SSSHeader);
const float3 WorldNormal = TopLayerData.WorldNormal;
const FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams(StrataSubSurfaceHeaderGetProfileId(SSSHeader));
#else // STRATA_ENABLED
// TODO: Use existing GBuffer data, instead of overwriting?
FGBufferData GBufferData = GetGBufferData(ScreenUV);
bool IsSubsurfaceProfile = GBufferData.ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE;
float3 WorldNormal = GBufferData.WorldNormal;
FTransmissionProfileParams TransmissionParams = GetTransmissionProfileParams(GBufferData);
#endif // STRATA_ENABLED
//if bSubsurface get SSS shadow,else opaque shadow.
if (IsSubsurfaceProfile)
{
Thickness = CalcTransmissionThickness(WorldNormal, TransmissionParams, WorldSampleToLightVec, PointLightProjParameters, LightPositionAndInvRadius.w, 0);
}
OutColor.a = IsSubsurfaceProfile ? EncodeLightAttenuation(Thickness) : OutColor.b;
#endif // USE_TRANSMISSION
}
#endif