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"