Add see-through X-ray reveal overlay for players
Add a second, additive HDR emission color (_XRayColor) that reveals a player through walls as a pulsing energy glow, driven during matches: - Shader: XRayOverlay pass (ZTest Greater, additive) for occluded parts plus a matching additive term in the forward fragment for visible parts, so the glow shows everywhere and never double-blends. Exposes _XRayColor, _XRayPulseSpeed and _XRayPulseStrength for a breathing pulse. - Render it after opaques via a RenderObjects feature (custom XRayReveal light-mode tag) on both PC and Mobile renderers, so the through-wall test is depth-sort independent and no longer drops out at some camera angles/distances. - MeshPainter drives _XRayColor alpha through a framerate-independent fade (xrayFadeDuration) rather than a hard toggle. - Reveal is driven per avatar on every client: all remaining un-found hiders glow during the Resolution phase, and a hider whose ConcealmentMeter EmbeddedPercent exceeds MatchManager.embeddedRevealPercent glows via an owner-authoritative NetworkVariable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7104f9314
commit
c54e0cf502
8 files changed
+338
-13
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user