// SPDX-License-Identifier: (Not available for this version, you are only allowed to use this software if you have express permission from the copyright holder and agreed to the latest NiloToonURP EULA) // Copyright (c) 2021 Kuroneko ShaderLab Limited // For more information, visit -> https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample Shader "Hidden/NiloToon/AverageShadowTestRT" { HLSLINCLUDE // we need URP's shadow map related keywords #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN #if defined(_MAIN_LIGHT_SHADOWS_SCREEN) // use _SURFACE_TYPE_TRANSPARENT to force URP Shadow running the classic sample path in Shadows.hlsl's half MainLightRealtimeShadow(float4 shadowCoord){...} // since screen space shadow will not work for this shader #define _SURFACE_TYPE_TRANSPARENT #endif #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl" // for most game type, 128 is a big enough number, but still not affect performance #define MAX_CHARACTER_COUNT 128 #define MAX_DATA_ARRAY_SIZE 512 // = 128 characters * 4 data slot #define TEST_COUNT 6 // 2D hemisphere surface sampling: 13x13 grid = 169 points, 113 after circular disk mask, per character per frame float _GlobalAverageShadowTestBoundingSphereDataArray[MAX_DATA_ARRAY_SIZE]; float _GlobalAverageShadowStrength; half Frag(Varyings input) : SV_Target { // SHADER_LIBRARY_VERSION_MAJOR is deprecated for Unity2022.2 or later, so we will use UNITY_VERSION instead // https://github.com/Cyanilux/URP_ShaderCodeTemplates/blob/main/URP_SimpleLitTemplate.shader#L145 #if UNITY_VERSION >= 202220 // (for URP 14 or above) float2 uv = input.texcoord; // URP14 changed the naming from uv to texcoord, see URP14's Runtime\Utilities\Blit.hlsl #else // (for below URP 14) float2 uv = input.uv; #endif int index = floor(uv.x * MAX_CHARACTER_COUNT); float3 center; center.x = _GlobalAverageShadowTestBoundingSphereDataArray[index*4+0]; center.y = _GlobalAverageShadowTestBoundingSphereDataArray[index*4+1]; center.z = _GlobalAverageShadowTestBoundingSphereDataArray[index*4+2]; float radius = _GlobalAverageShadowTestBoundingSphereDataArray[index*4+3]; // for any not in use slots, radius should be 0, // early exit to improve performance, since usually not much characters are enabled in scene if(radius == 0.0) return 1; Light mainLight = GetMainLight(); // Note: close-camera fadeout from cascade mismatch is addressed by explicit per-sample cascade selection below. // Remaining risk is only third-party renderer behavior that does not preserve URP shadow constants/keywords. // Sample the light-facing hemisphere surface of the bounding sphere instead of the full 3D volume. // This significantly reduces self-shadowing: surface points face the light and sit in front of // the character mesh from the light's POV, so the character's own ShadowCaster cannot occlude them. float3 lightDir = normalize(mainLight.direction); float3 helperUp = abs(lightDir.y) < 0.999 ? float3(0,1,0) : float3(1,0,0); float3 U = normalize(cross(helperUp, lightDir)); float3 V = cross(lightDir, U); float bias = min(0.01 * radius, 0.05); float shadowTestSum = 0; float sampleCount = 0; for(int gu = -TEST_COUNT; gu <= TEST_COUNT; gu++) for(int gv = -TEST_COUNT; gv <= TEST_COUNT; gv++) { float fu = (float)gu / (float)TEST_COUNT; float fv = (float)gv / (float)TEST_COUNT; float d2 = fu * fu + fv * fv; if(d2 > 1.0) continue; float3 samplePos = center + (fu * U + fv * V) * radius + lightDir * (sqrt(max(0.0, 1.0 - d2)) * radius + bias); // Multi-cascade and screen-space keyword variants both use explicit cascade selection. // CASCADE keeps skip-on-outside behavior; SCREEN falls back to cascade 0 to stay valid // when split-sphere data is unavailable (e.g., single-cascade projects). #if defined(_MAIN_LIGHT_SHADOWS_CASCADE) || defined(_MAIN_LIGHT_SHADOWS_SCREEN) half cascadeIndex = ComputeCascadeIndex(samplePos); #if defined(_MAIN_LIGHT_SHADOWS_CASCADE) if(cascadeIndex >= half(4.0)) continue; #else if(cascadeIndex >= half(4.0)) cascadeIndex = half(0.0); #endif float4 shadowCoord = float4(mul(_MainLightWorldToShadow[cascadeIndex], float4(samplePos, 1.0)).xyz, 0.0); #else float4 shadowCoord = TransformWorldToShadowCoord(samplePos); #endif shadowTestSum += MainLightRealtimeShadow(shadowCoord); sampleCount += 1.0; } if(sampleCount < 1.0) return 1; // all samples outside cascade coverage -> no shadow data, assume lit (matches URP convention) shadowTestSum /= sampleCount; // Compress range: partial shadow barely darkens, only heavy shadow is visible shadowTestSum = smoothstep(0.25, 0.75, shadowTestSum); return lerp(1,saturate(shadowTestSum),_GlobalAverageShadowStrength); } ENDHLSL SubShader { ZTest Always ZWrite Off Cull Off Pass { Name "RenderAverageShadowTestRT" HLSLPROGRAM #pragma vertex Vert // FullscreenVert #pragma fragment Frag ENDHLSL } } }