222 lines
9.9 KiB
GLSL
222 lines
9.9 KiB
GLSL
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
|
|
}
|
|
}
|
|
}
|