506 lines
17 KiB
HLSL
506 lines
17 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Common.ush"
|
|
#include "DeferredShadingCommon.ush"
|
|
#include "Substrate/Substrate.ush"
|
|
|
|
#define FP_MAX_VALID_HIT_DISTANCE (254.0f / 255.0f)
|
|
|
|
bool IsFirstPersonPixel(uint2 PixelPos)
|
|
{
|
|
#if SUBTRATE_GBUFFER_FORMAT
|
|
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
|
|
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
|
|
const bool bIsFirstPerson = SubstratePixelHeader.IsFirstPerson();
|
|
#else
|
|
const float2 BufferUV = (PixelPos + 0.5f) * View.BufferSizeAndInvSize.zw;
|
|
const FGBufferData GBuffer = GetGBufferData(BufferUV);
|
|
const bool bIsFirstPerson = IsFirstPerson(GBuffer);
|
|
#endif // SUBSTRATE_ENABLED
|
|
|
|
return bIsFirstPerson;
|
|
}
|
|
|
|
float3 GetWorldNormal(uint2 PixelPos)
|
|
{
|
|
#if SUBTRATE_GBUFFER_FORMAT==1
|
|
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(int3(PixelPos, 0)));
|
|
const float3 WorldNormal = TopLayerData.WorldNormal;
|
|
#else
|
|
// Normals are in a different texture than the first person bit, so we do not redundantly sample the same texture twice (assuming the compiler strips the unused GBuffer values).
|
|
const float2 BufferUV = (PixelPos + 0.5f) * View.BufferSizeAndInvSize.zw;
|
|
const FGBufferData GBuffer = GetGBufferData(BufferUV);
|
|
const float3 WorldNormal = GBuffer.WorldNormal;
|
|
#endif
|
|
|
|
return WorldNormal;
|
|
}
|
|
|
|
#ifdef FirstPersonSelfShadowDownsamplePS
|
|
|
|
uint CheckerboardMode; // 0: always min, 1: always max, 2: checkerboard
|
|
|
|
void FirstPersonSelfShadowDownsamplePS(
|
|
in float4 SVPos : SV_Position,
|
|
out float4 OutColor : SV_Target0,
|
|
out float OutDepth : SV_Depth
|
|
)
|
|
{
|
|
const uint2 DstPixelPos = uint2(SVPos.xy);
|
|
float MinZ = FarDepthValue;
|
|
float MaxZ = FarDepthValue;
|
|
uint2 MinZPixelPos = 0u;
|
|
uint2 MaxZPixelPos = 0u;
|
|
for (uint y = 0; y < 2; ++y)
|
|
{
|
|
for (uint x = 0; x < 2; ++x)
|
|
{
|
|
const uint2 FullResPixelPos = min(DstPixelPos * 2u + uint2(x, y), View.ViewRectMinAndSize.zw) + View.ViewRectMinAndSize.xy;
|
|
float DeviceZ = LookupDeviceZ(FullResPixelPos);
|
|
|
|
// Ignore far depth values
|
|
if (DeviceZ != FarDepthValue)
|
|
{
|
|
const bool bIsFirstPerson = IsFirstPersonPixel(FullResPixelPos);
|
|
|
|
// When the pixel is not first person, set DeviceZ to FarDepthValue so that we ignore it in the checks below.
|
|
DeviceZ = bIsFirstPerson ? DeviceZ : FarDepthValue;
|
|
|
|
if (DeviceZ != FarDepthValue && (MinZ == FarDepthValue || DeviceZ < MinZ))
|
|
{
|
|
MinZ = DeviceZ;
|
|
MinZPixelPos = FullResPixelPos;
|
|
}
|
|
if (DeviceZ != FarDepthValue && (MaxZ == FarDepthValue || DeviceZ > MaxZ))
|
|
{
|
|
MaxZ = DeviceZ;
|
|
MaxZPixelPos = FullResPixelPos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bCheckerboard = false;
|
|
switch (CheckerboardMode)
|
|
{
|
|
case 0: bCheckerboard = true; break;
|
|
case 1: bCheckerboard = false; break;
|
|
case 2: bCheckerboard = ((DstPixelPos.x + DstPixelPos.y) & 1u) != 0u; break;
|
|
}
|
|
const float DownsampledDepth = bCheckerboard ? MinZ : MaxZ;
|
|
|
|
// Early out
|
|
if (DownsampledDepth == FarDepthValue)
|
|
{
|
|
discard;
|
|
}
|
|
|
|
const uint2 FullResPixelPos = bCheckerboard ? MinZPixelPos : MaxZPixelPos;
|
|
const float3 WorldNormal = GetWorldNormal(FullResPixelPos);
|
|
const float2 WorldNormalOct = UnitVectorToOctahedron(WorldNormal);
|
|
|
|
const uint2 BaseFullResPixelPos = min(DstPixelPos * 2u, View.ViewRectMinAndSize.zw) + View.ViewRectMinAndSize.xy;
|
|
const uint2 FullResPixelOffset = FullResPixelPos - BaseFullResPixelPos; // Should only be 0 or 1. Taking this offset into account for tracing gives better results.
|
|
// Could do tighter packing of the offset here, but we need at least 16 bits for the normal for good results, so we need to go with RGBA8 anyways.
|
|
OutColor = float4(WorldNormalOct * 0.5f + 0.5f, FullResPixelOffset.x / 255.0f, FullResPixelOffset.y / 255.0f);
|
|
OutDepth = DownsampledDepth;
|
|
}
|
|
|
|
#endif // FirstPersonSelfShadowDownsamplePS
|
|
|
|
|
|
#ifdef FirstPersonSelfShadowTracePS
|
|
|
|
#include "DynamicLightingCommon.ush"
|
|
#include "DeferredShadingCommon.ush"
|
|
#include "LightDataUniforms.ush"
|
|
#define HZB_TRACE_INCLUDE_FULL_RES_DEPTH 1
|
|
#define HZB_TRACE_USE_BILINEAR_SAMPLED_DEPTH 1
|
|
#include "HZBTracing.ush"
|
|
#include "HZB.ush"
|
|
|
|
#ifndef RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
#define RAW_FULL_RESOLUTION_FP_SELF_SHADOWS 0
|
|
#endif
|
|
|
|
// Remap light shape permutation into light type
|
|
#if LIGHT_SOURCE_SHAPE == 0
|
|
#define CURRENT_LIGHT_TYPE LIGHT_TYPE_DIRECTIONAL
|
|
#elif LIGHT_SOURCE_SHAPE == 2
|
|
#define CURRENT_LIGHT_TYPE LIGHT_TYPE_RECT
|
|
#else // LIGHT_SOURCE_SHAPE == 1
|
|
#define CURRENT_LIGHT_TYPE (IsDeferredSpotlight() ? LIGHT_TYPE_SPOT : LIGHT_TYPE_POINT)
|
|
#endif
|
|
|
|
#if !RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
Texture2D<float4> DownsampledInputsTexture;
|
|
Texture2D<float> DownsampledDepthTexture;
|
|
#endif
|
|
|
|
float HZBMaxTraceDistance;
|
|
float HZBMaxIterations;
|
|
float HZBRelativeDepthThickness;
|
|
uint HZBMinimumTracingThreadOccupancy;
|
|
|
|
struct FFirstPersonScreenTracingInputs
|
|
{
|
|
uint2 FullResPixelPos;
|
|
float2 FullResSVPos;
|
|
float DeviceZ;
|
|
float SceneDepth;
|
|
float3 WorldNormal;
|
|
};
|
|
|
|
FFirstPersonScreenTracingInputs GetFirstPersonScreenTracingInputs(uint2 PixelPos)
|
|
{
|
|
FFirstPersonScreenTracingInputs Result;
|
|
|
|
#if RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
|
|
Result.FullResPixelPos = PixelPos;
|
|
Result.FullResSVPos = PixelPos + 0.5f;
|
|
|
|
// When running in full resolution, we don't have a stencil bit to let the hardware early-out on non FP pixels, so we do it manually here.
|
|
const bool bIsFirstPerson = IsFirstPersonPixel(Result.FullResPixelPos);
|
|
if (!bIsFirstPerson)
|
|
{
|
|
discard;
|
|
}
|
|
|
|
Result.DeviceZ = LookupDeviceZ(Result.FullResPixelPos);
|
|
Result.SceneDepth = ConvertFromDeviceZ(Result.DeviceZ);
|
|
Result.WorldNormal = GetWorldNormal(Result.FullResPixelPos);
|
|
|
|
#else // RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
|
|
const float4 DownsampledInputs = DownsampledInputsTexture.Load(int3(PixelPos, 0));
|
|
const uint2 PixelOffset = uint2(DownsampledInputs.zw * 255.0f);
|
|
|
|
Result.FullResPixelPos = min(PixelPos * 2u + PixelOffset, View.ViewRectMinAndSize.zw) + View.ViewRectMinAndSize.xy;
|
|
Result.FullResSVPos = Result.FullResPixelPos + 0.5f;
|
|
Result.DeviceZ = DownsampledDepthTexture.Load(int3(PixelPos, 0)).x;
|
|
Result.SceneDepth = ConvertFromDeviceZ(Result.DeviceZ);
|
|
Result.WorldNormal = OctahedronToUnitVector(DownsampledInputs.xy * 2.0f - 1.0f);
|
|
|
|
#endif // RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
|
|
return Result;
|
|
}
|
|
|
|
float4 EncodeOutput(float Shadow, float HitDistance)
|
|
{
|
|
#if RAW_FULL_RESOLUTION_FP_SELF_SHADOWS
|
|
return EncodeLightAttenuation(Shadow);
|
|
#else
|
|
// Store a normalized hit distance (in pixels). 0 means immediate intersection, 1 means miss and 254/255 is the longest representable hit distance.
|
|
const float MaxPixelDistance = 64.0f; // Seems to give decent results with shadows contact hardening up to 64 pixels from the shadow caster.
|
|
HitDistance = (HitDistance >= 0.0f) ? (saturate(HitDistance / MaxPixelDistance) * FP_MAX_VALID_HIT_DISTANCE) : 1.0f;
|
|
|
|
return HitDistance;
|
|
#endif
|
|
}
|
|
|
|
bool IsLightVisible(FDeferredLightData Light, float3 TranslatedWorldPosition, float3 Normal)
|
|
{
|
|
if (!Light.bRadialLight)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const float3 ToLight = Light.TranslatedWorldPosition - TranslatedWorldPosition;
|
|
const float InvDist = rsqrt(dot(ToLight, ToLight));
|
|
const float3 L = ToLight * InvDist;
|
|
|
|
const bool bInLightRadius = InvDist >= Light.InvRadius;
|
|
const bool bInLightCone = SpotAttenuationMask(L, -Light.Direction, Light.SpotAngles) > 0.0f;
|
|
|
|
bool bInLightRegion = bInLightRadius && bInLightCone;
|
|
|
|
if (Light.bRectLight)
|
|
{
|
|
const bool bIsBehindRectLight = dot(ToLight, Light.Direction) < 0.0f;
|
|
if (bIsBehindRectLight)
|
|
{
|
|
bInLightRegion = false;
|
|
}
|
|
}
|
|
|
|
return bInLightRegion;
|
|
}
|
|
|
|
float TraceFirstPersonScreenSpaceShadows(FFirstPersonScreenTracingInputs Inputs, FDeferredLightData LightData, float3 TranslatedWorldPosition, out float3 OutHitUVz)
|
|
{
|
|
// Bias along the normal to avoid self-intersecting camera facing scene depth texels
|
|
// HZB traversal uses point filtering, so scene depth texels stick out from the plane they are representing
|
|
float NormalBias = 0.0f;
|
|
{
|
|
const float3 CornerTranslatedWorldPosition = SvPositionToTranslatedWorld(float4(Inputs.FullResSVPos + 0.5f, Inputs.DeviceZ, 1.0f));
|
|
// Project texel corner onto normal
|
|
NormalBias = abs(dot(CornerTranslatedWorldPosition - TranslatedWorldPosition, Inputs.WorldNormal)) * 2.0f;
|
|
}
|
|
|
|
const float3 TranslatedRayOrigin = TranslatedWorldPosition + NormalBias * Inputs.WorldNormal;
|
|
const float4 RayStartClip = mul(float4(TranslatedRayOrigin, 1.0f), View.TranslatedWorldToClip);
|
|
|
|
// Skip screen traces if the normal bias pushed our starting point off-screen, as TraceHZB() doesn't handle this
|
|
if (any(RayStartClip.xy < -RayStartClip.w) || any(RayStartClip.xy > RayStartClip.w))
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
float3 RayWorldDirection;
|
|
float MaxWorldTraceDistance;
|
|
if (LightData.bRadialLight)
|
|
{
|
|
const float3 ToLight = LightData.TranslatedWorldPosition - TranslatedRayOrigin;
|
|
const float LightDistance = length(ToLight);
|
|
RayWorldDirection = ToLight / LightDistance;
|
|
MaxWorldTraceDistance = min(LightDistance, HZBMaxTraceDistance);
|
|
}
|
|
else
|
|
{
|
|
RayWorldDirection = LightData.Direction;
|
|
MaxWorldTraceDistance = min(1.0e27f, HZBMaxTraceDistance);
|
|
}
|
|
|
|
bool bHit = false;
|
|
bool bUncertain = false;
|
|
float3 OutLastVisibleScreenUV = 0.0f;
|
|
float OutHitTileZ = 0.0f;
|
|
|
|
TraceHZB(
|
|
SceneTexturesStruct.SceneDepthTexture,
|
|
ClosestHZBTexture,
|
|
HZBBaseTexelSize,
|
|
HZBUVToScreenUVScaleBias,
|
|
TranslatedRayOrigin,
|
|
RayWorldDirection,
|
|
MaxWorldTraceDistance,
|
|
HZBUvFactorAndInvFactor,
|
|
HZBMaxIterations,
|
|
HZBRelativeDepthThickness,
|
|
0 /*NumThicknessStepsToDetermineCertainty*/,
|
|
HZBMinimumTracingThreadOccupancy,
|
|
bHit,
|
|
bUncertain,
|
|
OutHitUVz,
|
|
OutLastVisibleScreenUV,
|
|
OutHitTileZ);
|
|
|
|
return bHit || bUncertain ? 0.0f : 1.0f;
|
|
}
|
|
|
|
void FirstPersonSelfShadowTracePS(
|
|
float4 SVPos : SV_Position,
|
|
out float4 OutColor : SV_Target0)
|
|
{
|
|
const FDeferredLightData LightData = InitDeferredLightFromUniforms(CURRENT_LIGHT_TYPE);
|
|
const FFirstPersonScreenTracingInputs Inputs = GetFirstPersonScreenTracingInputs(SVPos.xy);
|
|
const float3 TranslatedWorldPosition = SvPositionToTranslatedWorld(float4(Inputs.FullResSVPos, Inputs.DeviceZ, 1.0f));
|
|
|
|
float Shadow = 1.0f;
|
|
float HitDistance = -1.0f;
|
|
if (IsLightVisible(LightData, TranslatedWorldPosition, Inputs.WorldNormal))
|
|
{
|
|
float3 HitUVz;
|
|
Shadow = TraceFirstPersonScreenSpaceShadows(Inputs, LightData, TranslatedWorldPosition, HitUVz);
|
|
|
|
if (Shadow < 1.0f)
|
|
{
|
|
HitDistance = distance(HitUVz.xy * View.BufferSizeAndInvSize.xy, Inputs.FullResSVPos);
|
|
}
|
|
}
|
|
|
|
OutColor = EncodeOutput(Shadow, HitDistance);
|
|
}
|
|
|
|
#endif // FirstPersonSelfShadowTracePS
|
|
|
|
|
|
#ifdef FirstPersonSelfShadowBlurPS
|
|
|
|
Texture2D<float4> InputsTexture;
|
|
Texture2D<float> DepthTexture;
|
|
float InvDepthThreshold;
|
|
|
|
struct FFirstPersonBlurInputs
|
|
{
|
|
float DeviceZ;
|
|
float SceneDepth;
|
|
float HitDistance;
|
|
float ShadowFactor;
|
|
};
|
|
|
|
FFirstPersonBlurInputs LoadInputs(uint2 PixelPos)
|
|
{
|
|
FFirstPersonBlurInputs Result = (FFirstPersonBlurInputs)0;
|
|
Result.DeviceZ = DepthTexture.Load(int3(PixelPos, 0)).x;
|
|
if (Result.DeviceZ != FarDepthValue)
|
|
{
|
|
Result.SceneDepth = ConvertFromDeviceZ(Result.DeviceZ);
|
|
|
|
const float PackedShadowAndHitDistance = InputsTexture.Load(int3(PixelPos, 0)).x;
|
|
Result.HitDistance = PackedShadowAndHitDistance != 1.0f ? (PackedShadowAndHitDistance / FP_MAX_VALID_HIT_DISTANCE) : -1.0f;
|
|
Result.ShadowFactor = PackedShadowAndHitDistance != 1.0f ? 0.0f : 1.0f;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
struct FSampleAccumulator
|
|
{
|
|
float ShadowSum;
|
|
float HitDistanceSum;
|
|
float ShadowWeightSum;
|
|
float HitDistanceWeightSum;
|
|
};
|
|
|
|
void AccumulateSample(inout FSampleAccumulator Accumulator, FFirstPersonBlurInputs CenterInputs, uint2 SamplePixelPos)
|
|
{
|
|
const FFirstPersonBlurInputs SampleInputs = LoadInputs(SamplePixelPos);
|
|
if (SampleInputs.DeviceZ != FarDepthValue)
|
|
{
|
|
const float Scale = InvDepthThreshold;
|
|
const float DepthWeight = min(1.0f, 1.0f / (Scale * abs(CenterInputs.SceneDepth - SampleInputs.SceneDepth) + 1e-4f));
|
|
const float SampleWeight = DepthWeight;
|
|
|
|
Accumulator.ShadowSum += SampleInputs.ShadowFactor * SampleWeight;
|
|
Accumulator.ShadowWeightSum += SampleWeight;
|
|
if (SampleInputs.HitDistance > 0.0f)
|
|
{
|
|
Accumulator.HitDistanceSum += SampleInputs.HitDistance * SampleWeight;
|
|
Accumulator.HitDistanceWeightSum += SampleWeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FirstPersonSelfShadowBlurPS(
|
|
float4 SVPos : SV_Position,
|
|
out float4 OutColor : SV_Target0)
|
|
{
|
|
const uint2 PixelPos = uint2(SVPos.xy);
|
|
|
|
const FFirstPersonBlurInputs CenterInputs = LoadInputs(PixelPos);
|
|
|
|
// Initialize accumulator with center input
|
|
FSampleAccumulator Accumulator = (FSampleAccumulator)0;
|
|
Accumulator.ShadowSum = CenterInputs.ShadowFactor;
|
|
Accumulator.ShadowWeightSum = 1.0f;
|
|
Accumulator.HitDistanceSum = CenterInputs.HitDistance > 0.0f ? CenterInputs.HitDistance : 0.0f;
|
|
Accumulator.HitDistanceWeightSum = CenterInputs.HitDistance > 0.0f ? 1.0f : 0.0f;
|
|
|
|
|
|
// Accumulate samples in a 3x3 area
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2(-1, -1));
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2( 0, -1));
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2( 1, -1));
|
|
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2(-1, 0));
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2( 1, 0));
|
|
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2(-1, 1));
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2( 0, 1));
|
|
AccumulateSample(Accumulator, CenterInputs, PixelPos + int2( 1, 1));
|
|
|
|
const float InvShadowWeightSum = 1.0f / Accumulator.ShadowWeightSum; // ShadowWeightSum should be at least 1.0f due to the center pixel
|
|
const float InvHitDistanceWeightSum = Accumulator.HitDistanceWeightSum > 1e-5f ? (1.0f / Accumulator.HitDistanceWeightSum) : 0.0f; // HitDistanceWeightSum can actually be zero
|
|
|
|
const float BlurredShadow = Accumulator.ShadowSum * InvShadowWeightSum;
|
|
const float AverageHitDistance = Accumulator.HitDistanceSum * InvHitDistanceWeightSum;
|
|
const float FilterStrength = saturate(AverageHitDistance);
|
|
|
|
// Make shadow contact hardening by lerping between the filtered and unfiltered result. Simple, but works well enough for 3x3.
|
|
const float FinalShadow = lerp(CenterInputs.ShadowFactor, BlurredShadow, FilterStrength);
|
|
|
|
OutColor = EncodeLightAttenuation(FinalShadow);
|
|
}
|
|
|
|
#endif // FirstPersonSelfShadowBlurPS
|
|
|
|
|
|
#ifdef FirstPersonSelfShadowUpsamplePS
|
|
|
|
#include "DeferredShadingCommon.ush"
|
|
|
|
Texture2D<float> DownsampledDepthTexture;
|
|
Texture2D<float4> ShadowFactorsTexture;
|
|
float2 DownsampledInvBufferSize;
|
|
float InvDepthThreshold;
|
|
|
|
float4 GatherDepths(float2 UV)
|
|
{
|
|
return DownsampledDepthTexture.GatherRed(GlobalBilinearClampedSampler, UV);
|
|
}
|
|
|
|
float4 GatherShadowFactors(float2 UV)
|
|
{
|
|
return ShadowFactorsTexture.GatherRed(GlobalBilinearClampedSampler, UV);
|
|
}
|
|
|
|
float4 GetBilinearWeights(float2 DownsampledSVPos)
|
|
{
|
|
const float2 DownsampledCoordFrac = frac(DownsampledSVPos - 0.5f);
|
|
const float4 BilinearWeights = float4(
|
|
(1.0f - DownsampledCoordFrac.x) * DownsampledCoordFrac.y,
|
|
DownsampledCoordFrac.x * DownsampledCoordFrac.y,
|
|
DownsampledCoordFrac.x * (1.0f - DownsampledCoordFrac.y),
|
|
(1.0f - DownsampledCoordFrac.x) * (1.0f - DownsampledCoordFrac.y)
|
|
);
|
|
|
|
return BilinearWeights;
|
|
}
|
|
|
|
float4 GetDepthWeights(float FullResSceneDepth, float4 DownsampledDeviceZ)
|
|
{
|
|
const float4 DownsampledSceneDepths = float4(
|
|
ConvertFromDeviceZ(DownsampledDeviceZ.x),
|
|
ConvertFromDeviceZ(DownsampledDeviceZ.y),
|
|
ConvertFromDeviceZ(DownsampledDeviceZ.z),
|
|
ConvertFromDeviceZ(DownsampledDeviceZ.w));
|
|
|
|
const float4 DepthWeights = min(1.0f, 1.0f / (InvDepthThreshold * abs(FullResSceneDepth - DownsampledSceneDepths) + 1e-4f));
|
|
|
|
// Downsampled depth has been forced to FarDepthValue for non-FP pixels and we want to ensure they're excluded from the weighting.
|
|
const float4 FirstPersonDepthWeights = select(DownsampledDeviceZ != FarDepthValue, 1.0f, 0.0f);
|
|
|
|
return DepthWeights * FirstPersonDepthWeights;
|
|
}
|
|
|
|
void FirstPersonSelfShadowUpsamplePS(
|
|
float4 SVPos : SV_Position,
|
|
out float4 OutColor : SV_Target0)
|
|
{
|
|
const bool bIsFirstPerson = IsFirstPersonPixel(uint2(SVPos.xy));
|
|
if (!bIsFirstPerson)
|
|
{
|
|
discard;
|
|
}
|
|
|
|
const float2 DownsampledSVPos = (SVPos.xy - View.ViewRectMinAndSize.xy) * 0.5f;
|
|
const float2 DownsampledUV = DownsampledSVPos * DownsampledInvBufferSize;
|
|
const float4 DownsampledDeviceZ = GatherDepths(DownsampledUV);
|
|
|
|
// Calculate weights
|
|
const float4 DepthWeights = GetDepthWeights(CalcSceneDepth(uint2(SVPos.xy)), DownsampledDeviceZ);
|
|
const float4 BilinearWeights = GetBilinearWeights(DownsampledSVPos);
|
|
|
|
// Normalize weights or fall back to bilinear
|
|
float4 TotalWeights = DepthWeights * BilinearWeights;
|
|
const float TotalWeightSum = dot(TotalWeights, 1.0f);
|
|
if (TotalWeightSum > 1e-5f)
|
|
{
|
|
TotalWeights /= TotalWeightSum;
|
|
}
|
|
else
|
|
{
|
|
TotalWeights = BilinearWeights;
|
|
}
|
|
|
|
// Load low res shadow factors and apply weights
|
|
const float4 LowResShadowFactors = DecodeLightAttenuation(GatherShadowFactors(DownsampledUV));
|
|
const float UpsampledShadowFactor = dot(LowResShadowFactors, TotalWeights);
|
|
|
|
OutColor = EncodeLightAttenuation(UpsampledShadowFactor);
|
|
}
|
|
|
|
#endif // FirstPersonSelfShadowUpsamplePS
|