128 lines
5.7 KiB
GLSL

// 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
}
}
}