diff --git a/TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.fbx b/TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.FBX similarity index 100% rename from TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.fbx rename to TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.FBX diff --git a/TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.fbx.meta b/TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.FBX.meta similarity index 100% rename from TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.fbx.meta rename to TestProjects/UniversalGraphicsTest_Foundation/Assets/CommonAssets/Meshes/Adam/Crowd_LOD0.FBX.meta diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs index a83cab9ebc3..19fdd5ae990 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/HDLightUI.cs @@ -1276,10 +1276,18 @@ static void DrawShadowMapAdditionalContent(SerializedHDLight serialized, Editor if (lightType == HDLightType.Area && serialized.areaLightShape == AreaLightShape.Rectangle) { - EditorGUILayout.Slider(serialized.evsmExponent, HDAdditionalLightData.k_MinEvsmExponent, HDAdditionalLightData.k_MaxEvsmExponent, s_Styles.evsmExponent); - EditorGUILayout.Slider(serialized.evsmLightLeakBias, HDAdditionalLightData.k_MinEvsmLightLeakBias, HDAdditionalLightData.k_MaxEvsmLightLeakBias, s_Styles.evsmLightLeakBias); - EditorGUILayout.Slider(serialized.evsmVarianceBias, HDAdditionalLightData.k_MinEvsmVarianceBias, HDAdditionalLightData.k_MaxEvsmVarianceBias, s_Styles.evsmVarianceBias); - EditorGUILayout.IntSlider(serialized.evsmBlurPasses, HDAdditionalLightData.k_MinEvsmBlurPasses, HDAdditionalLightData.k_MaxEvsmBlurPasses, s_Styles.evsmAdditionalBlurPasses); + if (HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.hdShadowInitParams.shadowFilteringQuality == HDShadowFilteringQuality.VeryHigh) + { + EditorGUILayout.Slider(serialized.slopeBias, 0.0f, 1.0f, s_Styles.slopeBias); + EditorGUILayout.Slider(serialized.normalBias, 0.0f, 5.0f, s_Styles.normalBias); + } + else + { + EditorGUILayout.Slider(serialized.evsmExponent, HDAdditionalLightData.k_MinEvsmExponent, HDAdditionalLightData.k_MaxEvsmExponent, s_Styles.evsmExponent); + EditorGUILayout.Slider(serialized.evsmLightLeakBias, HDAdditionalLightData.k_MinEvsmLightLeakBias, HDAdditionalLightData.k_MaxEvsmLightLeakBias, s_Styles.evsmLightLeakBias); + EditorGUILayout.Slider(serialized.evsmVarianceBias, HDAdditionalLightData.k_MinEvsmVarianceBias, HDAdditionalLightData.k_MaxEvsmVarianceBias, s_Styles.evsmVarianceBias); + EditorGUILayout.IntSlider(serialized.evsmBlurPasses, HDAdditionalLightData.k_MinEvsmBlurPasses, HDAdditionalLightData.k_MaxEvsmBlurPasses, s_Styles.evsmAdditionalBlurPasses); + } } else { diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs index e2b79e667f5..2379c8cc806 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs @@ -279,11 +279,6 @@ public class Styles public static readonly GUIContent[] shadowBitDepthNames = { new GUIContent("32 bit"), new GUIContent("16 bit") }; public static readonly int[] shadowBitDepthValues = { (int)DepthBits.Depth32, (int)DepthBits.Depth16 }; - // TEMP: HDShadowFilteringQuality.VeryHigh - This filtering mode is not ready so disabling in UI - // To re-enable remove the two following light and re-enable the third one - public static readonly GUIContent[] shadowFilteringNames = { new GUIContent("Low"), new GUIContent("Medium"), new GUIContent("High") }; - public static readonly int[] shadowFilteringValue = { 0, 1, 2 }; - public const string memoryDrawback = "Adds GPU memory"; public const string shaderVariantDrawback = "Adds Shader Variants"; public const string lotShaderVariantDrawback = "Adds multiple Shader Variants"; diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs index b978a39e788..9b5907690e6 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs @@ -355,11 +355,7 @@ static void Drawer_SectionShadows(SerializedHDRenderPipelineAsset serialized, Ed if (!serialized.renderPipelineSettings.supportedLitShaderMode.hasMultipleDifferentValues) { - // TEMP: HDShadowFilteringQuality.VeryHigh - This filtering mode is not ready so disabling in UI - // To re-enable remove the wo following light and re-enable the third one - int value = EditorGUILayout.IntPopup(Styles.filteringQuality, serialized.renderPipelineSettings.hdShadowInitParams.shadowFilteringQuality.enumValueIndex, Styles.shadowFilteringNames, Styles.shadowFilteringValue); - serialized.renderPipelineSettings.hdShadowInitParams.shadowFilteringQuality.enumValueIndex = value; - //EditorGUILayout.PropertyField(serialized.renderPipelineSettings.hdShadowInitParams.shadowFilteringQuality, Styles.filteringQuality); + EditorGUILayout.PropertyField(serialized.renderPipelineSettings.hdShadowInitParams.shadowFilteringQuality, Styles.filteringQuality); } else { diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Light/HDAdditionalLightData.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Light/HDAdditionalLightData.cs index 4f7e57d2e15..427a6966438 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Light/HDAdditionalLightData.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Light/HDAdditionalLightData.cs @@ -2500,7 +2500,7 @@ void SetCommonShadowRequestSettings(HDShadowRequest shadowRequest, VisibleLight shadowRequest.shadowSoftness = softness; shadowRequest.blockerSampleCount = blockerSampleCount; shadowRequest.filterSampleCount = filterSampleCount; - shadowRequest.minFilterSize = minFilterSize * 0.001f; // This divide by 1000 is here to have a range [0...1] exposed to user + shadowRequest.minFilterSize = minFilterSize * 0.01f; // This divide by 1000 is here to have a range [0...1] exposed to user shadowRequest.kernelSize = (uint)kernelSize; shadowRequest.lightAngle = (lightAngle * Mathf.PI / 180.0f); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDPCSS.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDPCSS.hlsl index 724f578a1a5..9ba197874d3 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDPCSS.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDPCSS.hlsl @@ -86,80 +86,128 @@ static const float2 fibonacciSpiralDirection[DISK_SAMPLE_COUNT] = float2 (0.9205789302157817, 0.3905565685566777) }; -real2 ComputeFibonacciSpiralDiskSample(const in int sampleIndex, const in real diskRadius, const in real sampleCountInverse, const in real sampleCountBias) +// Samples uniformly spread across the disk kernel +real2 ComputeFibonacciSpiralDiskSampleUniform(const in int sampleIndex, const in real sampleCountInverse, out real sampleDistNorm) { - real sampleRadius = diskRadius * sqrt((real)sampleIndex * sampleCountInverse + sampleCountBias); - real2 sampleDirection = fibonacciSpiralDirection[sampleIndex]; - return sampleDirection * sampleRadius; + sampleDistNorm = (real)sampleIndex * sampleCountInverse; + + // sqrt results in uniform distribution + sampleDistNorm = sqrt(sampleDistNorm); + + return fibonacciSpiralDirection[sampleIndex] * sampleDistNorm; } -real PenumbraSizePunctual(real Reciever, real Blocker) +// Samples denser near the center - important for blocker search +real2 ComputeFibonacciSpiralDiskSample(const in int sampleIndex, const in real sampleCountInverse, out real sampleDistNorm) { - return abs((Reciever - Blocker) / Blocker); + sampleDistNorm = (real)sampleIndex * sampleCountInverse; + + // Third power chosen arbitrarily - center area is really that much more important + // TODO: experiment with other radial functions, still overweighing the center though + sampleDistNorm = sampleDistNorm * sampleDistNorm * sampleDistNorm; + + return fibonacciSpiralDirection[sampleIndex] * sampleDistNorm; +} + +real PenumbraSizePunctual(real receiver, real blocker) +{ + return abs((receiver - blocker) / blocker); } -real PenumbraSizeDirectional(real Reciever, real Blocker, real rangeScale) +real PenumbraSizeDirectional(real receiver, real blocker, real rangeScale) { - return abs(Reciever - Blocker) * rangeScale; + return abs(receiver - blocker) * rangeScale; +} + +void FilterScaleOffset(real3 coord, real maxSampleZDistance, real shadowmapSamplingScale, out real2 filterScalePos, out real2 filterScaleNeg, out real2 filterOffset) +{ + real d = shadowmapSamplingScale * maxSampleZDistance / (1 - coord.z); + real2 target = (coord.xy + 0.5) * 0.5; + + filterScalePos = (1 - target) * d; + filterScaleNeg = target * d; + filterOffset = (target - coord.xy) * d; } -bool BlockerSearch(inout real averageBlockerDepth, inout real numBlockers, real lightArea, real3 coord, real2 sampleJitter, Texture2D shadowMap, SamplerState pointSampler, int sampleCount) +bool BlockerSearch(inout real averageBlockerDepth, inout real numBlockers, real maxSampleZDistance, real2 shadowmapInAtlasScale, real2 posTCAtlas, real3 posTCShadowmap, real2 sampleJitter, Texture2D shadowMap, SamplerState pointSampler, int sampleCount) { real blockerSum = 0.0; real sampleCountInverse = rcp((real)sampleCount); - real sampleCountBias = 0.5 * sampleCountInverse; real ditherRotation = sampleJitter.x; + // The z extent of the filter cone shouldn't go beyond the near plane of the shadow. Near plane at 1. + maxSampleZDistance = min(1 - posTCShadowmap.z, maxSampleZDistance); + + real2 filterScalePos, filterScaleNeg; + real2 filterOffset; + FilterScaleOffset(posTCShadowmap, maxSampleZDistance, shadowmapInAtlasScale.x, filterScalePos, filterScaleNeg, filterOffset); + for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i) { - real2 offset = ComputeFibonacciSpiralDiskSample(i, lightArea, sampleCountInverse, sampleCountBias); + real sampleDistNorm; + real2 offset = ComputeFibonacciSpiralDiskSample(i, sampleCountInverse, sampleDistNorm); offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x, offset.x * -sampleJitter.x + offset.y * sampleJitter.y); - real shadowMapDepth = SAMPLE_TEXTURE2D_LOD(shadowMap, pointSampler, coord.xy + offset, 0.0).x; + offset = offset * (offset > 0 ? filterScalePos : filterScaleNeg) + filterOffset * sampleDistNorm; + + real blocker = SAMPLE_TEXTURE2D_LOD(shadowMap, pointSampler, posTCAtlas + offset, 0.0).x; - if (COMPARE_DEVICE_DEPTH_CLOSER(shadowMapDepth, coord.z)) + real zoffset = maxSampleZDistance * sampleDistNorm; + + if (COMPARE_DEVICE_DEPTH_CLOSER(blocker, posTCShadowmap.z + zoffset)) { - blockerSum += shadowMapDepth; + blockerSum += blocker; numBlockers += 1.0; } } - averageBlockerDepth = blockerSum / numBlockers; + averageBlockerDepth = numBlockers > 0 ? blockerSum / numBlockers : posTCShadowmap.z; return numBlockers >= 1; } -real PCSS(real3 coord, real filterRadius, real2 scale, real2 offset, real2 sampleJitter, Texture2D shadowMap, SamplerComparisonState compSampler, int sampleCount) +real PCSS(real2 posTCAtlas, real3 posTCShadowmap, real maxSampleZDistance, real2 shadowmapInAtlasScale, real2 shadowmapInAtlasOffset, real2 sampleJitter, Texture2D shadowMap, SamplerComparisonState compSampler, int sampleCount) { - real UMin = offset.x; - real UMax = offset.x + scale.x; + real UMin = shadowmapInAtlasOffset.x; + real UMax = shadowmapInAtlasOffset.x + shadowmapInAtlasScale.x; - real VMin = offset.y; - real VMax = offset.y + scale.y; + real VMin = shadowmapInAtlasOffset.y; + real VMax = shadowmapInAtlasOffset.y + shadowmapInAtlasScale.y; real sum = 0.0; real sampleCountInverse = rcp((real)sampleCount); real sampleCountBias = 0.5 * sampleCountInverse; real ditherRotation = sampleJitter.x; + real2 filterScalePos, filterScaleNeg; + real2 filterOffset; + FilterScaleOffset(posTCShadowmap, maxSampleZDistance, shadowmapInAtlasScale.x, filterScalePos, filterScaleNeg, filterOffset); + for (int i = 0; i < sampleCount && i < DISK_SAMPLE_COUNT; ++i) { - real2 offset = ComputeFibonacciSpiralDiskSample(i, filterRadius, sampleCountInverse, sampleCountBias); + real sampleDistNorm; + real2 offset = ComputeFibonacciSpiralDiskSampleUniform(i, sampleCountInverse, sampleDistNorm); offset = real2(offset.x * sampleJitter.y + offset.y * sampleJitter.x, offset.x * -sampleJitter.x + offset.y * sampleJitter.y); - real U = coord.x + offset.x; - real V = coord.y + offset.y; + offset = offset * (offset > 0 ? filterScalePos : filterScaleNeg) + filterOffset * sampleDistNorm; + + real U = posTCAtlas.x + offset.x; + real V = posTCAtlas.y + offset.y; + + real zoffset = maxSampleZDistance * sampleDistNorm; //NOTE: We must clamp the sampling within the bounds of the shadow atlas. // Overfiltering will leak results from other shadow lights. //TODO: Investigate moving this to blocker search. - // coord.xy = clamp(coord.xy, float2(UMin, VMin), float2(UMax, VMax)); + // coord.xy = clamp(posTCAtlas.xy, float2(UMin, VMin), float2(UMax, VMax)); + // TODO: vectorize into two comparisons? if (U <= UMin || U >= UMax || V <= VMin || V >= VMax) - sum += SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, real3(coord.xy, coord.z)).r; + // TODO: why wasn't it just not sampling here? Investigate before removing + sum += 1;//SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, real3(posTCAtlas, posTCShadowmap.z + zoffset)).r; else - sum += SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, real3(U, V, coord.z)).r; + sum += SAMPLE_TEXTURE2D_SHADOW(shadowMap, compSampler, real3(U, V, posTCShadowmap.z + zoffset)).r; } return sum / sampleCount; diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowAlgorithms.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowAlgorithms.hlsl index 0e08873fe78..6b0de750b85 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowAlgorithms.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowAlgorithms.hlsl @@ -139,24 +139,31 @@ float3 EvalShadow_NormalBias(float worldTexelSize, float normalBias, float3 norm 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) + +bool CalculatePosTC(HDShadowData sd, float2 texelSize, float3 positionWS, float3 normalWS, float L_dist, bool perspective, out float3 posTC) { - 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); + 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 + 0.5f * texelSize; - return any(posTC.xy > maxCoord || posTC.xy < minCoord) ? 1.0f : PUNCTUAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS); + return !any(posTC.xy > maxCoord || posTC.xy < minCoord); +} + +// +// 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; + float3 posTC; + return CalculatePosTC(sd, texelSize, positionWS, normalWS, L_dist, perspective, posTC) ? PUNCTUAL_FILTER_ALGORITHM(sd, positionSS, posTC, tex, samp, FIXED_UNIFORM_BIAS) : 1.0f; } // @@ -179,6 +186,17 @@ float EvalShadow_AreaDepth(HDShadowData sd, Texture2D tex, float2 positionSS, fl { float2 texelSize = sd.isInCachedAtlas ? _CachedAreaShadowAtlasSize.zw : _AreaShadowAtlasSize.zw; +// TODO: refactor into AREA_FILTER_ALGORITHM +#ifdef SHADOW_VERY_HIGH // Do PCSS + + float3 posTC; + if (CalculatePosTC(sd, texelSize, positionWS, normalWS, L_dist, perspective, posTC)) + return SampleShadow_PCSS(posTC, positionSS, sd.shadowMapSize.xy * texelSize, sd.atlasOffset, sd.shadowFilterParams0.x, sd.shadowFilterParams0.w, asint(sd.shadowFilterParams0.y), asint(sd.shadowFilterParams0.z), tex, s_linear_clamp_compare_sampler, s_point_clamp_sampler, FIXED_UNIFORM_BIAS, sd.zBufferParam, true, (sd.isInCachedAtlas ? _CachedAreaShadowAtlasSize.xz : _AreaShadowAtlasSize.xz)); + else + return 1.0f; + +#else // Do EVSM + positionWS = positionWS + sd.cacheTranslationDelta.xyz; float3 posTC = EvalShadow_GetTexcoordsAtlas(sd, texelSize, positionWS, perspective); @@ -187,6 +205,8 @@ float EvalShadow_AreaDepth(HDShadowData sd, Texture2D tex, float2 positionSS, fl EvalShadow_Area_GetMinMaxCoords(sd, texelSize, minCoord, maxCoord); return any(posTC.xy > maxCoord || posTC.xy < minCoord) ? 1.0f : AREA_FILTER_ALGORITHM(sd, positionSS, posTC, tex, s_linear_clamp_compare_sampler, FIXED_UNIFORM_BIAS); + +#endif } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowManager.cs index 3de9c87f5d1..3856eb5b641 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowManager.cs @@ -375,7 +375,7 @@ public void InitShadowManager(HDRenderPipelineRuntimeResources renderPipelineRes areaAtlasInitParams.width = initParams.areaLightShadowAtlas.shadowAtlasResolution; areaAtlasInitParams.height = initParams.areaLightShadowAtlas.shadowAtlasResolution; areaAtlasInitParams.atlasShaderID = HDShaderIDs._ShadowmapAreaAtlas; - areaAtlasInitParams.blurAlgorithm = HDShadowAtlas.BlurAlgorithm.EVSM; + areaAtlasInitParams.blurAlgorithm = GetAreaLightShadowBlurAlgorithm(); areaAtlasInitParams.depthBufferBits = initParams.areaLightShadowAtlas.shadowAtlasDepthBits; areaAtlasInitParams.name = "Area Light Shadow Map Atlas"; @@ -453,6 +453,12 @@ public static DirectionalShadowAlgorithm GetDirectionalShadowAlgorithm() return DirectionalShadowAlgorithm.PCF5x5; } + public static HDShadowAtlas.BlurAlgorithm GetAreaLightShadowBlurAlgorithm() + { + return HDRenderPipeline.currentAsset.currentPlatformRenderPipelineSettings.hdShadowInitParams.shadowFilteringQuality == HDShadowFilteringQuality.VeryHigh ? + HDShadowAtlas.BlurAlgorithm.None : HDShadowAtlas.BlurAlgorithm.EVSM; + } + public void UpdateShaderVariablesGlobalCB(ref ShaderVariablesGlobal cb) { if (m_MaxShadowRequests == 0) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowSampling.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowSampling.hlsl index d912a266a1c..2e78862f7b7 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowSampling.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/Shadow/HDShadowSampling.hlsl @@ -286,56 +286,40 @@ float SampleShadow_MSM_1tap(float3 tcs, float lightLeakBias, float momentBias, f // PCSS sampling // // Note shadowAtlasInfo contains: x: resolution, y: the inverse of atlas resolution -float SampleShadow_PCSS(float3 tcs, float2 posSS, float2 scale, float2 offset, float shadowSoftness, float minFilterRadius, int blockerSampleCount, int filterSampleCount, Texture2D tex, SamplerComparisonState compSamp, SamplerState samp, float depthBias, float4 zParams, bool isPerspective, float2 shadowAtlasInfo) +float SampleShadow_PCSS(float3 posTCAtlas, float2 posSS, float2 scale, float2 offset, float shadowSoftness, float minFilterRadius, int blockerSampleCount, int filterSampleCount, Texture2D tex, SamplerComparisonState compSamp, SamplerState samp, float depthBias, float4 zParams, bool isPerspective, float2 shadowAtlasInfo) { #if SHADOW_USE_DEPTH_BIAS == 1 // add the depth bias - tcs.z += depthBias; + posTCAtlas.z += depthBias; #endif + float maxSampleZDistance = shadowSoftness; + + // Undo shadowmap-in-atlas scaling of this value, since we don't interpret it here as the size of the sampling kernel, but z distance + float shadowmapWidth = scale.x * shadowAtlasInfo.x; + // TODO: this constant is here for historical reasons, move it to the c# side/softness fitting + maxSampleZDistance *= 4096.0 / shadowmapWidth; + uint taaFrameIndex = _TaaFrameInfo.z; float sampleJitterAngle = InterleavedGradientNoise(posSS.xy, taaFrameIndex) * 2.0 * PI; float2 sampleJitter = float2(sin(sampleJitterAngle), cos(sampleJitterAngle)); - - // Note: this is a hack, but the original implementation was faulty as it didn't scale offset based on the resolution of the atlas (*not* the shadow map). - // All the softness fitting has been done using a reference 4096x4096, hence the following scale. - float atlasResFactor = (4096 * shadowAtlasInfo.y); - - // max softness is empirically set resolution of 512, so needs to be set res independent. - float shadowMapRes = scale.x * shadowAtlasInfo.x; // atlas is square - float resIndepenentMaxSoftness = 0.04 * (shadowMapRes / 512); + // TODO: should maybe pass it from an earlier stage instead of calculating it again + float3 posTCShadowmap = float3((posTCAtlas.xy - offset) / scale, posTCAtlas.z); //1) Blocker Search float averageBlockerDepth = 0.0; float numBlockers = 0.0; - bool blockerFound = BlockerSearch(averageBlockerDepth, numBlockers, min((shadowSoftness + 0.000001), resIndepenentMaxSoftness) * atlasResFactor, tcs, sampleJitter, tex, samp, blockerSampleCount); - - // We scale the softness also based on the distance between the occluder if we assume that the light is a sphere source. - // Also, we don't bother if the blocker has not been found. - if (isPerspective && blockerFound) - { - float dist = 1.0f / (zParams.z * averageBlockerDepth + zParams.w); - dist = min(dist, 7.5); // We need to clamp the distance as the fitted curve will do strange things after this and because there is no point in scale further after this point. - float dist2 = dist * dist; - float dist4 = dist2 * dist2; - // Fitted curve to match ray trace reference as good as possible. - float distScale = 3.298300241 - 2.001364639 * dist + 0.4967311427 * dist2 - 0.05464058455 * dist * dist2 + 0.0021974 * dist2 * dist2; - shadowSoftness *= distScale; - - // Clamp maximum softness here again and not C# as it could be scaled signifcantly by the distance scale. - shadowSoftness = min(shadowSoftness, resIndepenentMaxSoftness); - } + bool blockerFound = BlockerSearch(averageBlockerDepth, numBlockers, maxSampleZDistance, scale, posTCAtlas.xy, posTCShadowmap, sampleJitter, tex, samp, blockerSampleCount); //2) Penumbra Estimation - float filterSize = shadowSoftness * (isPerspective ? PenumbraSizePunctual(tcs.z, averageBlockerDepth) : - PenumbraSizeDirectional(tcs.z, averageBlockerDepth, zParams.x)); - filterSize = max(filterSize, minFilterRadius); - filterSize *= atlasResFactor; + maxSampleZDistance *= isPerspective ? PenumbraSizePunctual(posTCAtlas.z, averageBlockerDepth) : PenumbraSizeDirectional(posTCAtlas.z, averageBlockerDepth, zParams.x); + maxSampleZDistance = max(maxSampleZDistance, minFilterRadius); //3) Filter // Note: we can't early out of the function if blockers are not found since Vulkan triggers a warning otherwise. Hence, we check for blockerFound here. - return blockerFound ? PCSS(tcs, filterSize, scale, offset, sampleJitter, tex, compSamp, filterSampleCount) : 1.0f; + bool withinShadowmap = all(posTCShadowmap.xy > 0 && posTCShadowmap.xy < 1); + return blockerFound && withinShadowmap ? PCSS(posTCAtlas.xy, posTCShadowmap, maxSampleZDistance, scale, offset, sampleJitter, tex, compSamp, filterSampleCount) : 1.0f; } // Note this is currently not available as an option, but is left here to show what needs including if IMS is to be used.