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>
This commit is contained in:
1 parent
b6aaf69235
commit
ac25f59232
10 files changed
+896
-7
No files matched your search
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
using Unity.Cinemachine;
|
||||
|
||||
/// <summary>
|
||||
/// Drives the screen-space radial "hero cutout". Every late frame it projects the local player to
|
||||
/// screen space and feeds that position + a circle radius/feather + the player's eye-space depth into
|
||||
/// two global shader properties (<c>_HeroCutoutPosRadius</c>, <c>_HeroCutoutDepthOn</c>) that the
|
||||
/// <c>Custom/HeroCutoutLit</c> shader reads to clip occluder geometry between the camera and the
|
||||
/// player - punching a soft see-through hole toward the character.
|
||||
///
|
||||
/// The effect is only armed while the third-person orbit <see cref="CinemachineCamera"/> is the live
|
||||
/// Cinemachine camera (i.e. Player Control / Paint mode). In first-person, free-cam, or while a menu
|
||||
/// is open a different vcam is live, so the cutout disarms and occluders render solid. Disarming sets
|
||||
/// the global "enabled" flag to 0, which also matches the zero-initialised default - so any scene
|
||||
/// without this driver leaves cutout materials looking exactly like URP/Lit.
|
||||
///
|
||||
/// Attach to a persistent scene object (e.g. the GameModeManager host).
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class HeroCutout : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Orbit camera input whose CinemachineCamera must be live for the cutout to arm. Empty = found automatically.")]
|
||||
public ThirdPersonOrbitInput orbitInput;
|
||||
|
||||
[Tooltip("Game mode manager, used as a fallback source for the live player. Empty = found automatically.")]
|
||||
public GameModeManager modes;
|
||||
|
||||
[Header("Circle")]
|
||||
[Tooltip("Radius of the see-through circle, as a fraction of screen height (0.13 = 13% of height).")]
|
||||
[Range(0.02f, 0.6f)] public float radius = 0.13f;
|
||||
|
||||
[Tooltip("Soft-edge width of the circle, as a fraction of screen height.")]
|
||||
[Range(0f, 0.3f)] public float feather = 0.05f;
|
||||
|
||||
[Tooltip("Metres added to the player's depth so geometry right at the player isn't clipped away.")]
|
||||
public float depthBias = 0.5f;
|
||||
|
||||
[Tooltip("Vertical offset (m) from the follow target where the circle centres (chest/head height).")]
|
||||
public float heightOffset = 0f;
|
||||
|
||||
static readonly int PosRadiusId = Shader.PropertyToID("_HeroCutoutPosRadius");
|
||||
static readonly int DepthOnId = Shader.PropertyToID("_HeroCutoutDepthOn");
|
||||
|
||||
private CinemachineCamera _orbitCam;
|
||||
private CinemachineBrain _brain;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!orbitInput) orbitInput = FindFirstObjectByType<ThirdPersonOrbitInput>(FindObjectsInactive.Include);
|
||||
if (!modes) modes = FindFirstObjectByType<GameModeManager>();
|
||||
if (orbitInput) _orbitCam = orbitInput.GetComponent<CinemachineCamera>();
|
||||
}
|
||||
|
||||
private void OnDisable() => Disarm();
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
Camera cam = Camera.main;
|
||||
if (!cam) { Disarm(); return; }
|
||||
|
||||
if (!_brain || _brain.gameObject != cam.gameObject)
|
||||
_brain = cam.GetComponent<CinemachineBrain>();
|
||||
|
||||
if (!IsThirdPersonLive()) { Disarm(); return; }
|
||||
|
||||
Transform target = ResolveHeroTarget();
|
||||
if (!target) { Disarm(); return; }
|
||||
|
||||
Vector3 heroPos = target.position + Vector3.up * heightOffset;
|
||||
Vector3 vp = cam.WorldToViewportPoint(heroPos);
|
||||
if (vp.z <= 0f) { Disarm(); return; } // hero is behind the camera
|
||||
|
||||
Shader.SetGlobalVector(PosRadiusId, new Vector4(vp.x, vp.y, radius, Mathf.Max(feather, 1e-4f)));
|
||||
Shader.SetGlobalVector(DepthOnId, new Vector4(vp.z + depthBias, 1f, 0f, 0f));
|
||||
}
|
||||
|
||||
/// <summary>True only when the orbit vcam is the live Cinemachine camera (third-person modes).</summary>
|
||||
private bool IsThirdPersonLive()
|
||||
{
|
||||
if (!_orbitCam || !_brain) return false;
|
||||
var live = _brain.ActiveVirtualCamera as CinemachineCamera;
|
||||
return live && live == _orbitCam;
|
||||
}
|
||||
|
||||
private Transform ResolveHeroTarget()
|
||||
{
|
||||
if (_orbitCam && _orbitCam.Follow) return _orbitCam.Follow;
|
||||
if (modes && modes.Player) return modes.Player.transform;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Clears the "enabled" flag so cutout materials render as plain URP/Lit.</summary>
|
||||
private void Disarm() => Shader.SetGlobalVector(DepthOnId, Vector4.zero);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7d67030b8dcdec489471b88c35dd78b
|
||||
Reference in new issue
Block a user