2021-09-09 20:42:29 -04:00

354 lines
16 KiB
HLSL

#ifndef HD_SHADOW_ALGORITHMS_INCLUDED
#define HD_SHADOW_ALGORITHMS_INCLUDED
// Configure which shadow algorithms to use per shadow level quality
// Since we use slope-scale bias, the constant bias is for now set as a small fixed value
#define FIXED_UNIFORM_BIAS (1.0f / 65536.0f)
// WARNINGS:
// Keep in sync with both HDShadowManager::GetDirectionalShadowAlgorithm() and GetPunctualFilterWidthInTexels() in C# as well!
#ifdef SHADOW_ULTRA_LOW
#define PUNCTUAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCF_Tent_3x3(sd.isInCachedAtlas ? _CachedShadowAtlasSize.zwxy : _ShadowAtlasSize.zwxy, posTC, tex, samp, bias)
#define DIRECTIONAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_Gather_PCF(_CascadeShadowAtlasSize.zwxy, posTC, tex, samp, bias)
#elif defined(SHADOW_LOW)
#define PUNCTUAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCF_Tent_3x3(sd.isInCachedAtlas ? _CachedShadowAtlasSize.zwxy : _ShadowAtlasSize.zwxy, posTC, tex, samp, bias)
#define DIRECTIONAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCF_Tent_5x5(_CascadeShadowAtlasSize.zwxy, posTC, tex, samp, bias)
#elif defined(SHADOW_MEDIUM)
#define PUNCTUAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCF_Tent_5x5(sd.isInCachedAtlas ? _CachedShadowAtlasSize.zwxy : _ShadowAtlasSize.zwxy, posTC, tex, samp, bias)
#define DIRECTIONAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCF_Tent_7x7(_CascadeShadowAtlasSize.zwxy, posTC, tex, samp, bias)
// Note: currently quality settings for PCSS need to be expose in UI and is control in HDLightUI.cs file IsShadowSettings
#elif defined(SHADOW_HIGH)
#define PUNCTUAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCSS(posTC, posSS, sd.shadowMapSize.xy * (sd.isInCachedAtlas ? _CachedShadowAtlasSize.zw : _ShadowAtlasSize.zw), sd.atlasOffset, sd.shadowFilterParams0.x, sd.shadowFilterParams0.w, asint(sd.shadowFilterParams0.y), asint(sd.shadowFilterParams0.z), tex, samp, s_point_clamp_sampler, bias, sd.zBufferParam, true, (sd.isInCachedAtlas ? _CachedShadowAtlasSize.xz : _ShadowAtlasSize.xz))
#define DIRECTIONAL_FILTER_ALGORITHM(sd, posSS, posTC, tex, samp, bias) SampleShadow_PCSS(posTC, posSS, sd.shadowMapSize.xy * _CascadeShadowAtlasSize.zw, sd.atlasOffset, sd.shadowFilterParams0.x, sd.shadowFilterParams0.w, asint(sd.shadowFilterParams0.y), asint(sd.shadowFilterParams0.z), tex, samp, s_point_clamp_sampler, bias, sd.zBufferParam, false, _CascadeShadowAtlasSize.xz)
#endif
#ifndef PUNCTUAL_FILTER_ALGORITHM
#error "Undefined punctual shadow filter algorithm"
#endif
#ifndef DIRECTIONAL_FILTER_ALGORITHM
#error "Undefined directional shadow filter algorithm"
#endif
float4 EvalShadow_WorldToShadow(HDShadowData sd, float3 positionWS, bool perspProj)
{
// Note: Due to high VGRP load we can't use the whole view projection matrix, instead we reconstruct it from
// rotation, position and projection vectors (projection and position are stored in SGPR)
#if 0
return mul(viewProjection, float4(positionWS, 1));
#else
if(perspProj)
{
positionWS = positionWS - sd.pos;
float3x3 view = { sd.rot0, sd.rot1, sd.rot2 };
positionWS = mul(view, positionWS);
}
else
{
float3x4 view;
view[0] = float4(sd.rot0, sd.pos.x);
view[1] = float4(sd.rot1, sd.pos.y);
view[2] = float4(sd.rot2, sd.pos.z);
positionWS = mul(view, float4(positionWS, 1.0)).xyz;
}
float4x4 proj;
proj = 0.0;
proj._m00 = sd.proj[0];
proj._m11 = sd.proj[1];
proj._m22 = sd.proj[2];
proj._m23 = sd.proj[3];
if(perspProj)
proj._m32 = -1.0;
else
proj._m33 = 1.0;
return mul(proj, float4(positionWS, 1.0));
#endif
}
// function called by spot, point and directional eval routines to calculate shadow coordinates
float3 EvalShadow_GetTexcoordsAtlas(HDShadowData sd, float2 atlasSizeRcp, float3 positionWS, out float3 posNDC, bool perspProj)
{
float4 posCS = EvalShadow_WorldToShadow(sd, positionWS, perspProj);
// Avoid (0 / 0 = NaN).
posNDC = (perspProj && posCS.w != 0) ? (posCS.xyz / posCS.w) : posCS.xyz;
// calc TCs
float3 posTC = float3(saturate(posNDC.xy * 0.5 + 0.5), posNDC.z);
posTC.xy = posTC.xy * sd.shadowMapSize.xy * atlasSizeRcp + sd.atlasOffset;
return posTC;
}
float3 EvalShadow_GetTexcoordsAtlas(HDShadowData sd, float2 atlasSizeRcp, float3 positionWS, bool perspProj)
{
float3 ndc;
return EvalShadow_GetTexcoordsAtlas(sd, atlasSizeRcp, positionWS, ndc, perspProj);
}
float2 EvalShadow_GetTexcoordsAtlas(HDShadowData sd, float2 atlasSizeRcp, float3 positionWS, out float2 closestSampleNDC, bool perspProj)
{
float4 posCS = EvalShadow_WorldToShadow(sd, positionWS, perspProj);
// Avoid (0 / 0 = NaN).
float2 posNDC = (perspProj && posCS.w != 0) ? (posCS.xy / posCS.w) : posCS.xy;
// calc TCs
float2 posTC = posNDC * 0.5 + 0.5;
closestSampleNDC = (floor(posTC * sd.shadowMapSize.xy) + 0.5) * sd.shadowMapSize.zw * 2.0 - 1.0.xx;
return posTC * sd.shadowMapSize.xy * atlasSizeRcp + sd.atlasOffset;
}
uint2 EvalShadow_GetIntTexcoordsAtlas(HDShadowData sd, float4 atlasSize, float3 positionWS, out float2 closestSampleNDC, bool perspProj)
{
float2 texCoords = EvalShadow_GetTexcoordsAtlas(sd, atlasSize.zw, positionWS, closestSampleNDC, perspProj);
return uint2(texCoords * atlasSize.xy);
}
//
// Biasing functions
//
// helper function to get the world texel size
float EvalShadow_WorldTexelSize(float worldTexelSize, float L_dist, bool perspProj)
{
return perspProj ? (worldTexelSize * L_dist) : worldTexelSize;
}
// receiver bias either using the normal to weight normal and view biases, or just light view biasing
float3 EvalShadow_NormalBias(float worldTexelSize, float normalBias, float3 normalWS)
{
float normalBiasMult = normalBias * worldTexelSize;
return normalWS * normalBiasMult;
}
//
// Point shadows
//
float EvalShadow_PunctualDepth(HDShadowData sd, Texture2D tex, SamplerComparisonState samp, float2 positionSS, float3 positionWS, float3 normalWS, float3 L, float L_dist, bool perspective)
{
float2 texelSize = sd.isInCachedAtlas ? _CachedShadowAtlasSize.zw : _ShadowAtlasSize.zw;
positionWS = positionWS + sd.cacheTranslationDelta.xyz;
/* bias the world position */
float worldTexelSize = EvalShadow_WorldTexelSize(sd.worldTexelSize, L_dist, true);
float3 normalBias = EvalShadow_NormalBias(worldTexelSize, sd.normalBias, normalWS);
positionWS += normalBias;
/* get shadowmap texcoords */
float3 posTC = EvalShadow_GetTexcoordsAtlas(sd, texelSize, positionWS, perspective);
/* sample the texture */
// We need to do the check on min/max coordinates because if the shadow spot angle is smaller than the actual cone, then we could have artifacts due to the clamp sampler.
float2 maxCoord = (sd.shadowMapSize.xy - 0.5f) * texelSize + sd.atlasOffset;
float2 minCoord = sd.atlasOffset;
return any(posTC.xy > maxCoord || posTC.xy < minCoord) ? 1.0f : PUNCTUAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS);
}
//
// Area light shadows
//
float EvalShadow_AreaDepth(HDShadowData sd, Texture2D tex, float2 positionSS, float3 positionWS, float3 normalWS, float3 L, float L_dist, bool perspective)
{
float2 texelSize = sd.isInCachedAtlas ? _CachedAreaShadowAtlasSize.zw : _AreaShadowAtlasSize.zw;
positionWS = positionWS + sd.cacheTranslationDelta.xyz;
/* get shadowmap texcoords */
float3 posTC = EvalShadow_GetTexcoordsAtlas(sd, texelSize, positionWS, perspective);
int blurPassesScale = (1 + min(4, sd.shadowFilterParams0.w) * 4.0f);// This is needed as blurring might cause some leaks. It might be overclipping, but empirically is a good value.
float2 maxCoord = (sd.shadowMapSize.xy - 0.5f * blurPassesScale) * texelSize + sd.atlasOffset;
float2 minCoord = sd.atlasOffset + texelSize * blurPassesScale;
if (any(posTC.xy > maxCoord || posTC.xy < minCoord))
{
return 1.0f;
}
else
{
float2 exponents = sd.shadowFilterParams0.xx;
float lightLeakBias = sd.shadowFilterParams0.y;
float varianceBias = sd.shadowFilterParams0.z;
return SampleShadow_EVSM_1tap(posTC, lightLeakBias, varianceBias, exponents, false, tex, s_linear_clamp_sampler);
}
}
//
// Directional shadows (cascaded shadow map)
//
int EvalShadow_GetSplitIndex(HDShadowContext shadowContext, int index, float3 positionWS, out float alpha, out int cascadeCount)
{
uint i = 0;
float relDistance = 0.0;
float3 wposDir, splitSphere;
HDDirectionalShadowData dsd = shadowContext.directionalShadowData;
// find the current cascade
for (; i < _CascadeShadowCount; i++)
{
float4 sphere = dsd.sphereCascades[i];
wposDir = -sphere.xyz + positionWS;
float distSq = dot(wposDir, wposDir);
relDistance = distSq / sphere.w;
if (relDistance > 0.0 && relDistance <= 1.0)
{
splitSphere = sphere.xyz;
wposDir /= sqrt(distSq);
break;
}
}
int shadowSplitIndex = i < _CascadeShadowCount ? i : -1;
cascadeCount = dsd.cascadeDirection.w;
float border = dsd.cascadeBorders[shadowSplitIndex];
alpha = border <= 0.0 ? 0.0 : saturate((relDistance - (1.0 - border)) / border);
// The above code will generate transitions on the whole cascade sphere boundary.
// It means that depending on the light and camera direction, sometimes the transition appears on the wrong side of the cascade
// To avoid that we attenuate the effect (lerp very sharply to 0.0) when view direction and cascade center to pixel vector face opposite directions.
// This way you only get fade out on the right side of the cascade.
float3 viewDir = GetWorldSpaceViewDir(positionWS);
float cascDot = dot(viewDir, wposDir);
// At high border sizes the sharp lerp is noticeable, hence we need to lerp how sharpenss factor
// if we are below 80% we keep the very sharp transition.
float lerpSharpness = 8.0f + 512.0f * smoothstep(1.0f, 0.7f, border);// lerp(1024.0f, 8.0f, saturate(border - 0.8) / 0.2f);
alpha = lerp(alpha, 0.0, saturate(cascDot * lerpSharpness));
return shadowSplitIndex;
}
void LoadDirectionalShadowDatas(inout HDShadowData sd, HDShadowContext shadowContext, int index)
{
sd.proj = shadowContext.shadowDatas[index].proj;
sd.pos = shadowContext.shadowDatas[index].pos;
sd.worldTexelSize = shadowContext.shadowDatas[index].worldTexelSize;
sd.atlasOffset = shadowContext.shadowDatas[index].atlasOffset;
#if defined(SHADOW_HIGH)
sd.shadowFilterParams0.x = shadowContext.shadowDatas[index].shadowFilterParams0.x;
sd.zBufferParam = shadowContext.shadowDatas[index].zBufferParam;
#endif
sd.cacheTranslationDelta = shadowContext.shadowDatas[index].cacheTranslationDelta;
}
float EvalShadow_CascadedDepth_Blend_SplitIndex(HDShadowContext shadowContext, Texture2D tex, SamplerComparisonState samp, float2 positionSS, float3 positionWS, float3 normalWS, int index, float3 L, out int shadowSplitIndex)
{
float alpha;
int cascadeCount;
float shadow = 1.0;
shadowSplitIndex = EvalShadow_GetSplitIndex(shadowContext, index, positionWS, alpha, cascadeCount);
float3 basePositionWS = positionWS;
if (shadowSplitIndex >= 0.0)
{
HDShadowData sd = shadowContext.shadowDatas[index];
LoadDirectionalShadowDatas(sd, shadowContext, index + shadowSplitIndex);
positionWS = basePositionWS + sd.cacheTranslationDelta.xyz;
/* normal based bias */
float3 orig_pos = positionWS;
float3 normalBias = EvalShadow_NormalBias(sd.worldTexelSize, sd.normalBias, normalWS);
positionWS += normalBias;
/* get shadowmap texcoords */
float3 posTC = EvalShadow_GetTexcoordsAtlas(sd, _CascadeShadowAtlasSize.zw, positionWS, false);
/* evalute the first cascade */
shadow = DIRECTIONAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS);
float shadow1 = 1.0;
shadowSplitIndex++;
if (shadowSplitIndex < cascadeCount)
{
shadow1 = shadow;
if (alpha > 0.0)
{
LoadDirectionalShadowDatas(sd, shadowContext, index + shadowSplitIndex);
float3 evaluationPosWS = basePositionWS + sd.cacheTranslationDelta.xyz + normalBias;
float3 posNDC;
posTC = EvalShadow_GetTexcoordsAtlas(sd, _CascadeShadowAtlasSize.zw, evaluationPosWS, posNDC, false);
/* sample the texture */
UNITY_BRANCH
if (all(abs(posNDC.xy) <= (1.0 - sd.shadowMapSize.zw * 0.5)))
shadow1 = DIRECTIONAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS);
}
}
shadow = lerp(shadow, shadow1, alpha);
}
return shadow;
}
float EvalShadow_CascadedDepth_Blend(HDShadowContext shadowContext, Texture2D tex, SamplerComparisonState samp, float2 positionSS, float3 positionWS, float3 normalWS, int index, float3 L)
{
int unusedSplitIndex;
return EvalShadow_CascadedDepth_Blend_SplitIndex(shadowContext, tex, samp, positionSS, positionWS, normalWS, index, L, unusedSplitIndex);
}
float EvalShadow_CascadedDepth_Dither_SplitIndex(HDShadowContext shadowContext, Texture2D tex, SamplerComparisonState samp, float2 positionSS, float3 positionWS, float3 normalWS, int index, float3 L, out int shadowSplitIndex)
{
float alpha;
int cascadeCount;
float shadow = 1.0;
shadowSplitIndex = EvalShadow_GetSplitIndex(shadowContext, index, positionWS, alpha, cascadeCount);
float3 basePositionWS = positionWS;
if (shadowSplitIndex >= 0.0)
{
HDShadowData sd = shadowContext.shadowDatas[index];
LoadDirectionalShadowDatas(sd, shadowContext, index + shadowSplitIndex);
positionWS = basePositionWS + sd.cacheTranslationDelta.xyz;
/* normal based bias */
float worldTexelSize = sd.worldTexelSize;
float3 normalBias = EvalShadow_NormalBias(worldTexelSize, sd.normalBias, normalWS);
/* We select what split we need to sample from */
float nextSplit = min(shadowSplitIndex + 1, cascadeCount - 1);
bool evalNextCascade = nextSplit != shadowSplitIndex && alpha > 0 && step(InterleavedGradientNoise(positionSS.xy, _TaaFrameInfo.z), alpha);
if (evalNextCascade)
{
LoadDirectionalShadowDatas(sd, shadowContext, index + nextSplit);
positionWS = basePositionWS + sd.cacheTranslationDelta.xyz;
float biasModifier = (sd.worldTexelSize / worldTexelSize);
normalBias *= biasModifier;
}
positionWS += normalBias;
float3 posTC = EvalShadow_GetTexcoordsAtlas(sd, _CascadeShadowAtlasSize.zw, positionWS, false);
shadow = DIRECTIONAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS);
shadow = (shadowSplitIndex < cascadeCount - 1) ? shadow : lerp(shadow, 1.0, alpha);
}
return shadow;
}
float EvalShadow_CascadedDepth_Dither(HDShadowContext shadowContext, Texture2D tex, SamplerComparisonState samp, float2 positionSS, float3 positionWS, float3 normalWS, int index, float3 L)
{
int unusedSplitIndex;
return EvalShadow_CascadedDepth_Dither_SplitIndex(shadowContext, tex, samp, positionSS, positionWS, normalWS, index, L, unusedSplitIndex);
}
// TODO: optimize this using LinearEyeDepth() to avoid having to pass the shadowToWorld matrix
float EvalShadow_SampleClosestDistance_Punctual(HDShadowData sd, Texture2D tex, SamplerState sampl, float3 positionWS, float3 L, float3 lightPositionWS)
{
float2 texelSize = sd.isInCachedAtlas ? _CachedShadowAtlasSize.zw : _ShadowAtlasSize.zw;
float4 closestNDC = { 0,0,0,1 };
float2 texelIdx = EvalShadow_GetTexcoordsAtlas(sd, texelSize, positionWS, closestNDC.xy, true);
// sample the shadow map
closestNDC.z = SAMPLE_TEXTURE2D_LOD(tex, sampl, texelIdx, 0).x;
// reconstruct depth position
float4 closestWS = mul(closestNDC, sd.shadowToWorld);
float3 occluderPosWS = closestWS.xyz / closestWS.w;
return distance(occluderPosWS, lightPositionWS);
}
#endif