Shader "Ichni/UI/Contour Flow" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1, 1, 1, 1) [Header(Organic Motion)] _FlowScale ("Flow Scale", Range(0.5, 12)) = 4.0 _FlowSpeed ("Flow Speed", Range(-2, 2)) = 0.18 _FlowStrength ("Flow Strength", Range(0, 0.03)) = 0.004 _RippleCenter ("Ripple Center", Vector) = (0.38, 0.48, 0, 0) _RippleRadius ("Ripple Radius", Range(0.05, 1.5)) = 0.9 _RippleFrequency ("Ripple Frequency", Range(1, 80)) = 32 _RippleSpeed ("Ripple Speed", Range(-10, 10)) = 0.7 _RippleStrength ("Ripple Strength", Range(0, 0.03)) = 0.003 [Header(Contour Energy)] _LineColor ("Line Color", Color) = (0.36, 0.58, 1, 1) _AccentColor ("Pulse Accent", Color) = (0.64, 0.38, 1, 1) _LineThreshold ("Line Threshold", Range(0, 1)) = 0.17 _LineSoftness ("Line Softness", Range(0.001, 0.5)) = 0.14 _LineBoost ("Line Boost", Range(0, 2)) = 0.38 _PulseSpeed ("Pulse Speed", Range(-2, 2)) = 0.08 _PulseWidth ("Pulse Width", Range(0.02, 0.48)) = 0.14 _PulseIntensity ("Pulse Intensity", Range(0, 3)) = 0.9 _BreathSpeed ("Breath Speed", Range(0, 4)) = 0.55 _EchoDistance ("Colored Echo Distance", Range(0, 0.02)) = 0.002 _EchoStrength ("Colored Echo Strength", Range(0, 2)) = 0.22 [Header(Bottom Energy)] _BottomGlowColor ("Bottom Glow Color", Color) = (0.04, 0.2, 1, 1) _BottomGlowStrength ("Bottom Glow Strength", Range(0, 1)) = 0.14 _BottomGlowPower ("Bottom Glow Falloff", Range(0.5, 8)) = 3.2 [Header(UI)] _StencilComp ("Stencil Comparison", Float) = 8 _Stencil ("Stencil ID", Float) = 0 _StencilOp ("Stencil Operation", Float) = 0 _StencilWriteMask ("Stencil Write Mask", Float) = 255 _StencilReadMask ("Stencil Read Mask", Float) = 255 _ColorMask ("Color Mask", Float) = 15 [Toggle(UNITY_UI_CLIP_RECT)] _UseClipRect ("Use UI Clip Rect", Float) = 0 [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use UI Alpha Clip", Float) = 0 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "False" } Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] Blend SrcAlpha OneMinusSrcAlpha ColorMask [_ColorMask] Pass { Name "Contour Flow UI" CGPROGRAM #pragma target 3.0 #pragma vertex vert #pragma fragment frag #pragma multi_compile_local _ UNITY_UI_CLIP_RECT #pragma multi_compile_local _ UNITY_UI_ALPHACLIP #include "UnityCG.cginc" #include "UnityUI.cginc" struct appdata_t { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float4 texcoord : TEXCOORD0; float4 worldPosition : TEXCOORD1; half4 mask : TEXCOORD2; UNITY_VERTEX_OUTPUT_STEREO }; sampler2D _MainTex; float4 _MainTex_ST; float4 _MainTex_TexelSize; fixed4 _Color; fixed4 _TextureSampleAdd; float4 _ClipRect; float _UIMaskSoftnessX; float _UIMaskSoftnessY; float _FlowScale; float _FlowSpeed; float _FlowStrength; float4 _RippleCenter; float _RippleRadius; float _RippleFrequency; float _RippleSpeed; float _RippleStrength; fixed4 _LineColor; fixed4 _AccentColor; float _LineThreshold; float _LineSoftness; float _LineBoost; float _PulseSpeed; float _PulseWidth; float _PulseIntensity; float _BreathSpeed; float _EchoDistance; float _EchoStrength; fixed4 _BottomGlowColor; float _BottomGlowStrength; float _BottomGlowPower; v2f vert(appdata_t v) { v2f o; UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); o.worldPosition = v.vertex; o.vertex = UnityObjectToClipPos(o.worldPosition); o.texcoord.xy = TRANSFORM_TEX(v.texcoord, _MainTex); o.texcoord.zw = v.texcoord; o.color = v.color * _Color; float2 pixelSize = o.vertex.w; pixelSize /= abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy)); float4 clampedRect = clamp(_ClipRect, -2e10, 2e10); o.mask = half4( v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)) ); return o; } half PerceivedLuminance(half3 color) { #ifndef UNITY_COLORSPACE_GAMMA color = LinearToGammaSpace(color); #endif return dot(color, half3(0.2126, 0.7152, 0.0722)); } float2 BuildAnimatedUV(float2 uv, out float2 flowDirection, out float flowSignal) { float time = _Time.y; float aspect = max(_MainTex_TexelSize.z / max(_MainTex_TexelSize.w, 1.0), 0.001); float2 aspectPosition = uv - 0.5; aspectPosition.x *= aspect; float flowTime = time * _FlowSpeed; float flowX = sin(aspectPosition.y * _FlowScale + flowTime) + 0.45 * sin((aspectPosition.x + aspectPosition.y) * (_FlowScale * 0.73) - flowTime * 0.61); float flowY = cos(aspectPosition.x * (_FlowScale * 0.89) - flowTime * 0.83) + 0.45 * sin((aspectPosition.x - aspectPosition.y) * (_FlowScale * 0.57) + flowTime * 0.47); float2 flow = float2(flowX, flowY) / 1.45; float2 flowUV = flow; flowUV.x /= aspect; float2 rippleDelta = uv - _RippleCenter.xy; rippleDelta.x *= aspect; float rippleDistance = max(length(rippleDelta), 0.0001); float2 rippleDirection = rippleDelta / rippleDistance; float rippleEnvelope = 1.0 - smoothstep(_RippleRadius * 0.2, _RippleRadius, rippleDistance); float rippleWave = sin(rippleDistance * _RippleFrequency - time * _RippleSpeed); float2 rippleUV = rippleDirection; rippleUV.x /= aspect; float2 animatedUV = uv + flowUV * _FlowStrength + rippleUV * (rippleWave * rippleEnvelope * _RippleStrength); flowDirection = normalize(flowUV + rippleUV * (rippleWave * rippleEnvelope) + float2(0.001, 0.001)); flowSignal = (flowX + flowY + rippleWave * rippleEnvelope) * 0.3333; float2 halfTexel = _MainTex_TexelSize.xy * 0.5; return clamp(animatedUV, halfTexel, 1.0 - halfTexel); } fixed4 frag(v2f i) : SV_Target { float2 flowDirection; float flowSignal; float2 animatedUV = BuildAnimatedUV(i.texcoord.xy, flowDirection, flowSignal); half4 source = tex2D(_MainTex, animatedUV) + _TextureSampleAdd; half sourceLuminance = PerceivedLuminance(source.rgb); half echoDifference = 0; UNITY_BRANCH if (_EchoStrength > 0.0001 && _EchoDistance > 0.000001) { float2 halfTexel = _MainTex_TexelSize.xy * 0.5; float2 echoUV = clamp( animatedUV - flowDirection * _EchoDistance, halfTexel, 1.0 - halfTexel ); half echoLuminance = PerceivedLuminance(tex2D(_MainTex, echoUV).rgb); echoDifference = saturate(abs(echoLuminance - sourceLuminance) * 5.0); } half lineMask = smoothstep( _LineThreshold, _LineThreshold + max(_LineSoftness, 0.001), sourceLuminance ); float pulsePhase = frac( animatedUV.y * 0.78 + animatedUV.x * 0.22 + flowSignal * 0.035 - _Time.y * _PulseSpeed ); float pulseDistance = abs(pulsePhase - 0.5); float pulseFeather = max(fwidth(pulsePhase) * 1.5, 0.006); half pulse = 1.0 - smoothstep( _PulseWidth, _PulseWidth + pulseFeather, pulseDistance ); half breath = 0.8 + 0.2 * sin( _Time.y * _BreathSpeed + sourceLuminance * 12.0 + flowSignal ); half lineEnergy = lineMask * breath * (0.35 + pulse * _PulseIntensity) * _LineBoost; half3 movingLineColor = lerp( _LineColor.rgb * _LineColor.a, _AccentColor.rgb * _AccentColor.a, pulse ); half echoEnergy = echoDifference * (0.35 + pulse * 0.65) * _EchoStrength; half bottomGlow = pow(saturate(1.0 - i.texcoord.w), _BottomGlowPower); bottomGlow *= _BottomGlowStrength * (0.9 + 0.1 * cos(_Time.y * _BreathSpeed * 0.6)); half3 enhancedColor = source.rgb + movingLineColor * lineEnergy + _AccentColor.rgb * (_AccentColor.a * echoEnergy) + _BottomGlowColor.rgb * (_BottomGlowColor.a * bottomGlow); fixed4 color; color.rgb = enhancedColor * i.color.rgb; color.a = source.a * i.color.a; #ifdef UNITY_UI_CLIP_RECT half2 clipMask = saturate( (_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw ); color.a *= clipMask.x * clipMask.y; #endif #ifdef UNITY_UI_ALPHACLIP clip(color.a - 0.001); #endif return color; } ENDCG } } }