#ifndef BADDOG_AREA_LIGHT_SHADOWS_INCLUDED #define BADDOG_AREA_LIGHT_SHADOWS_INCLUDED // URP shadow sampling utilities #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" #include "Packages/com.baddog.rendering.arealight/Shaders/Include/HDPCSS.hlsl" //----------------------------------------------------------------------------- // Area Light Shadow Data //----------------------------------------------------------------------------- // Maximum number of area lights that can cast shadows #define MAX_AREA_LIGHT_SHADOW_COUNT 8 // Area Light Shadow Atlas TEXTURE2D_SHADOW(_AreaLightsShadowmapTexture); // Area Light Shadow Atlas Size // x: 1.0 / width (texel size) // y: 1.0 / height (texel size) // z: width (resolution) // w: height (resolution) float4 _AreaLightShadowmapSize; // Area Light Shadow Parameters (per area light, indexed by areaLightIndex) // x: shadow strength [0,1] // y: soft shadow (1.0 for soft, 0.0 for hard) - reserved for future use // z: light type identifier (for shader variants) - reserved for future use // w: areaLightIndex (same as array index, or -1 if no shadow) float4 _AreaLightShadowParams[MAX_AREA_LIGHT_SHADOW_COUNT]; // Area Light World-to-Shadow Matrices (per area light, indexed by areaLightIndex) float4x4 _AreaLightsWorldToShadow[MAX_AREA_LIGHT_SHADOW_COUNT]; // PCSS Parameters for Area Light Shadows // x: shadowSoftness (world space units) // y: blockerSampleCount // z: filterSampleCount // w: rangeScale (for directional lights, 1.0 for area lights) float4 _PCSSAdditionalLightParams; //----------------------------------------------------------------------------- // PCSS Support Function (if not provided by URP Shadows.hlsl) //----------------------------------------------------------------------------- real SampleShadowmapFilteredPCSS(TEXTURE2D_SHADOW_PARAM(ShadowMap, sampler_ShadowMap), float4 shadowCoord, ShadowSamplingData samplingData, float4 pcssParams, float3 positionWS, bool isPerspectiveProjection) { // Generate jitter for PCSS using screen pixel coordinates float4 clipPos = TransformWorldToHClip(positionWS); float2 screenPos = ComputeScreenPos(clipPos).xy * _ScreenParams.xy; float sampleJitterAngle = InterleavedGradientNoise(screenPos, 0) * 2.0 * PI; float2 sampleJitter = float2(sin(sampleJitterAngle), cos(sampleJitterAngle)); // Extract PCSS parameters float shadowSoftness = pcssParams.x; int blockerSampleCount = (int)pcssParams.y; int filterSampleCount = (int)pcssParams.z; float lightRangeScale = pcssParams.w; float minFilterRadius = 2.0; // shadowmapSize format: (texelWidth, texelHeight, width, height) // We need to convert shadowSoftness (world units) to UV space // shadowSoftness is in world units, we convert to texels then to UV float lightArea = shadowSoftness * samplingData.shadowmapSize.x; // Convert to UV space using texel size float maxLightArea = 0.04; // 4% of UV space lightArea = min(lightArea, maxLightArea); // Shadow map bounds (full texture for non-atlas) float UMin = 0.0, UMax = 1.0, VMin = 0.0, VMax = 1.0; // Perform PCSS blocker search real averageBlockerDepth; real numBlockers; bool blockerFound = BlockerSearch(averageBlockerDepth, numBlockers, lightArea, shadowCoord.xyz, UMin, UMax, VMin, VMax, sampleJitter, ShadowMap, sampler_PointClamp, blockerSampleCount); // Calculate filter size based on penumbra estimation float filterSize = shadowSoftness * (isPerspectiveProjection ? PenumbraSizePunctual(shadowCoord.z, averageBlockerDepth) : PenumbraSizeDirectional(shadowCoord.z, averageBlockerDepth, lightRangeScale)); filterSize = blockerFound ? max(filterSize, minFilterRadius) : minFilterRadius; // Convert filter size to UV space (filterSize is in world units, multiply by texel size) filterSize *= samplingData.shadowmapSize.x; // Perform PCSS filtering return PCSS(shadowCoord.xyz, UMin, UMax, VMin, VMax, filterSize, sampleJitter, ShadowMap, sampler_ShadowMap, filterSampleCount); } //----------------------------------------------------------------------------- // Area Light Shadow Sampling //----------------------------------------------------------------------------- // Sample area light shadow for a given area light index // areaLightIndex: Index of the area light (matches _AreaLightDataBuffer index) // positionWS: World space position to sample shadow at // Returns: Shadow attenuation [0,1], where 1 = fully lit, 0 = fully shadowed half SampleAreaLightShadow(uint areaLightIndex, float3 positionWS) { float4 shadowParams = _AreaLightShadowParams[areaLightIndex]; // Check if this light has shadow (w >= 0) // w contains the areaLightIndex if shadow is enabled, or -1 if disabled if (shadowParams.w < 0.0) return 1.0; // Transform position to shadow space float4 shadowCoord = mul(_AreaLightsWorldToShadow[areaLightIndex], float4(positionWS, 1.0)); // Build sampling data compatible with URP shadow sampling ShadowSamplingData samplingData; samplingData.shadowOffset0 = 0; samplingData.shadowOffset1 = 0; samplingData.shadowmapSize = _AreaLightShadowmapSize; samplingData.softShadowQuality = half(shadowParams.y); // Perspective divide and out-of-bounds handling shadowCoord.xyz /= max(shadowCoord.w, 1e-5); if (BEYOND_SHADOW_FAR(shadowCoord)) return 1.0; real attenuation; // Directly call URP shadow filter entry points by BG keywords (no macro remap). #if defined(_BG_AREALIGHT_SHADOWS_PCF2X2) attenuation = SampleShadowmapFilteredLowQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData); #elif defined(_BG_AREALIGHT_SHADOWS_TENT5X5) attenuation = SampleShadowmapFilteredMediumQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData); #elif defined(_BG_AREALIGHT_SHADOWS_TENT7X7) attenuation = SampleShadowmapFilteredHighQuality(TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), shadowCoord, samplingData); #elif defined(_BG_AREALIGHT_SHADOWS_PCSS) // PCSS for area lights: reuse the PCSS path implemented in Shadows.hlsl, // controlled by _PCSSAdditionalLightParams (x: softness, y: blockerSamples, z: filterSamples, w: rangeScale) attenuation = SampleShadowmapFilteredPCSS( TEXTURE2D_SHADOW_ARGS(_AreaLightsShadowmapTexture, sampler_LinearClampCompare), float4(shadowCoord.xyz, 1.0), samplingData, _PCSSAdditionalLightParams, positionWS, /*isPerspectiveProjection*/ true); #else attenuation = real(SAMPLE_TEXTURE2D_SHADOW(_AreaLightsShadowmapTexture, sampler_LinearClampCompare, shadowCoord.xyz)); #endif // Apply shadow strength attenuation = LerpWhiteTo(attenuation, shadowParams.x); return half(attenuation); } #endif // BADDOG_AREA_LIGHT_SHADOWS_INCLUDED