diff --git a/Assets/Scripts/MatchManager.cs b/Assets/Scripts/MatchManager.cs
index 15ab957..f9085b1 100644
--- a/Assets/Scripts/MatchManager.cs
+++ b/Assets/Scripts/MatchManager.cs
@@ -145,11 +145,23 @@ public class MatchManager : MonoBehaviour
[Min(0f)]
[SerializeField] private float shootCooldown = 0.4f;
+ [Header("Concealment reveal")]
+ [Tooltip("A hider whose ConcealmentMeter EmbeddedPercent reaches this value (while a match is running) is " +
+ "revealed through walls with the material's X-ray glow, so nobody can hide by burying itself deep " +
+ "inside geometry. 0..100; 100 effectively disables it (never reached). Read owner-side per avatar.")]
+ [Range(0f, 100f)]
+ [SerializeField] private float embeddedRevealPercent = 80f;
+
/// Range of the hunter's shoot raycast (read by ).
public float ShootRange => shootRange;
/// Hunter shoot cooldown in seconds (read by ).
public float ShootCooldown => shootCooldown;
+ /// EmbeddedPercent [0..100] a hider must reach for the through-walls X-ray reveal to switch on
+ /// (only while a match is running). Read by each owner's against its own
+ /// concealment meter; baked identically on every client, so it needs no replication.
+ public float EmbeddedRevealPercent => embeddedRevealPercent;
+
/// Server-local phase guard. Not replicated - see class summary.
public MatchPhase Phase { get; private set; } = MatchPhase.Lobby;
diff --git a/Assets/Scripts/MeshPainter.Setup.cs b/Assets/Scripts/MeshPainter.Setup.cs
index 70c7c3b..f0f1cf4 100644
--- a/Assets/Scripts/MeshPainter.Setup.cs
+++ b/Assets/Scripts/MeshPainter.Setup.cs
@@ -260,4 +260,19 @@ public partial class MeshPainter
if (_materialInstance && _materialInstance.HasProperty(EmissionColorId))
_materialInstance.SetColor(EmissionColorId, Color.white * Mathf.Max(0f, emissionIntensity));
}
+
+ /// Eases the see-through X-ray glow's alpha toward its on/off target ()
+ /// at a constant rate, so the reveal fades in/out over xrayFadeDuration seconds instead of popping.
+ /// Rgb is left untouched (the authored glow colour). Only writes the material on an actual change, and
+ /// snaps instantly when the duration is 0.
+ private void TickXRayFade()
+ {
+ if (!_materialInstance || !_materialInstance.HasProperty(XRayColorId)) return;
+ Color c = _materialInstance.GetColor(XRayColorId);
+ float step = xrayFadeDuration > 0f ? Time.deltaTime / xrayFadeDuration : 1f;
+ float a = Mathf.MoveTowards(c.a, _xrayTarget, step);
+ if (a == c.a) return; // already at target -> no material write
+ c.a = a;
+ _materialInstance.SetColor(XRayColorId, c);
+ }
}
diff --git a/Assets/Scripts/MeshPainter.cs b/Assets/Scripts/MeshPainter.cs
index 8be5467..a05fd81 100644
--- a/Assets/Scripts/MeshPainter.cs
+++ b/Assets/Scripts/MeshPainter.cs
@@ -120,6 +120,25 @@ public partial class MeshPainter : MonoBehaviour
set { if (_materialInstance && _materialInstance.HasProperty(ParallaxId)) _materialInstance.SetFloat(ParallaxId, Mathf.Max(0f, value) * ParallaxBase); }
}
+ /// Drives the see-through-walls X-ray overlay (see PlayerCamoLit): sets the TARGET for
+ /// the ALPHA of the authored _XRayColor, so the rgb glow colour set on the material is preserved
+ /// (target 0 = off/no effect, 1 = full glow). The material alpha eases toward this target over
+ /// seconds in Update (a smooth fade instead of a hard pop); set the
+ /// duration to 0 for an instant toggle. This is a per-avatar runtime reveal, NOT a painted channel: it
+ /// neither persists with the painting nor round-trips through the HUD.
+ /// raises it for the remaining hiders during the resolution phase and for a hider that buries itself
+ /// too deep in geometry (over the concealment threshold).
+ public bool XRayRevealed
+ {
+ get => _xrayTarget > 0f;
+ set => _xrayTarget = value ? 1f : 0f;
+ }
+
+ [Tooltip("Seconds for the see-through X-ray reveal glow to fade fully in or out when toggled (0 = instant snap).")]
+ [Min(0f)]
+ [SerializeField] private float xrayFadeDuration = 0.3f;
+ private float _xrayTarget; // desired _XRayColor alpha (0/1); the material eases toward it each frame
+
[Header("Backend")]
[Tooltip("ON = GPU painting (fast), OFF = CPU painting. Can be toggled live in Play mode.")]
public bool useGpu = true;
@@ -361,6 +380,7 @@ public partial class MeshPainter : MonoBehaviour
private static readonly int HalfLambertId = Shader.PropertyToID("_HalfLambert");
private static readonly int HalfLambertBrightnessId = Shader.PropertyToID("_HalfLambertBrightness");
private static readonly int SpecularDampenId = Shader.PropertyToID("_SpecularDampen");
+ private static readonly int XRayColorId = Shader.PropertyToID("_XRayColor");
private static readonly int BrushColorId = Shader.PropertyToID("_BrushColor");
private static readonly int ColorWriteMaskId = Shader.PropertyToID("_ColorWriteMask");
private static readonly int BrushCenterId = Shader.PropertyToID("_BrushCenter");
@@ -404,6 +424,10 @@ public partial class MeshPainter : MonoBehaviour
if (_materialInstance.HasProperty(BaseColorId)) _materialInstance.SetColor(BaseColorId, Color.white);
else if (_materialInstance.HasProperty(ColorId)) _materialInstance.SetColor(ColorId, Color.white);
+ // Seed the X-ray reveal target from the authored alpha so the fade baseline matches the material
+ // (authored 0 = off). NetworkPlayerSetup drives this target at runtime; Update eases the alpha to it.
+ if (_materialInstance.HasProperty(XRayColorId)) _xrayTarget = _materialInstance.GetColor(XRayColorId).a;
+
BuildSeedColors();
// Always start fresh from the material seed. Saved paintings are loaded explicitly from the
// HUD library (MeshPainter.Persistence), never auto-loaded on start.
@@ -695,6 +719,7 @@ public partial class MeshPainter : MonoBehaviour
if (useGpu != _gpuActive) SwitchBackend(useGpu);
RefreshEmissionColor(); // live-tunable emission intensity
+ TickXRayFade(); // ease the see-through X-ray glow toward its on/off target (runs on every avatar)
// Painting disabled by the game mode (e.g. player-control mode): do nothing.
if (!PaintingEnabled)
diff --git a/Assets/Scripts/NetworkPlayerSetup.cs b/Assets/Scripts/NetworkPlayerSetup.cs
index 31842e8..f9f497b 100644
--- a/Assets/Scripts/NetworkPlayerSetup.cs
+++ b/Assets/Scripts/NetworkPlayerSetup.cs
@@ -81,6 +81,13 @@ public class NetworkPlayerSetup : NetworkBehaviour
/// target that is already out of play.
public bool IsFound => _isFound.Value;
+ // Owner-authoritative "this hider is buried too deep" flag. Only the OWNER runs the ConcealmentMeter
+ // (it is disabled on remotes), so the owner compares its EmbeddedPercent against MatchManager's
+ // threshold and writes this; every client reads it to switch the through-walls X-ray glow on/off for
+ // this avatar. Stays false on hunters / the lobby avatar and whenever no match is running.
+ private readonly NetworkVariable _embeddedReveal =
+ new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
+
// Conversion-mode in-place respawn position. The owner holds authority over the NetworkTransform, so the
// server's Instantiate position is NOT honoured on the owner (it would otherwise snap to the prefab origin,
// 0,0,0). The server writes the tagged world position here in its OnNetworkSpawn so it rides the initial
@@ -104,9 +111,16 @@ public class NetworkPlayerSetup : NetworkBehaviour
private bool _lastVisible = true;
private bool _renderGate; // conversion: body force-hidden until the fresh hunter has settled into place
+ private MeshPainter _painter; // owns the per-avatar material instance carrying _XRayColor
+ private ConcealmentMeter _concealment; // owner-only embedded-percent source (disabled on remotes)
+ private bool _xrayApplied;
+ private bool _lastXRay;
+
public override void OnNetworkSpawn()
{
_bodyRenderers = GetComponentsInChildren(true);
+ _painter = GetComponentInChildren(true);
+ _concealment = GetComponentInChildren(true);
_isFound.OnValueChanged += OnFoundChanged;
if (_isFound.Value) ApplyFound(); // defensive: an avatar is never normally spawned already-found
@@ -142,10 +156,10 @@ public class NetworkPlayerSetup : NetworkBehaviour
if (cc) cc.enabled = false;
// Owner cleanup: the concealment meter is a per-frame skinned-mesh bake + voxelization +
- // physics overlap whose result only feeds the LOCAL player (HUD / gameplay). A remote avatar's
- // concealment is computed on its own owner's machine, so running it here is pure wasted CPU.
- var concealment = GetComponentInChildren(true);
- if (concealment) concealment.enabled = false;
+ // physics overlap whose result only feeds the LOCAL player (HUD / gameplay + the embedded-reveal
+ // flag). A remote avatar's concealment is computed on its own owner's machine, so running it here
+ // is pure wasted CPU.
+ if (_concealment) _concealment.enabled = false;
// NOTE: SkinnedColliderBaker deliberately stays ENABLED on remotes. MeshPainter's stamp replay
// (ApplyNetworkStamp -> DrawBrushGpu) rasterizes the baker's live, per-frame BakedMesh to map a
@@ -219,7 +233,12 @@ public class NetworkPlayerSetup : NetworkBehaviour
}
}
- private void Update() => UpdateVisibility();
+ private void Update()
+ {
+ UpdateVisibility();
+ UpdateEmbeddedReveal();
+ UpdateXRayReveal();
+ }
// Per-client visibility: each machine decides whether to render THIS avatar's body from the LOCAL
// viewer's perspective (hunter bodies are always visible; only hider bodies are ever hidden). A found
@@ -286,6 +305,50 @@ public class NetworkPlayerSetup : NetworkBehaviour
return true;
}
+ // Owner-only. Publishes whether this hider is buried too deep to the replicated flag every client reads.
+ // Only a Hider avatar during a running match qualifies (the anti-burrow reveal targets hiders); the meter
+ // runs owner-side only, so this is where EmbeddedPercent is available. Cheap: a bool compare, one network
+ // write only on an actual edge (NetworkVariable dirties on assignment, so guard against no-op writes).
+ private void UpdateEmbeddedReveal()
+ {
+ if (!IsOwner) return;
+
+ bool over = false;
+ var match = MatchManager.Instance;
+ if (match && match.MatchActive && role == PlayerRole.Hider && !_isFound.Value &&
+ _concealment && _concealment.HasResult)
+ over = _concealment.EmbeddedPercent >= match.EmbeddedRevealPercent;
+
+ if (over != _embeddedReveal.Value) _embeddedReveal.Value = over;
+ }
+
+ // Per-client. Switches this avatar's see-through-walls X-ray glow on/off by driving the material's
+ // _XRayColor alpha (via MeshPainter). Two independent triggers, OR-combined: the replicated embedded
+ // flag (a buried hider, from its owner) and the local resolution phase (every un-found hider is revealed
+ // to everyone at the end). Change-guarded so the material is only touched on an actual transition.
+ private void UpdateXRayReveal()
+ {
+ if (!_painter) return;
+ bool reveal = ComputeXRay();
+ if (_xrayApplied && reveal == _lastXRay) return;
+ _painter.XRayRevealed = reveal;
+ _lastXRay = reveal;
+ _xrayApplied = true;
+ }
+
+ // The X-ray reveal predicate for this avatar on the local machine.
+ private bool ComputeXRay()
+ {
+ // Buried-hider reveal: already gated to hider + running match on the owner before it is published.
+ if (_embeddedReveal.Value) return true;
+
+ // Resolution reveals every remaining (un-found) hider through walls, on every client.
+ var match = MatchManager.Instance;
+ return match && match.MatchActive &&
+ match.LocalSubPhase == MatchManager.MatchSubPhase.Resolution &&
+ role == PlayerRole.Hider && !_isFound.Value;
+ }
+
// True if THIS machine's local player is a Hunter role variant (so it must not see hiders during hide).
private static bool LocalViewerIsHunter()
{
diff --git a/Assets/Settings/Mobile_Renderer.asset b/Assets/Settings/Mobile_Renderer.asset
index ea246b2..a201535 100644
--- a/Assets/Settings/Mobile_Renderer.asset
+++ b/Assets/Settings/Mobile_Renderer.asset
@@ -13,11 +13,9 @@ MonoBehaviour:
m_Name: Mobile_Renderer
m_EditorClassIdentifier:
debugShaders:
- debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7,
- type: 3}
+ debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
- probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959,
- type: 3}
+ probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3}
probeVolumeResources:
probeVolumeDebugShader: {fileID: 0}
probeVolumeFragmentationDebugShader: {fileID: 0}
@@ -26,11 +24,15 @@ MonoBehaviour:
probeSamplingDebugMesh: {fileID: 0}
probeSamplingDebugTexture: {fileID: 0}
probeVolumeBlendStatesCS: {fileID: 0}
- m_RendererFeatures: []
- m_RendererFeatureMap:
+ m_RendererFeatures:
+ - {fileID: 8698145602227116336}
+ m_RendererFeatureMap: 30995f295705b678
m_UseNativeRenderPass: 1
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
- m_AssetVersion: 2
+ m_AssetVersion: 3
+ m_PrepassLayerMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
m_OpaqueLayerMask:
serializedVersion: 2
m_Bits: 4294967295
@@ -48,5 +50,50 @@ MonoBehaviour:
m_RenderingMode: 0
m_DepthPrimingMode: 0
m_CopyDepthMode: 0
+ m_DepthAttachmentFormat: 0
+ m_DepthTextureFormat: 0
m_AccurateGbufferNormals: 0
m_IntermediateTextureMode: 0
+--- !u!114 &8698145602227116336
+MonoBehaviour:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 6b3d386ba5cd94485973aee1479b272e, type: 3}
+ m_Name: XRay Reveal (through walls)
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.RenderObjects
+ m_Active: 1
+ settings:
+ passTag: XRayReveal
+ Event: 300
+ filterSettings:
+ RenderQueueType: 0
+ LayerMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ PassNames:
+ - XRayReveal
+ overrideMaterial: {fileID: 0}
+ overrideMaterialPassIndex: 0
+ overrideShader: {fileID: 0}
+ overrideShaderPassIndex: 0
+ overrideMode: 0
+ overrideDepthState: 0
+ depthCompareFunction: 4
+ enableWrite: 1
+ stencilSettings:
+ overrideStencilState: 0
+ stencilReference: 0
+ stencilCompareFunction: 8
+ passOperation: 0
+ failOperation: 0
+ zFailOperation: 0
+ cameraSettings:
+ overrideCamera: 0
+ restoreCamera: 1
+ offset: {x: 0, y: 0, z: 0, w: 0}
+ cameraFieldOfView: 60
diff --git a/Assets/Settings/PC_Renderer.asset b/Assets/Settings/PC_Renderer.asset
index ce55f95..27c4c9d 100644
--- a/Assets/Settings/PC_Renderer.asset
+++ b/Assets/Settings/PC_Renderer.asset
@@ -26,7 +26,8 @@ MonoBehaviour:
probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, type: 3}
m_RendererFeatures:
- {fileID: 7833122117494664109}
- m_RendererFeatureMap: ad6b866f10d7b46c
+ - {fileID: 6245067092740157231}
+ m_RendererFeatureMap: ad6b866f10d7b46c2f2f42dd6eefaa56
m_UseNativeRenderPass: 1
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
m_AssetVersion: 3
@@ -54,6 +55,49 @@ MonoBehaviour:
m_DepthTextureFormat: 0
m_AccurateGbufferNormals: 0
m_IntermediateTextureMode: 0
+--- !u!114 &6245067092740157231
+MonoBehaviour:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 6b3d386ba5cd94485973aee1479b272e, type: 3}
+ m_Name: XRay Reveal (through walls)
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.RenderObjects
+ m_Active: 1
+ settings:
+ passTag: XRayReveal
+ Event: 300
+ filterSettings:
+ RenderQueueType: 0
+ LayerMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ PassNames:
+ - XRayReveal
+ overrideMaterial: {fileID: 0}
+ overrideMaterialPassIndex: 0
+ overrideShader: {fileID: 0}
+ overrideShaderPassIndex: 0
+ overrideMode: 0
+ overrideDepthState: 0
+ depthCompareFunction: 4
+ enableWrite: 1
+ stencilSettings:
+ overrideStencilState: 0
+ stencilReference: 0
+ stencilCompareFunction: 8
+ passOperation: 0
+ failOperation: 0
+ zFailOperation: 0
+ cameraSettings:
+ overrideCamera: 0
+ restoreCamera: 1
+ offset: {x: 0, y: 0, z: 0, w: 0}
+ cameraFieldOfView: 60
--- !u!114 &7833122117494664109
MonoBehaviour:
m_ObjectHideFlags: 0
diff --git a/Assets/Shaders/PlayerCamo.hlsl b/Assets/Shaders/PlayerCamo.hlsl
index 7b5bcc7..48d54a5 100644
--- a/Assets/Shaders/PlayerCamo.hlsl
+++ b/Assets/Shaders/PlayerCamo.hlsl
@@ -51,6 +51,12 @@ half _HeightNormalIntensity;
half _OcclusionThreshold;
float4 _OcclusionMap_TexelSize;
+// X-Ray reveal glow (rgb = HDR color, a = fade/opacity master). Consumed here for the VISIBLE parts;
+// the separate XRayOverlay pass (SRPDefaultUnlit) covers the OCCLUDED parts. See CamoForwardFragment.
+half4 _XRayColor;
+half _XRayPulseSpeed; // 0 = steady; otherwise breathing pulse via _Time (see CamoForwardFragment)
+half _XRayPulseStrength; // modulation depth of the pulse around the authored intensity
+
// Remaps the painted occlusion/height value into surface AO + a relief normal. Runs after URP's
// InitializeStandardLitSurfaceData (uv is already parallax-displaced) and before InitializeInputData,
// so the relief normal flows into inputData.normalWS through the tangent frame.
@@ -285,6 +291,18 @@ void CamoForwardFragment(
half4 color = UniversalFragmentCamo(inputData, surfaceData);
color.rgb = MixFog(color.rgb, inputData.fogCoord);
+
+ // X-Ray reveal glow on the VISIBLE parts. URP draws the SRPDefaultUnlit XRayOverlay pass BEFORE
+ // this UniversalForward pass (see DrawObjectsPass shader-tag order), so that pass alone can only
+ // survive on OCCLUDED pixels - this opaque forward write overwrites the visible ones. Adding the
+ // same additive term here reveals the glow on the visible parts too. Visible (here) and occluded
+ // (overlay, ZTest Greater) are mutually exclusive by depth => no double contribution. Added
+ // post-fog to match the overlay, which outputs the raw color.
+ // Breathing pulse (sin over _Time) on the glow master, matching the XRayOverlay pass exactly so the
+ // visible and occluded halves stay in phase. Speed 0 => term is 1 => identical to no pulse.
+ half xrayPulse = 1.0h + sin(_Time.y * _XRayPulseSpeed) * _XRayPulseStrength;
+ color.rgb += _XRayColor.rgb * (_XRayColor.a * xrayPulse);
+
color.a = OutputAlpha(color.a, IsSurfaceTypeTransparent(_Surface));
outColor = color;
diff --git a/Assets/Shaders/PlayerCamoLit.shader b/Assets/Shaders/PlayerCamoLit.shader
index acea0de..eec5ba3 100644
--- a/Assets/Shaders/PlayerCamoLit.shader
+++ b/Assets/Shaders/PlayerCamoLit.shader
@@ -56,6 +56,17 @@ Shader "Custom/PlayerCamoLit"
[HDR] _EmissionColor("Color", Color) = (0,0,0)
_EmissionMap("Emission", 2D) = "white" {}
+ // Second, "see-through-walls" emission color for the XRayOverlay pass (additive reveal glow).
+ // HDR rgb = glow color, alpha = fade/opacity master. Default (0,0,0,0) => additive term is
+ // zero, so the avatar looks unchanged until something fades this in at runtime.
+ [HDR] _XRayColor("X-Ray Overlay Color", Color) = (0,0,0,0)
+
+ // Optional breathing pulse for the X-ray glow (driven by _Time). Speed 0 => steady, identical to
+ // no pulse; strength = modulation depth around the authored intensity. Applied identically to the
+ // visible (ForwardLit) and occluded (XRayOverlay) halves so they stay in phase.
+ _XRayPulseSpeed("X-Ray Pulse Speed", Float) = 3
+ _XRayPulseStrength("X-Ray Pulse Strength", Range(0,1)) = 0.5
+
_DetailMask("Detail Mask", 2D) = "white" {}
_DetailAlbedoMapScale("Scale", Range(0.0, 2.0)) = 1.0
_DetailAlbedoMap("Detail Albedo x2", 2D) = "linearGrey" {}
@@ -197,6 +208,96 @@ Shader "Custom/PlayerCamoLit"
ENDHLSL
}
+ // ------------------------------------------------------------------
+ // X-Ray overlay - the OCCLUDED half of "see the player anywhere". This pass draws the additive
+ // reveal glow only where the avatar is HIDDEN behind geometry (ZTest Greater). The VISIBLE half
+ // is added directly in the ForwardLit fragment (PlayerCamo.hlsl, _XRayColor); visible (forward)
+ // and occluded (this pass) are mutually exclusive by depth => no double glow. ZWrite Off so it
+ // never disturbs depth.
+ //
+ // IMPORTANT - dispatch: the LightMode is the CUSTOM tag "XRayReveal", NOT a URP built-in, so
+ // URP's default opaque DrawObjectsPass never draws it. Instead a "Render Objects" renderer
+ // feature (on PC_Renderer / Mobile_Renderer, event AfterRenderingOpaques, Pass Names =
+ // ["XRayReveal"]) dispatches it AFTER all opaque geometry has written depth. That is what makes
+ // ZTest Greater robust: the whole depth buffer is populated, so occlusion is correct regardless
+ // of opaque sort order. The earlier SRPDefaultUnlit variant drew DURING the opaque phase and was
+ // sort-order dependent - if the occluder had not written depth yet, Greater failed and the glow
+ // vanished depending on camera angle/distance. => This pass RELIES on that renderer feature; drop
+ // the feature and the occluded glow won't render.
+ //
+ // Off by default (_XRayColor alpha 0 => additive term zero); a runtime driver fades the color in
+ // during the resolution phase. Plain color property, no keyword => nothing for the stripper to drop.
+ Pass
+ {
+ Name "XRayOverlay"
+ Tags
+ {
+ "LightMode" = "XRayReveal"
+ }
+
+ // -------------------------------------
+ // Render State Commands
+ Blend SrcAlpha One // additive: framebuffer += alpha * rgb
+ ZWrite Off
+ ZTest Greater // occluded parts only; the visible parts glow from the forward pass
+ Cull[_Cull]
+
+ HLSLPROGRAM
+ #pragma target 2.0
+
+ // -------------------------------------
+ // Shader Stages
+ #pragma vertex XRayOverlayVertex
+ #pragma fragment XRayOverlayFragment
+
+ //--------------------------------------
+ // GPU Instancing
+ #pragma multi_compile_instancing
+
+ #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
+
+ // Declared outside UnityPerMaterial like the camo knobs (this material already breaks the
+ // SRP batcher, see PlayerCamo.hlsl), so there is no CBUFFER-layout match to keep here.
+ half4 _XRayColor;
+ half _XRayPulseSpeed; // 0 = steady; otherwise breathing pulse via _Time (same math as the forward half)
+ half _XRayPulseStrength; // modulation depth of the pulse around the authored intensity
+
+ struct XRayAttributes
+ {
+ float4 positionOS : POSITION;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ };
+
+ struct XRayVaryings
+ {
+ float4 positionCS : SV_POSITION;
+ UNITY_VERTEX_INPUT_INSTANCE_ID
+ UNITY_VERTEX_OUTPUT_STEREO
+ };
+
+ XRayVaryings XRayOverlayVertex(XRayAttributes input)
+ {
+ XRayVaryings output = (XRayVaryings)0;
+ UNITY_SETUP_INSTANCE_ID(input);
+ UNITY_TRANSFER_INSTANCE_ID(input, output);
+ UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
+ output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
+ return output;
+ }
+
+ half4 XRayOverlayFragment(XRayVaryings input) : SV_Target
+ {
+ UNITY_SETUP_INSTANCE_ID(input);
+ UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
+ // Flat additive glow: HDR rgb is the reveal color, alpha the fade/opacity master.
+ // Breathing pulse (sin over _Time) on the alpha master; kept in phase with the forward
+ // half via the shared _Time and identical math. Speed 0 => term is 1 => steady glow.
+ half xrayPulse = 1.0h + sin(_Time.y * _XRayPulseSpeed) * _XRayPulseStrength;
+ return half4(_XRayColor.rgb, _XRayColor.a * xrayPulse);
+ }
+ ENDHLSL
+ }
+
Pass
{
Name "ShadowCaster"