524 lines
19 KiB
HLSL
524 lines
19 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VirtualShadowMapProjectionSpot.ush:
|
|
=============================================================================*/
|
|
#pragma once
|
|
|
|
#include "../DeferredShadingCommon.ush"
|
|
#include "../SceneTexturesCommon.ush"
|
|
#include "../LightShaderParameters.ush"
|
|
#include "../PathTracing/Utilities/PathTracingRandomSequence.ush"
|
|
#include "VirtualShadowMapPageAccessCommon.ush"
|
|
#include "VirtualShadowMapProjectionCommon.ush"
|
|
#include "VirtualShadowMapSMRTCommon.ush"
|
|
|
|
float2 ComputeDepthSlopeLocalUV(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle,
|
|
float3 ShadowTranslatedWorldPosition,
|
|
float3 WorldNormal)
|
|
{
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapHandle);
|
|
|
|
float4 NormalPlaneTranslatedWorld = float4(WorldNormal, -dot(WorldNormal, ShadowTranslatedWorldPosition));
|
|
float4 NormalPlaneUV = mul(NormalPlaneTranslatedWorld, ProjectionData.TranslatedWorldToShadowUVNormalMatrix);
|
|
float2 DepthSlopeUV = -NormalPlaneUV.xy / NormalPlaneUV.z;
|
|
return DepthSlopeUV;
|
|
}
|
|
|
|
float ComputeOptimalSlopeBiasLocal(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle,
|
|
float3 ShadowTranslatedWorldPosition,
|
|
float2 DepthSlopeUV,
|
|
bool bClamp = true)
|
|
{
|
|
// NOTE: Better to reload the necessary data here based on VSM ID than keep it alive in registers!
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapHandle);
|
|
|
|
float4 ShadowUVz = mul(float4(ShadowTranslatedWorldPosition, 1.0f), ProjectionData.TranslatedWorldToShadowUVMatrix);
|
|
ShadowUVz.xyz /= ShadowUVz.w;
|
|
FShadowPageTranslationResult Page = ShadowVirtualToPhysicalUV(ProjectionData.VirtualShadowMapHandle, ShadowUVz.xy, ProjectionData.MinMipLevel);
|
|
|
|
float MipLevelDim = float(CalcLevelDimsTexels(Page.LODOffset));
|
|
float2 TexelCenter = float2(Page.VirtualTexelAddress) + 0.5f;
|
|
float2 TexelCenterOffset = TexelCenter - Page.VirtualTexelAddressFloat;
|
|
float2 TexelCenterOffsetUV = TexelCenterOffset / MipLevelDim;
|
|
float OptimalSlopeBias = 2.0f * max(0.0f, dot(DepthSlopeUV, TexelCenterOffsetUV));
|
|
|
|
return OptimalSlopeBias;
|
|
}
|
|
|
|
|
|
struct FSMRTSingleRayState
|
|
{
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle;
|
|
float3 RayStartUVz;
|
|
float3 RayStepUVz;
|
|
float ExtrapolateSlope;
|
|
uint MinMipLevel;
|
|
// Debug output
|
|
int MipLevel;
|
|
uint2 VirtualTexelAddress;
|
|
uint2 PhysicalTexelAddress;
|
|
};
|
|
|
|
FSMRTSingleRayState SMRTSingleRayInitialize(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle,
|
|
// Shadow PreViewTranslation already added in
|
|
float3 RayStartShadowTranslatedWorld,
|
|
float3 RayEndShadowTranslatedWorld,
|
|
float ExtrapolateSlope,
|
|
float DepthBias,
|
|
uint MipLevel,
|
|
bool bClampUVs = true)
|
|
{
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapHandle);
|
|
|
|
float4 RayStartUVz = mul(float4(RayStartShadowTranslatedWorld, 1), ProjectionData.TranslatedWorldToShadowUVMatrix).xyzw;
|
|
float4 RayEndUVz = mul(float4(RayEndShadowTranslatedWorld , 1), ProjectionData.TranslatedWorldToShadowUVMatrix).xyzw;
|
|
|
|
// NOTE: We assume by construction that ray ends are not behind the light near plane as the warping
|
|
// due to SMRT ray tests gets severe long before the rays would otherwise clip at that plane.
|
|
// If clipping is ever necessary, it must be done to the ray endpoints in world space so that lights that
|
|
// need to walk multiple faces/clipmap levels in lock step can do it consistently.
|
|
|
|
RayStartUVz.xyz = RayStartUVz.xyz / RayStartUVz.w;
|
|
RayEndUVz.xyz = RayEndUVz.xyz / RayEndUVz.w;
|
|
float3 RayStepUVz = RayEndUVz.xyz - RayStartUVz.xyz;
|
|
|
|
// Offsets can move it slightly off the edge of spotlight projection. Clamp to edge.
|
|
// Note we do *not* want to do this if we are tracing dual face cubemap rays!
|
|
if (bClampUVs)
|
|
{
|
|
RayStartUVz.xy = saturate(RayStartUVz.xy);
|
|
}
|
|
RayStartUVz.z += DepthBias;
|
|
|
|
FSMRTSingleRayState Result;
|
|
Result.VirtualShadowMapHandle = VirtualShadowMapHandle;
|
|
Result.RayStartUVz = RayStartUVz.xyz;
|
|
Result.RayStepUVz = RayStepUVz.xyz;
|
|
Result.ExtrapolateSlope = ExtrapolateSlope * RayStepUVz.z;
|
|
Result.MinMipLevel = max(MipLevel, ProjectionData.MinMipLevel);
|
|
Result.MipLevel = 0;
|
|
Result.VirtualTexelAddress = uint2(0xFFFFFFFF, 0xFFFFFFFF);
|
|
Result.PhysicalTexelAddress = uint2(0xFFFFFFFF, 0xFFFFFFFF);
|
|
return Result;
|
|
}
|
|
|
|
FSMRTSample SMRTFindSample(inout FSMRTSingleRayState RayState, float SampleTime)
|
|
{
|
|
float3 UVz = RayState.RayStartUVz.xyz + RayState.RayStepUVz.xyz * SampleTime;
|
|
|
|
FSMRTSample Sample = InitSMRTSample();
|
|
Sample.bValid = false;
|
|
Sample.ReferenceDepth = UVz.z;
|
|
Sample.ExtrapolateSlope = RayState.ExtrapolateSlope;
|
|
|
|
if (all(UVz.xy == saturate(UVz.xy)))
|
|
{
|
|
FVirtualShadowMapSample SmSample = SampleVirtualShadowMap(RayState.VirtualShadowMapHandle, UVz.xy, RayState.MinMipLevel);
|
|
if (SmSample.bValid)
|
|
{
|
|
Sample.bValid = true;
|
|
Sample.SampleDepth = SmSample.Depth;
|
|
|
|
// Debug output
|
|
RayState.MipLevel = SmSample.MipLevel;
|
|
RayState.PhysicalTexelAddress = SmSample.PhysicalTexelAddress;
|
|
RayState.VirtualTexelAddress = SmSample.VirtualTexelAddress;
|
|
}
|
|
}
|
|
|
|
return Sample;
|
|
}
|
|
|
|
// Instantiate SMRTRayCast for FSMRTSingleRayState
|
|
#define SMRT_TEMPLATE_RAY_STRUCT FSMRTSingleRayState
|
|
#include "VirtualShadowMapSMRTTemplate.ush"
|
|
#undef SMRT_TEMPLATE_RAY_STRUCT
|
|
|
|
|
|
struct FSMRTTwoCubeFaceRayState
|
|
{
|
|
FSMRTSingleRayState Face0;
|
|
FSMRTSingleRayState Face1;
|
|
bool bSampleInFace1;
|
|
};
|
|
|
|
FSMRTTwoCubeFaceRayState SMRTTwoCubeFaceRayInitialize(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle0,
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle1,
|
|
float3 RayStartShadowTranslatedWorld,
|
|
float3 RayEndShadowTranslatedWorld,
|
|
float ExtrapolateSlope,
|
|
float DepthBias,
|
|
uint MipLevel)
|
|
{
|
|
FSMRTTwoCubeFaceRayState Result;
|
|
Result.Face0 = SMRTSingleRayInitialize(VirtualShadowMapHandle0, RayStartShadowTranslatedWorld, RayEndShadowTranslatedWorld, ExtrapolateSlope, DepthBias, MipLevel, false);
|
|
Result.Face1 = SMRTSingleRayInitialize(VirtualShadowMapHandle1, RayStartShadowTranslatedWorld, RayEndShadowTranslatedWorld, ExtrapolateSlope, DepthBias, MipLevel, false);
|
|
Result.bSampleInFace1 = true;
|
|
return Result;
|
|
}
|
|
|
|
FSMRTSample SMRTFindSample(inout FSMRTTwoCubeFaceRayState RayState, float SampleTime)
|
|
{
|
|
// NOTE: Traces from END to START, so we start in face1
|
|
FSMRTSample Sample = InitSMRTSample();
|
|
if (RayState.bSampleInFace1)
|
|
{
|
|
Sample = SMRTFindSample(RayState.Face1, SampleTime);
|
|
if (!Sample.bValid)
|
|
{
|
|
Sample = SMRTFindSample(RayState.Face0, SampleTime);
|
|
Sample.bResetExtrapolation = true;
|
|
RayState.bSampleInFace1 = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Sample = SMRTFindSample(RayState.Face0, SampleTime);
|
|
}
|
|
return Sample;
|
|
}
|
|
|
|
// Instantiate SMRTRayCast for FSMRTTwoCubeFaceRayState
|
|
#define SMRT_TEMPLATE_RAY_STRUCT FSMRTTwoCubeFaceRayState
|
|
#include "VirtualShadowMapSMRTTemplate.ush"
|
|
#undef SMRT_TEMPLATE_RAY_STRUCT
|
|
|
|
|
|
FSMRTResult ShadowRayCastSpotLight(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle,
|
|
float3 RayStartShadowTranslatedWorld,
|
|
float3 RayEndShadowTranslatedWorld,
|
|
float DepthBias,
|
|
uint MipLevel,
|
|
int NumSteps,
|
|
float StepOffset,
|
|
float ExtrapolateSlope,
|
|
// To get debug data out
|
|
inout FSMRTSingleRayState OutRayState)
|
|
{
|
|
OutRayState = SMRTSingleRayInitialize(VirtualShadowMapHandle, RayStartShadowTranslatedWorld, RayEndShadowTranslatedWorld, ExtrapolateSlope, DepthBias, MipLevel);
|
|
return SMRTRayCast(OutRayState, NumSteps, StepOffset);
|
|
}
|
|
|
|
FSMRTResult ShadowRayCastPointLight(
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle,
|
|
float3 RayStartShadowTranslatedWorld,
|
|
float3 RayEndShadowTranslatedWorld,
|
|
float DepthBias,
|
|
uint MipLevel,
|
|
int NumSteps,
|
|
float StepOffset,
|
|
float ExtrapolateSlope,
|
|
// To get debug data out
|
|
inout FSMRTSingleRayState OutRayState)
|
|
{
|
|
FVirtualShadowMapHandle RayStartFaceHandle = VirtualShadowMapHandle.MakeOffset(VirtualShadowMapGetCubeFace(RayStartShadowTranslatedWorld));
|
|
FVirtualShadowMapHandle RayEndFaceHandle = VirtualShadowMapHandle.MakeOffset(VirtualShadowMapGetCubeFace(RayEndShadowTranslatedWorld));
|
|
|
|
FSMRTResult Result;
|
|
if (WaveActiveAnyTrue(RayStartFaceHandle.Id != RayEndFaceHandle.Id))
|
|
{
|
|
FSMRTTwoCubeFaceRayState RayState = SMRTTwoCubeFaceRayInitialize(RayStartFaceHandle, RayEndFaceHandle,
|
|
RayStartShadowTranslatedWorld, RayEndShadowTranslatedWorld, ExtrapolateSlope, DepthBias, MipLevel);
|
|
Result = SMRTRayCast(RayState, NumSteps, StepOffset);
|
|
|
|
if (RayState.bSampleInFace1)
|
|
{
|
|
OutRayState = RayState.Face1;
|
|
}
|
|
else
|
|
{
|
|
OutRayState = RayState.Face0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fast path: ray stays on a single cube map face (effectively a spot light)
|
|
OutRayState = SMRTSingleRayInitialize(RayEndFaceHandle, RayStartShadowTranslatedWorld, RayEndShadowTranslatedWorld, ExtrapolateSlope, DepthBias, MipLevel);
|
|
Result = SMRTRayCast(OutRayState, NumSteps, StepOffset);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
// Normal should be normalized
|
|
bool IsBackfaceToLocalLight(
|
|
float3 ToLight,
|
|
float3 Normal,
|
|
float LightSourceRadius)
|
|
{
|
|
float DistSqr = dot(ToLight, ToLight);
|
|
float Falloff = rcp(DistSqr + 1);
|
|
float InvDist = rsqrt(DistSqr);
|
|
float SinAlphaSqr = saturate(Pow2(LightSourceRadius) * Falloff);
|
|
float SinAlpha = sqrt(SinAlphaSqr);
|
|
bool bBackface = dot(Normal, ToLight * InvDist) < -SinAlpha;
|
|
return bBackface;
|
|
}
|
|
|
|
// Shadow resolution scale that roughly matches the LOD function in VSM page marking
|
|
float ComputeShadowResolutionScale(FVirtualShadowMapProjectionShaderData ProjectionData, float SceneDepth)
|
|
{
|
|
const float2 RadiusXY = 1.0f / (View.ViewSizeAndInvSize.xy * View.ViewToClip._11_22);
|
|
const float RadiusScreen = min(RadiusXY.x, RadiusXY.y);
|
|
const float DepthScale = SceneDepth * View.ViewToClip._34 + View.ViewToClip._44;
|
|
const float ScreenPixelRadiusWorld = DepthScale * RadiusScreen;
|
|
|
|
// Clamp the minimum here since our shadows sadly don't actually have infinite resolution near the camera...
|
|
float ShadowResolutionScale = max(0.1f, ScreenPixelRadiusWorld * exp2(ProjectionData.ResolutionLodBias));
|
|
|
|
return ShadowResolutionScale;
|
|
}
|
|
|
|
float VirtualShadowMapGetClampedRayDistanceScaleLocal(float CosTheta)
|
|
{
|
|
// The further afield our rays go the poorer our approximation is as the "bend" due to our testing
|
|
// against a shadow map instead of along the ray increases. Thus we avoid going all the way to the light
|
|
// where the projection becomes extreme.
|
|
|
|
// This clamping function is a bit arbitrary but is fairly smooth and avoids the worst artifacts in practice
|
|
float SinTheta = sqrt(1.0f - CosTheta * CosTheta);
|
|
return 0.75f * saturate(1.5f / (CosTheta + VirtualShadowMap.SMRTCotMaxRayAngleFromLight * SinTheta));
|
|
}
|
|
|
|
FVirtualShadowMapSampleResult TraceLocalLight(
|
|
int VirtualShadowMapId,
|
|
FLightShaderParameters Light,
|
|
uint2 PixelPos,
|
|
const float SceneDepth,
|
|
float3 TranslatedWorldPosition,
|
|
float RayStartOffset,
|
|
const float Noise,
|
|
float3 WorldNormal,
|
|
const FSMRTTraceSettings Settings = GetSMRTTraceSettingsLocal())
|
|
{
|
|
FVirtualShadowMapHandle VirtualShadowMapHandle = FVirtualShadowMapHandle::MakeFromId(VirtualShadowMapId);
|
|
const uint LightType = Light.SpotAngles.x > -2.0f ? LIGHT_TYPE_SPOT : LIGHT_TYPE_POINT;
|
|
|
|
const float3 ToLight = Light.TranslatedWorldPosition - TranslatedWorldPosition;
|
|
const float3 ConeAxis = normalize(ToLight);
|
|
const float DistToLight = length(ToLight);
|
|
const float ConeSin = Light.SourceRadius / DistToLight;
|
|
|
|
FVirtualShadowMapSampleResult Result = InitVirtualShadowMapSampleResult();
|
|
Result.bValid = true;
|
|
Result.ShadowFactor = 0.0f;
|
|
|
|
FVirtualShadowMapHandle OffsetVirtualShadowMapHandle = VirtualShadowMapHandle;
|
|
if (LightType == LIGHT_TYPE_POINT)
|
|
{
|
|
OffsetVirtualShadowMapHandle = OffsetVirtualShadowMapHandle.MakeOffset(VirtualShadowMapGetCubeFace(-ToLight));
|
|
}
|
|
|
|
// Move from PrimaryView translated world to Shadow translated world
|
|
// NOTE: Cube map faces all share the same PreViewTranslation and projection depth scale
|
|
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(OffsetVirtualShadowMapHandle);
|
|
const float3 PrimaryToShadowTranslation = DFFastLocalSubtractDemote(ProjectionData.PreViewTranslation, PrimaryView.PreViewTranslation);
|
|
const float3 ShadowTranslatedWorld = TranslatedWorldPosition + PrimaryToShadowTranslation;
|
|
|
|
const float2 DepthSlopeUV = ComputeDepthSlopeLocalUV(OffsetVirtualShadowMapHandle, ShadowTranslatedWorld, WorldNormal);
|
|
|
|
const float ShadowResolutionScale = ComputeShadowResolutionScale(ProjectionData, SceneDepth);
|
|
// Rough scaling from world units -> post projection depth.
|
|
// We use a radially symmetric mapping here instead of the true function to minimize issues crossing cubemap faces.
|
|
const float ShadowDepthFunctionScale = abs(ProjectionData.ShadowViewToClipMatrix._43 / length2(ToLight));
|
|
|
|
const float DitherScale = (Settings.TexelDitherScale * ProjectionData.TexelDitherScale) * ShadowResolutionScale;
|
|
const float MaxDitherSlope = VirtualShadowMap.SMRTMaxSlopeBiasLocal * DitherScale;
|
|
const float MaxDepthBias = VirtualShadowMap.SMRTMaxSlopeBiasLocal * ShadowResolutionScale * ShadowDepthFunctionScale;
|
|
|
|
// Moving the ray origin means we need to attempt to account for local receiver geometry to avoid
|
|
// incorrect self-shadowing. To this end we move the point on a plane aligned with the shading normal.
|
|
// It would be much better to use geometric (faceted) normals - as with optimal bias - but we do not
|
|
// have access to those. Shading normals that stray too far from the real geometry will cause artifacts.
|
|
const float3x3 ConeTangentToWorldMatrix = GetTangentBasis(ConeAxis);
|
|
const float3 NormalPlaneTangent = mul(ConeTangentToWorldMatrix, WorldNormal);
|
|
const float2 DepthSlopeTangent = -NormalPlaneTangent.xy / NormalPlaneTangent.z;
|
|
|
|
// If source angle = 0, we don't need multiple samples on the same ray
|
|
uint SamplesPerRay = Settings.SamplesPerRay;
|
|
if (ConeSin == 0.0f)
|
|
{
|
|
SamplesPerRay = 0;
|
|
}
|
|
|
|
// When using receiver mask, we also must compute the base mip level to use
|
|
// We cannot use greedy mip selection in this case since the higher res mips may not have the rendered
|
|
// geometry that covers the requested mask at this receiver. We must use a similar-or-lower mip than
|
|
// the one that we marked.
|
|
// TODO: Any significant mip changes in GetMipLevelLocal should probably affect the above
|
|
// bias/texel dither math as well, which currently assumes a roughly proportional pixel:texel mapping
|
|
uint MipLevel = VirtualShadowMapGetMipLevelLocalFromReceiver(ProjectionData, ShadowTranslatedWorld, SceneDepth);
|
|
|
|
uint i = 0;
|
|
uint RayMissCount = 0;
|
|
float StepOffset = Noise;
|
|
float OccluderDistanceSum = 0.0f;
|
|
float MaxOccluderDistance = -1.0f;
|
|
const uint MaxRayCount = Settings.RayCount;
|
|
for( ; i < MaxRayCount; i++ )
|
|
{
|
|
float4 RandSample = VirtualShadowMapGetRandomSample(PixelPos, View.StateFrameIndex, i, MaxRayCount);
|
|
|
|
float2 LightUV = UniformSampleDiskConcentricApprox( RandSample.xy ).xy;
|
|
LightUV *= ConeSin;
|
|
float SinTheta2 = dot(LightUV, LightUV);
|
|
float CosTheta = sqrt(1.0f - SinTheta2);
|
|
float3 Dir = mul(float3(LightUV, CosTheta), ConeTangentToWorldMatrix);
|
|
|
|
float3 RayStartWithDither = ShadowTranslatedWorld;
|
|
|
|
if (DitherScale > 0.0f)
|
|
{
|
|
// Offset following the plane of the receiver
|
|
float2 RandomOffset = (RandSample.zw - 0.5f) * DitherScale;
|
|
float OffsetZ = min(MaxDitherSlope, 2.0f * max(0.0f, dot(DepthSlopeTangent, RandomOffset)));
|
|
float3 Offset = mul(float3(RandomOffset, OffsetZ), ConeTangentToWorldMatrix);
|
|
RayStartWithDither += Offset;
|
|
}
|
|
|
|
float ClampedEnd = DistToLight * VirtualShadowMapGetClampedRayDistanceScaleLocal(CosTheta);
|
|
float Start = min(RayStartOffset, ClampedEnd - 1e-6f);
|
|
|
|
float3 RayStart = RayStartWithDither + Dir * Start;
|
|
float3 RayEnd = RayStartWithDither + Dir * ClampedEnd;
|
|
|
|
float DepthBias = 0.0f;
|
|
if (MaxDepthBias > 0.0f)
|
|
{
|
|
// Clamp depth bias to avoid excessive degenerate slope biases causing flickering lit pixels
|
|
DepthBias = min(MaxDepthBias, ComputeOptimalSlopeBiasLocal(OffsetVirtualShadowMapHandle, RayStart, DepthSlopeUV));
|
|
}
|
|
|
|
FSMRTResult SMRTResult;
|
|
FSMRTSingleRayState RayState; // For debug output
|
|
if ( LightType == LIGHT_TYPE_SPOT )
|
|
{
|
|
SMRTResult = ShadowRayCastSpotLight(
|
|
VirtualShadowMapHandle,
|
|
RayStart,
|
|
RayEnd,
|
|
DepthBias,
|
|
MipLevel,
|
|
SamplesPerRay,
|
|
StepOffset,
|
|
Settings.ExtrapolateMaxSlope,
|
|
RayState);
|
|
}
|
|
else
|
|
{
|
|
SMRTResult = ShadowRayCastPointLight(
|
|
VirtualShadowMapHandle,
|
|
RayStart,
|
|
RayEnd,
|
|
DepthBias,
|
|
MipLevel,
|
|
SamplesPerRay,
|
|
StepOffset,
|
|
Settings.ExtrapolateMaxSlope,
|
|
RayState);
|
|
}
|
|
|
|
if (SMRTResult.bValidHit)
|
|
{
|
|
// TODO: Do we want to mess with this at all due to our offset?
|
|
float ReceiverDistance = length(RayStart);
|
|
float OccluderDistance = ComputeOccluderDistancePerspective(
|
|
// Re-fetch this here to avoid reg pressure
|
|
GetVirtualShadowMapProjectionData(RayState.VirtualShadowMapHandle).ShadowViewToClipMatrix,
|
|
SMRTResult.HitDepth,
|
|
saturate(RayState.RayStartUVz.z),
|
|
ReceiverDistance);
|
|
|
|
OccluderDistanceSum += OccluderDistance;
|
|
MaxOccluderDistance = max(MaxOccluderDistance, OccluderDistance);
|
|
}
|
|
else
|
|
{
|
|
++RayMissCount;
|
|
}
|
|
|
|
// Debug output (DCE'd if not used)
|
|
Result.ClipmapOrMipLevel = RayState.MipLevel;
|
|
Result.VirtualTexelAddress = RayState.VirtualTexelAddress;
|
|
Result.PhysicalTexelAddress = RayState.PhysicalTexelAddress;
|
|
|
|
if (MaxRayCount > 1 && Settings.AdaptiveRayCount > 0)
|
|
{
|
|
// TODO: Adapt this heuristic based on SMRTAdaptiveRayCount as well?
|
|
if( i == 0 )
|
|
{
|
|
bool bHit = SMRTResult.bValidHit;
|
|
|
|
// All lanes missed
|
|
bool bAllLanesMiss = WaveActiveAllTrue( !bHit );
|
|
if( bAllLanesMiss )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if( i >= Settings.AdaptiveRayCount)
|
|
{
|
|
// After N iterations and all have hit, assume umbra
|
|
bool bAllLanesHit = WaveActiveAllTrue( RayMissCount == 0 );
|
|
if( bAllLanesHit )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint Seed = asuint( StepOffset );
|
|
StepOffset = ( EvolveSobolSeed( Seed ) >> 8 ) * 5.96046447754e-08; // * 2^-24;
|
|
}
|
|
uint RayCount = min(i + 1, MaxRayCount); // break vs regular for loop exit
|
|
|
|
float OccluderDistance = (OccluderDistanceSum / float(max(1, RayCount - RayMissCount)));
|
|
//OccluderDistance = MaxOccluderDistance;
|
|
|
|
Result.ShadowFactor = float(RayMissCount) / float(RayCount);
|
|
Result.RayCount = RayCount;
|
|
Result.OccluderDistance = OccluderDistance;
|
|
|
|
return Result;
|
|
}
|
|
|
|
// Generate ray based on light source geometry
|
|
bool GenerateRayLocalLight(
|
|
FLightShaderParameters Light,
|
|
uint2 PixelPos,
|
|
float3 TranslatedWorldPosition,
|
|
float3 WorldNormal,
|
|
uint RayIndex,
|
|
uint RayCount,
|
|
inout float3 OutRayStart,
|
|
inout float3 OutRayEnd)
|
|
{
|
|
const float3 ToLight = Light.TranslatedWorldPosition - TranslatedWorldPosition;
|
|
const float3 ConeAxis = normalize(ToLight);
|
|
const float DistToLight = length(ToLight);
|
|
const float ConeSin = Light.SourceRadius / DistToLight;
|
|
|
|
bool bBackface = IsBackfaceToLocalLight(ToLight, WorldNormal, Light.SourceRadius);
|
|
if (!bBackface)
|
|
{
|
|
float2 E = VirtualShadowMapGetRandomSample(PixelPos, View.StateFrameIndex, RayIndex, RayCount).xy;
|
|
float2 LightUV = UniformSampleDiskConcentricApprox(E);
|
|
LightUV *= ConeSin;
|
|
float SinTheta2 = dot(LightUV, LightUV);
|
|
float CosTheta = sqrt(1 - SinTheta2);
|
|
float3 Dir = TangentToWorld(float3(LightUV, CosTheta), ConeAxis);
|
|
|
|
float ClampedLength = DistToLight * VirtualShadowMapGetClampedRayDistanceScaleLocal(CosTheta);
|
|
|
|
OutRayStart = TranslatedWorldPosition;
|
|
OutRayEnd = TranslatedWorldPosition + Dir * ClampedLength;
|
|
}
|
|
return !bBackface;
|
|
}
|