#ifndef HERO_CUTOUT_INCLUDED #define HERO_CUTOUT_INCLUDED // Screen-space radial "hero cutout": clips occluder fragments that sit inside a circle around the // on-screen player AND in front of it, punching a clean see-through hole toward the character. // // These two globals are driven every frame by HeroCutout.cs (Shader.SetGlobalVector). They live // OUTSIDE UnityPerMaterial, so the SRP Batcher is unaffected. When the effect is disabled // (_HeroCutoutDepthOn.y < 0.5, also the zero-initialised default) ApplyHeroCutout early-outs and the // shader renders exactly like URP/Lit. // _HeroCutoutPosRadius : xy = hero viewport pos (0..1, bottom-left), z = radius, w = feather // radius/feather are in normalized viewport-HEIGHT units // _HeroCutoutDepthOn : x = hero eye-space depth (incl. bias), y = enabled (0/1) float4 _HeroCutoutPosRadius; float4 _HeroCutoutDepthOn; // Interleaved-gradient noise -> screen-door dither, gives the circle a soft edge with no extra // textures or includes. float HeroCutoutDither(float2 positionSS) { return frac(52.9829189 * frac(dot(positionSS, float2(0.06711056, 0.00583715)))); } void ApplyHeroCutout(float4 positionCS) { if (_HeroCutoutDepthOn.y < 0.5) return; // Flip-correct screen UV (matches Camera.WorldToViewportPoint's bottom-left convention). float2 uv = GetNormalizedScreenSpaceUV(positionCS); float2 delta = uv - _HeroCutoutPosRadius.xy; delta.x *= _ScreenParams.x / max(_ScreenParams.y, 1.0); // aspect-correct -> a round circle float dist = length(delta); float radius = _HeroCutoutPosRadius.z; float feather = max(_HeroCutoutPosRadius.w, 1e-4); float inside = 1.0 - smoothstep(radius - feather, radius, dist); // 1 inside, 0 outside // Only hole geometry between the camera and the hero; anything at/behind the player stays solid. float eyeDepth = LinearEyeDepth(positionCS.z, _ZBufferParams); float inFront = step(eyeDepth, _HeroCutoutDepthOn.x); float cut = inside * inFront; clip(HeroCutoutDither(positionCS.xy) - cut); } #endif