Files
UnrealEngineUWP/Engine/Shaders/DistanceFieldShadowing.usf
Daniel Wright d740edcf36 Fixed DFAO distance fading to unoccluded
DF shadowing from point lights now clamps cone angles to 10 degrees to avoid artifacts

[CL 2303478 by Daniel Wright in Main branch]
2014-09-18 22:00:44 -04:00

568 lines
21 KiB
Plaintext

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DistanceFieldShadowing.usf
=============================================================================*/
#include "Common.usf"
#include "DeferredShadingCommon.usf"
#include "DistanceFieldLightingShared.usf"
bool RayHitSphere(float3 RayOrigin, float3 UnitRayDirection, float RayLength, float3 SphereCenter, float SphereRadius)
{
float DistanceAlongRay = dot(SphereCenter - RayOrigin, UnitRayDirection);
float3 ClosestPointOnRay = DistanceAlongRay * UnitRayDirection;
float3 CenterToRay = RayOrigin + ClosestPointOnRay - SphereCenter;
return dot(CenterToRay, CenterToRay) <= Square(SphereRadius) && DistanceAlongRay > -SphereRadius && DistanceAlongRay - SphereRadius < RayLength;
}
RWBuffer<uint> RWTileHeadDataUnpacked;
RWBuffer<uint> RWTileArrayData;
float2 NumGroups;
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void ClearTilesCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint TileIndex = DispatchThreadId.y * NumGroups.x + DispatchThreadId.x;
RWTileHeadDataUnpacked[TileIndex * 2 + 0] = TileIndex;
RWTileHeadDataUnpacked[TileIndex * 2 + 1] = 0;
}
struct FShadowObjectCullVertexOutput
{
float4 Position : SV_POSITION;
nointerpolation float4 PositionAndRadius : TEXCOORD0;
nointerpolation uint ObjectIndex : TEXCOORD1;
};
float ConservativeRadiusScale;
float MinRadius;
float4x4 WorldToShadow;
/** Used when culling objects into screenspace tile lists */
void ShadowObjectCullVS(
float4 InPosition : ATTRIBUTE0,
uint ObjectIndex : SV_InstanceID,
out FShadowObjectCullVertexOutput Output
)
{
float4 ObjectPositionAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
// ConservativeRadiusScale pushes the sphere vertices out so the triangles between them lie completely outside the sphere
// MinRadius is for conservative rasterization
float EffectiveRadius = (ObjectPositionAndRadius.w + MinRadius) * ConservativeRadiusScale;
float3 WorldPosition = InPosition.xyz * EffectiveRadius + ObjectPositionAndRadius.xyz;
Output.Position = mul(float4(WorldPosition, 1), WorldToShadow);
Output.PositionAndRadius = ObjectPositionAndRadius;
Output.ObjectIndex = ObjectIndex;
}
/** Intersects a single object with the tile and adds to the intersection list if needed. */
void ShadowObjectCullPS(
FShadowObjectCullVertexOutput Input,
in float4 SVPos : SV_POSITION,
out float4 OutColor : COLOR0)
{
OutColor = 0;
uint2 TilePosition = (uint2)SVPos.xy;
uint TileIndex = TilePosition.y * NumGroups.x + TilePosition.x;
#define OBJECT_OBB_INTERSECTION 1
#if OBJECT_OBB_INTERSECTION
float3 ShadowTileMin;
float3 ShadowTileMax;
float2 TilePositionForCulling = float2(TilePosition.x, NumGroups.y - TilePosition.y);
//@todo - why is this expand needed
float TileExpand = 1;
ShadowTileMin.xy = (TilePositionForCulling - TileExpand) / (float2)NumGroups * 2 - 1;
ShadowTileMax.xy = (TilePositionForCulling + 1 + TileExpand) / (float2)NumGroups * 2 - 1;
ShadowTileMin.z = 0;
ShadowTileMax.z = 1;
float3 ObjectViewSpaceMin;
float3 ObjectViewSpaceMax;
LoadObjectViewSpaceBox(Input.ObjectIndex, ObjectViewSpaceMin, ObjectViewSpaceMax);
BRANCH
// Separating axis test on the AABB
if (all(ObjectViewSpaceMax > ShadowTileMin) && all(ObjectViewSpaceMin < ShadowTileMax))
{
float3 ObjectCenter = .5f * (ObjectViewSpaceMin + ObjectViewSpaceMax);
float3 MinProjections = 500000;
float3 MaxProjections = -500000;
{
float3 Corners[8];
Corners[0] = float3(ShadowTileMin.x, ShadowTileMin.y, ShadowTileMin.z);
Corners[1] = float3(ShadowTileMax.x, ShadowTileMin.y, ShadowTileMin.z);
Corners[2] = float3(ShadowTileMin.x, ShadowTileMax.y, ShadowTileMin.z);
Corners[3] = float3(ShadowTileMax.x, ShadowTileMax.y, ShadowTileMin.z);
Corners[4] = float3(ShadowTileMin.x, ShadowTileMin.y, ShadowTileMax.z);
Corners[5] = float3(ShadowTileMax.x, ShadowTileMin.y, ShadowTileMax.z);
Corners[6] = float3(ShadowTileMin.x, ShadowTileMax.y, ShadowTileMax.z);
Corners[7] = float3(ShadowTileMax.x, ShadowTileMax.y, ShadowTileMax.z);
float3 ObjectAxisX;
float3 ObjectAxisY;
float3 ObjectAxisZ;
LoadObjectAxes(Input.ObjectIndex, ObjectAxisX, ObjectAxisY, ObjectAxisZ);
UNROLL
for (int i = 0; i < 8; i++)
{
float3 CenterToVertex = Corners[i] - ObjectCenter;
float3 Projections = float3(dot(CenterToVertex, ObjectAxisX), dot(CenterToVertex, ObjectAxisY), dot(CenterToVertex, ObjectAxisZ));
MinProjections = min(MinProjections, Projections);
MaxProjections = max(MaxProjections, Projections);
}
}
BRANCH
// Separating axis test on the OBB
if (all(MinProjections < 1) && all(MaxProjections > -1))
{
uint ArrayIndex;
InterlockedAdd(RWTileHeadDataUnpacked[TileIndex * 2 + 1], 1U, ArrayIndex);
if (ArrayIndex < MAX_OBJECTS_PER_TILE)
{
uint DataIndex = (ArrayIndex * (uint)(NumGroups.x * NumGroups.y + .5f) + TileIndex);
RWTileArrayData[DataIndex] = Input.ObjectIndex;
}
}
}
#else
{
uint ArrayIndex;
InterlockedAdd(RWTileHeadDataUnpacked[TileIndex * 2 + 1], 1U, ArrayIndex);
if (ArrayIndex < MAX_OBJECTS_PER_TILE)
{
uint DataIndex = (ArrayIndex * (uint)(NumGroups.x * NumGroups.y + .5f) + TileIndex);
RWTileArrayData[DataIndex] = Input.ObjectIndex;
}
}
#endif
}
RWTexture2D<float2> RWShadowFactors;
/** From point being shaded toward light, for directional lights. */
float3 LightDirection;
float4 LightPositionAndInvRadius;
float LightSourceRadius;
float2 TanLightAngleAndNormalThreshold;
int4 ScissorRectMinAndSize;
#if SCATTER_TILE_CULLING
Buffer<uint> TileHeadDataUnpacked;
Buffer<uint> TileArrayData;
uint2 TileListGroupSize;
uint2 GetTileHead(uint2 TileCoordinate)
{
uint TileIndex = TileCoordinate.y * TileListGroupSize.x + TileCoordinate.x;
return uint2(
TileHeadDataUnpacked[TileIndex * 2 + 0],
min(TileHeadDataUnpacked[TileIndex * 2 + 1], (uint)MAX_OBJECTS_PER_TILE));
}
#else // SCATTER_TILE_CULLING
/** Min and Max depth for this tile. */
groupshared uint IntegerTileMinZ;
groupshared uint IntegerTileMaxZ;
/** Inner Min and Max depth for this tile. */
groupshared uint IntegerTileMinZ2;
groupshared uint IntegerTileMaxZ2;
/** Number of objects affecting the tile, after culling. */
groupshared uint TileNumObjects0;
groupshared uint TileNumObjects1;
#define MAX_INTERSECTING_OBJECTS 256
groupshared uint IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS * 2];
#endif
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void DistanceFieldShadowingCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint ThreadIndex = GroupThreadId.y * THREADGROUP_SIZEX + GroupThreadId.x;
float2 ScreenUV = float2((DispatchThreadId.xy * DOWNSAMPLE_FACTOR + ScissorRectMinAndSize.xy + .5f) * View.ViewSizeAndSceneTexelSize.zw);
float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float SceneDepth = CalcSceneDepth(ScreenUV);
float4 HomogeneousWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld);
float3 OpaqueWorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w;
// Distance for directional lights to trace
float TraceDistance = 10000;
//@todo - take advantage of CSM covering near pixels
//@todo - handle invalid tile depth ranges due to MinDepth
float MinDepth = 0;
uint NumIntersectingObjects = NumObjects;
bool bTileShouldComputeShadowing = SceneDepth > MinDepth;
#define USE_CULLING 1
#if USE_CULLING
#if SCATTER_TILE_CULLING
// Transform into shadow space
float4 HomogeneousShadowPosition = mul(float4(OpaqueWorldPosition, 1), WorldToShadow);
float2 NormalizedShadowPosition = HomogeneousShadowPosition.xy * .5f + .5f;
NormalizedShadowPosition.y = 1 - NormalizedShadowPosition.y;
// Quantize the shadow position to get our tile position
uint2 TilePosition = (uint2)(NormalizedShadowPosition * TileListGroupSize);
// Fetch the tile head information
uint2 TileHead = GetTileHead(TilePosition);
NumIntersectingObjects = TileHead.y;
#else
// Initialize per-tile variables
if (ThreadIndex == 0)
{
IntegerTileMinZ = 0x7F7FFFFF;
IntegerTileMaxZ = 0;
IntegerTileMinZ2 = 0x7F7FFFFF;
IntegerTileMaxZ2 = 0;
TileNumObjects0 = 0;
TileNumObjects1 = 0;
}
GroupMemoryBarrierWithGroupSync();
if (SceneDepth > MinDepth)
{
// Use shared memory atomics to build the depth bounds for this tile
// Each thread is assigned to a pixel at this point
//@todo - move depth range computation to a central point where it can be reused by all the frame's tiled deferred passes!
InterlockedMin(IntegerTileMinZ, asuint(SceneDepth));
InterlockedMax(IntegerTileMaxZ, asuint(SceneDepth));
}
GroupMemoryBarrierWithGroupSync();
float MinTileZ = asfloat(IntegerTileMinZ);
float MaxTileZ = asfloat(IntegerTileMaxZ);
float HalfZ = .5f * (MinTileZ + MaxTileZ);
// Compute a second min and max Z, clipped by HalfZ, so that we get two depth bounds per tile
// This results in more conservative tile depth bounds and fewer intersections
if (SceneDepth >= HalfZ && SceneDepth > MinDepth)
{
InterlockedMin(IntegerTileMinZ2, asuint(SceneDepth));
}
if (SceneDepth <= HalfZ && SceneDepth > MinDepth)
{
InterlockedMax(IntegerTileMaxZ2, asuint(SceneDepth));
}
GroupMemoryBarrierWithGroupSync();
float MinTileZ2 = asfloat(IntegerTileMinZ2);
float MaxTileZ2 = asfloat(IntegerTileMaxZ2);
float3 ViewTileMin;
float3 ViewTileMax;
float3 ViewTileMin2;
float3 ViewTileMax2;
float ExpandRadius = 0;
float2 TanViewFOV = float2(1 / View.ViewToClip[0][0], 1 / View.ViewToClip[1][1]);
// tan(FOV) = HalfUnitPlaneWidth / 1, so TanViewFOV * 2 is the size of the whole unit view plane
// We are operating on a subset of that defined by ScissorRectMinAndSize
float2 TileSize = TanViewFOV * 2 * ScissorRectMinAndSize.zw / ((float2)View.ViewSizeAndSceneTexelSize.xy * NumGroups);
float2 UnitPlaneMin = -TanViewFOV + TanViewFOV * 2 * ScissorRectMinAndSize.xy / (float2)View.ViewSizeAndSceneTexelSize.xy;
float2 UnitPlaneTileMin = (GroupId.xy * TileSize + UnitPlaneMin) * float2(1, -1);
float2 UnitPlaneTileMax = ((GroupId.xy + 1) * TileSize + UnitPlaneMin) * float2(1, -1);
ViewTileMin.xy = min(MinTileZ * UnitPlaneTileMin, MaxTileZ2 * UnitPlaneTileMin) - ExpandRadius;
ViewTileMax.xy = max(MinTileZ * UnitPlaneTileMax, MaxTileZ2 * UnitPlaneTileMax) + ExpandRadius;
ViewTileMin.z = MinTileZ - ExpandRadius;
ViewTileMax.z = MaxTileZ2 + ExpandRadius;
ViewTileMin2.xy = min(MinTileZ2 * UnitPlaneTileMin, MaxTileZ * UnitPlaneTileMin) - ExpandRadius;
ViewTileMax2.xy = max(MinTileZ2 * UnitPlaneTileMax, MaxTileZ * UnitPlaneTileMax) + ExpandRadius;
ViewTileMin2.z = MinTileZ2 - ExpandRadius;
ViewTileMax2.z = MaxTileZ + ExpandRadius;
float3 ViewGroup0Center = (ViewTileMax + ViewTileMin) / 2;
float3 WorldGroup0Center = mul(float4(ViewGroup0Center, 1), View.ViewToTranslatedWorld).xyz - View.PreViewTranslation;
float Group0BoundingRadius = length(ViewGroup0Center - ViewTileMax);
float3 ViewGroup1Center = (ViewTileMax2 + ViewTileMin2) / 2;
float3 WorldGroup1Center = mul(float4(ViewGroup1Center, 1), View.ViewToTranslatedWorld).xyz - View.PreViewTranslation;
float Group1BoundingRadius = length(ViewGroup1Center - ViewTileMax2);
bool bCullTile = true;
#if POINT_LIGHT
float3 LightVector0 = LightPositionAndInvRadius.xyz - WorldGroup0Center;
float LightVector0Length = length(LightVector0);
float3 LightVector1 = LightPositionAndInvRadius.xyz - WorldGroup1Center;
float LightVector1Length = length(LightVector1);
float3 LightDirection0 = LightVector0 / LightVector0Length;
float3 LightDirection1 = LightVector1 / LightVector1Length;;
float RayLength0 = LightVector0Length;
float RayLength1 = LightVector1Length;
// Don't operate on tiles completely outside of the light's influence
bCullTile = bTileShouldComputeShadowing = LightVector0Length < 1.0f / LightPositionAndInvRadius.w + Group0BoundingRadius
|| LightVector1Length < 1.0f / LightPositionAndInvRadius.w + Group1BoundingRadius;
#else
float3 LightDirection0 = LightDirection;
float3 LightDirection1 = LightDirection;
float RayLength0 = TraceDistance;
float RayLength1 = TraceDistance;
#endif
BRANCH
if (bCullTile)
{
// Compute per-tile lists of affecting objects through bounds culling
// Each thread now operates on a sample instead of a pixel
LOOP
for (uint ObjectIndex = ThreadIndex; ObjectIndex < NumObjects; ObjectIndex += THREADGROUP_TOTALSIZE)
{
float4 SphereCenterAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
BRANCH
if (RayHitSphere(WorldGroup0Center, LightDirection0, RayLength0, SphereCenterAndRadius.xyz, SphereCenterAndRadius.w + Group0BoundingRadius))
{
uint ListIndex;
InterlockedAdd(TileNumObjects0, 1U, ListIndex);
// Don't overwrite on overflow
ListIndex = min(ListIndex, MAX_INTERSECTING_OBJECTS - 1);
IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS * 0 + ListIndex] = ObjectIndex;
}
BRANCH
if (RayHitSphere(WorldGroup1Center, LightDirection1, RayLength1, SphereCenterAndRadius.xyz, SphereCenterAndRadius.w + Group1BoundingRadius))
{
uint ListIndex;
InterlockedAdd(TileNumObjects1, 1U, ListIndex);
// Don't write out of bounds on overflow
ListIndex = min(ListIndex, MAX_INTERSECTING_OBJECTS - 1);
IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS * 1 + ListIndex] = ObjectIndex;
}
}
}
GroupMemoryBarrierWithGroupSync();
uint GroupIndex = SceneDepth > MaxTileZ2 ? 1 : 0;
NumIntersectingObjects = GroupIndex == 0 ? TileNumObjects0 : TileNumObjects1;
#endif
#endif // USE_CULLING
float Result = 0;
#define COMPUTE_SHADOWING 1
#if COMPUTE_SHADOWING
BRANCH
if (bTileShouldComputeShadowing)
{
FGBufferData GBufferData = GetGBufferData(ScreenUV);
float3 WorldNormal = GBufferData.WorldNormal;
#if !POINT_LIGHT
BRANCH
if (dot(WorldNormal, LightDirection) > TanLightAngleAndNormalThreshold.y)
#endif
{
// World space offset along the start of the ray to avoid incorrect self-shadowing
float RayStartOffset = 2;
// Keeps result from going all the way sharp
float MinSphereRadius = .4f;
// Maintain reasonable culling bounds
float MaxSphereRadius = 100;
#if POINT_LIGHT
float3 LightVector = LightPositionAndInvRadius.xyz - OpaqueWorldPosition;
float LightVectorLength = length(LightVector);
float3 WorldRayStart = OpaqueWorldPosition + LightVector / LightVectorLength * RayStartOffset;
float3 WorldRayEnd = LightPositionAndInvRadius.xyz;
float MaxRayTime = LightVectorLength;
float MaxAngle = tan(10 * PI / 180.0f);
// Comparing tangents instead of angles, but tangent is always increasing in this range
float TanLightAngle = min(LightSourceRadius / LightVectorLength, MaxAngle);
#else
float3 WorldRayStart = OpaqueWorldPosition + LightDirection * RayStartOffset;
float3 WorldRayEnd = OpaqueWorldPosition + LightDirection * TraceDistance;
float MaxRayTime = TraceDistance;
float TanLightAngle = TanLightAngleAndNormalThreshold.x;
#endif
float MinRayTime = MaxRayTime;
float MinConeVisibility = 1;
LOOP
for (uint ListObjectIndex = 0; ListObjectIndex < NumIntersectingObjects && MinRayTime >= MaxRayTime; ListObjectIndex++)
{
#if USE_CULLING
#if SCATTER_TILE_CULLING
uint ObjectIndex = TileArrayData.Load(ListObjectIndex * TileListGroupSize.x * TileListGroupSize.y + TileHead.x);
#else
uint ObjectIndex = IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS * GroupIndex + ListObjectIndex];
#endif
#else
uint ObjectIndex = ListObjectIndex;
#endif
float3 LocalPositionExtent = LoadObjectLocalPositionExtent(ObjectIndex);
float4x4 WorldToVolume = LoadObjectWorldToVolume(ObjectIndex);
float4 UVScaleAndVolumeScale = LoadObjectUVScale(ObjectIndex);
float3 UVAdd = LoadObjectUVAdd(ObjectIndex);
float3 VolumeRayStart = mul(float4(WorldRayStart, 1), WorldToVolume).xyz;
float3 VolumeRayEnd = mul(float4(WorldRayEnd, 1), WorldToVolume).xyz;
float3 VolumeRayDirection = VolumeRayEnd - VolumeRayStart;
float VolumeRayLength = length(VolumeRayDirection);
VolumeRayDirection /= VolumeRayLength;
float VolumeMinSphereRadius = MinSphereRadius / UVScaleAndVolumeScale.w;
float VolumeMaxSphereRadius = MaxSphereRadius / UVScaleAndVolumeScale.w;
float4 SphereCenterAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
float ObjectCenterDistanceAlongRay = max(dot(SphereCenterAndRadius.xyz - WorldRayStart, WorldRayEnd - WorldRayStart), 0);
// Expand the intersection box by the radius of the cone at the distance of the object along the cone
float LocalConeRadiusAtObject = min(TanLightAngle * ObjectCenterDistanceAlongRay / UVScaleAndVolumeScale.w, VolumeMaxSphereRadius);
float2 IntersectionTimes = LineBoxIntersect(VolumeRayStart, VolumeRayEnd, -LocalPositionExtent - LocalConeRadiusAtObject, LocalPositionExtent + LocalConeRadiusAtObject);
BRANCH
if (IntersectionTimes.x < IntersectionTimes.y && IntersectionTimes.x < 1)
{
float SampleRayTime = IntersectionTimes.x * VolumeRayLength;
uint MaxSteps = 64;
float MinStepSize = 1.0f / (4 * MaxSteps);
float MinDistance = 1000000;
float3 IntersectionPosition = float3(0, 0, 0);
uint StepIndex = 0;
LOOP
for (; StepIndex < MaxSteps; StepIndex++)
{
float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
float3 ClampedSamplePosition = clamp(SampleVolumePosition, -LocalPositionExtent, LocalPositionExtent);
float DistanceToClamped = length(ClampedSamplePosition - SampleVolumePosition);
float3 VolumeUV = DistanceFieldVolumePositionToUV(ClampedSamplePosition, UVScaleAndVolumeScale.xyz, UVAdd);
float DistanceField = Texture3DSampleLevel(DistanceFieldTexture, DistanceFieldSampler, VolumeUV, 0).x + DistanceToClamped;
MinDistance = min(MinDistance, DistanceField);
float SphereRadius = clamp(TanLightAngle * SampleRayTime, VolumeMinSphereRadius, VolumeMaxSphereRadius);
MinConeVisibility = min(MinConeVisibility, saturate(DistanceField / SphereRadius));
IntersectionPosition = SampleVolumePosition;
float StepDistance = max(DistanceField, MinStepSize);
SampleRayTime += StepDistance;
// Terminate the trace if we reached a negative area or went past the end of the ray
if (DistanceField < 0
|| SampleRayTime > IntersectionTimes.y * VolumeRayLength)
{
break;
}
}
if (MinDistance < 0 || StepIndex == MaxSteps)
{
MinConeVisibility = 0;
MinRayTime = min(MinRayTime, SampleRayTime * UVScaleAndVolumeScale.w);
}
}
}
Result = MinConeVisibility;
}
}
#else
//Result = .0001f * NumIntersectingObjects;
Result = NumIntersectingObjects / 30.0f;
#endif
RWShadowFactors[DispatchThreadId.xy] = float2(Result, SceneDepth);
}
Texture2D ShadowFactorsTexture;
SamplerState ShadowFactorsSampler;
void DistanceFieldShadowingUpsamplePS(
in float4 UVAndScreenPos : TEXCOORD0,
in float4 SVPos : SV_POSITION,
out float4 OutColor : SV_Target0)
{
// Distance field shadowing was computed at 0,0 regardless of viewrect min
float2 DistanceFieldUVs = UVAndScreenPos.xy - ScissorRectMinAndSize.xy * View.ViewSizeAndSceneTexelSize.zw;
#define BILATERAL_UPSAMPLE 1
#if BILATERAL_UPSAMPLE
float2 LowResBufferSize = floor(View.RenderTargetSize / DOWNSAMPLE_FACTOR);
float2 LowResTexelSize = 1.0f / LowResBufferSize;
float2 Corner00UV = floor(DistanceFieldUVs * LowResBufferSize - .5f) / LowResBufferSize + .5f * LowResTexelSize;
float2 BilinearWeights = (DistanceFieldUVs - Corner00UV) * LowResBufferSize;
float2 TextureValues00 = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, Corner00UV, 0).xy;
float2 TextureValues10 = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, Corner00UV + float2(LowResTexelSize.x, 0), 0).xy;
float2 TextureValues01 = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, Corner00UV + float2(0, LowResTexelSize.y), 0).xy;
float2 TextureValues11 = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, Corner00UV + LowResTexelSize, 0).xy;
float4 CornerWeights = float4(
(1 - BilinearWeights.y) * (1 - BilinearWeights.x),
(1 - BilinearWeights.y) * BilinearWeights.x,
BilinearWeights.y * (1 - BilinearWeights.x),
BilinearWeights.y * BilinearWeights.x);
float Epsilon = .0001f;
float4 CornerDepths = abs(float4(TextureValues00.y, TextureValues10.y, TextureValues01.y, TextureValues11.y));
float SceneDepth = CalcSceneDepth(UVAndScreenPos.xy);
float4 DepthWeights = 1.0f / (abs(CornerDepths - SceneDepth.xxxx) + Epsilon);
float4 FinalWeights = CornerWeights * DepthWeights;
float InterpolatedResult =
(FinalWeights.x * TextureValues00.x
+ FinalWeights.y * TextureValues10.x
+ FinalWeights.z * TextureValues01.x
+ FinalWeights.w * TextureValues11.x)
/ dot(FinalWeights, 1);
float Output = InterpolatedResult;
#else
float Output = Texture2DSampleLevel(ShadowFactorsTexture, ShadowFactorsSampler, DistanceFieldUVs, 0).x;
#endif
OutColor = EncodeLightAttenuation(half4(Output, Output, Output, Output));
}