Shader "Custom/LiminalPerception" { // Fullscreen "altered perception" pass (Unabomber): breathing zoom, melting peripheral warp, // liquid ripples, rotating color split, colored double vision, flowing iridescent hue waves, // saturation swells, colored light leaks and a soft halation. // The effects work on color and shape rather than brightness, so the image doesn't wash out. // Photosensitivity: every temporal variation is slow (periods of several seconds) and the // overall brightness stays stable - no flashes, no strobing, no flickering pattern. Properties { [Header(Global)] _Weight ("Weight (fade in or out)", Range(0, 1)) = 0 _Swell ("Swell (driven by script)", Range(0, 1)) = 0 [Header(Breathing)] _Breath ("Breathing Zoom", Range(0, 0.05)) = 0.018 _BreathPeriod ("Breathing Period (s)", Range(3, 20)) = 7 [Header(Warp)] _Warp ("Warp Amount", Range(0, 0.05)) = 0.016 _WarpScale ("Warp Scale", Range(0.5, 8)) = 2.2 _WarpSpeed ("Warp Speed", Range(0, 1)) = 0.12 _Swirl ("Peripheral Swirl", Range(0, 0.1)) = 0.035 _Ripple ("Liquid Ripples", Range(0, 0.02)) = 0.004 _RippleFreq ("Ripple Frequency", Range(1, 40)) = 14 [Header(Color Split and Double Vision)] _Prism ("Color Split", Range(0, 0.03)) = 0.006 _Ghost ("Colored Double Vision", Range(0, 1)) = 0.35 _GhostOffset ("Double Vision Offset", Range(0, 0.05)) = 0.014 [Header(Iridescence)] _Hue ("Hue Waves (radians)", Range(0, 3)) = 0 _HueScale ("Hue Wave Scale", Range(0.5, 10)) = 3 _HueSpeed ("Hue Wave Speed", Range(0, 1)) = 0.25 _Saturation ("Saturation Boost", Range(0, 2)) = 0 [Header(Halation)] _Glow ("Glow Intensity", Range(0, 2)) = 0.3 _GlowThreshold ("Glow Threshold", Range(0, 1)) = 0.6 _GlowRadius ("Glow Radius", Range(0, 0.05)) = 0.018 _GlowTint ("Glow Tint", Color) = (0.75, 0.95, 0.95, 1) [Header(Colored Light Leaks)] _Leak ("Light Leak Intensity", Range(0, 1)) = 0.3 _LeakColorA ("Light Leak Color A", Color) = (0.25, 0.55, 0.6, 1) _LeakColorB ("Light Leak Color B", Color) = (0.7, 0.42, 0.22, 1) [Header(Tunnel)] _EdgeBlur ("Peripheral Blur", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Pass { Name "LiminalPerception" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" float _Weight; float _Swell; float _Breath; float _BreathPeriod; float _Warp; float _WarpScale; float _WarpSpeed; float _Swirl; float _Ripple; float _RippleFreq; float _Prism; float _Ghost; float _GhostOffset; float _Hue; float _HueScale; float _HueSpeed; float _Saturation; float _Glow; float _GlowThreshold; float _GlowRadius; float4 _GlowTint; float _Leak; float4 _LeakColorA; float4 _LeakColorB; float _EdgeBlur; float Hash(float2 p) { return frac(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); } float SmoothNoise(float2 p) { float2 i = floor(p); float2 f = frac(p); f = f * f * (3.0 - 2.0 * f); float a = Hash(i); float b = Hash(i + float2(1, 0)); float c = Hash(i + float2(0, 1)); float d = Hash(i + float2(1, 1)); return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y); } half3 Sample(float2 uv) { return SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv).rgb; } // Rotates the hue around the grey axis (Rodrigues), keeping the brightness close half3 HueRotate(half3 color, float angle) { const float3 k = 0.57735027; float c = cos(angle); float s = sin(angle); return color * c + cross(k, color) * s + k * dot(k, color) * (1.0 - c); } half4 Frag(Varyings input) : SV_Target { float2 uv = input.texcoord; if (_Weight <= 0.001) return half4(Sample(uv), 1.0); float t = _Time.y; float amount = _Weight * lerp(0.55, 1.0, _Swell); float aspect = _ScreenParams.x / _ScreenParams.y; float2 centered = uv - 0.5; float radius = length(centered * float2(aspect, 1.0)); float edge = saturate(radius * 1.35); edge *= edge; // ── Breathing: slow radial zoom, stronger in the periphery ── float breath = sin(t * 6.2831853 / _BreathPeriod) * _Breath * amount; float2 p = 0.5 + centered * (1.0 - breath * (0.35 + edge)); // ── Melting warp: two slow noise layers, the periphery melts more ── float2 noiseUV = p * _WarpScale; float2 warp = float2( SmoothNoise(noiseUV + float2(t * _WarpSpeed, 3.1)), SmoothNoise(noiseUV * 1.31 + float2(11.7, -t * _WarpSpeed * 0.8))) - 0.5; p += warp * _Warp * amount * (0.3 + edge * 1.4); // ── Liquid ripples crossing the view ── p.x += sin(p.y * _RippleFreq + t * 0.7) * _Ripple * amount; p.y += sin(p.x * _RippleFreq * 0.73 - t * 0.53) * _Ripple * 0.6 * amount; // ── Slow swirl of the periphery around the view center ── float angle = sin(t * 0.11) * _Swirl * amount * edge; float2 fromCenter = p - 0.5; float s = sin(angle), c = cos(angle); p = 0.5 + float2(fromCenter.x * c - fromCenter.y * s, fromCenter.x * s + fromCenter.y * c); // ── Color split: RGB pulled apart along a slowly rotating direction ── float2 splitDir = float2(cos(t * 0.15), sin(t * 0.15)); float2 split = splitDir * _Prism * amount * (0.35 + edge * 1.2); split.x /= aspect; half3 col; col.r = Sample(p + split).r; col.g = Sample(p).g; col.b = Sample(p - split).b; // ── Peripheral blur (tunnel vision) ── float2 blurStep = float2(1.0 / aspect, 1.0) * 0.004 * amount * edge; half3 blurred = (Sample(p + blurStep) + Sample(p - blurStep) + Sample(p + float2(blurStep.x, -blurStep.y)) + Sample(p + float2(-blurStep.x, blurStep.y))) * 0.25; col = lerp(col, blurred, _EdgeBlur * amount * edge); // ── Colored double vision: an orbiting ghost with shifted hue, mixed (not added) ── float2 ghostOffset = float2(cos(t * 0.21), sin(t * 0.17)) * _GhostOffset * amount; ghostOffset.x /= aspect; half3 ghost = HueRotate(Sample(p + ghostOffset), 2.1 + sin(t * 0.09)); col = lerp(col, ghost, _Ghost * amount * 0.5); // ── Iridescent hue waves flowing slowly through the image ── float hueWave = sin(p.x * _HueScale + t * _HueSpeed) + sin(p.y * _HueScale * 1.37 - t * _HueSpeed * 0.71) + (SmoothNoise(p * _HueScale * 0.6 + t * _HueSpeed * 0.3) - 0.5) * 2.0; col = HueRotate(col, hueWave * _Hue * amount * 0.4); // ── Saturation swells ── half luma = dot(col, half3(0.299, 0.587, 0.114)); col = max(lerp(luma.xxx, col, 1.0 + _Saturation * amount), 0.0); // ── Halation: soft glow bleeding from the brightest areas only ── float glowRadius = _GlowRadius * (1.0 + 0.25 * sin(t * 0.37)); half3 glow = 0; [unroll] for (int i = 0; i < 8; i++) { float a = i * 0.7853982 + t * 0.05; float2 o = float2(cos(a) / aspect, sin(a)) * glowRadius; glow += max(Sample(p + o) - _GlowThreshold, 0.0); } col += glow * 0.125 * _GlowTint.rgb * _Glow * amount; // ── Colored light leaks drifting slowly: they tint, they don't lift the blacks ── float2 leakA = float2(0.5 + 0.65 * sin(t * 0.047), 0.5 + 0.55 * cos(t * 0.039)); float2 leakB = float2(0.5 + 0.65 * cos(t * 0.031 + 2.0), 0.5 + 0.55 * sin(t * 0.053 + 1.0)); float2 dA = (uv - leakA) * float2(aspect, 1.0); float2 dB = (uv - leakB) * float2(aspect, 1.0); float maskA = exp(-dot(dA, dA) * 2.0) * _Leak * amount; float maskB = exp(-dot(dB, dB) * 2.0) * _Leak * amount; col *= lerp(1.0, _LeakColorA.rgb * 1.8, maskA); col *= lerp(1.0, _LeakColorB.rgb * 1.8, maskB); return half4(col, 1.0); } ENDHLSL } } }