Files
chameleon/Assets/Shaders/HeroCutout.hlsl
T
adminandClaude Opus 4.8 ac25f59232 Add radial hero-cutout shader for third-person camera
Reveal the player when geometry sits between the orbit camera and the
character: a screen-space circular mask in a URP/Lit clone (HeroCutoutLit)
clips occluders in front of the player, depth-gated so only geometry nearer
than the hero is cut. The cutout lives outside UnityPerMaterial (SRP batcher
intact) and is a no-op when disarmed, so occluders look identical to URP/Lit.

A runtime driver (HeroCutout.cs on Main/GameManager) feeds the player's
screen position, radius, feather and depth into global shader properties and
only arms while the third-person orbit vcam is live; it disarms in
first-person, free-cam and the pause menu. Applied to the lobby walls via a
dedicated occluder material (floor left solid). Shipped but shelved for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:18:32 +02:00

47 lines
2.0 KiB
HLSL

#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