@@ -1,25 +1,28 @@
#ifndef HUOSHAN_SCREEN_GLITCH_INCLUDED
#define HUOSHAN_SCREEN_GLITCH_INCLUDED
// 火山「屏幕」故障:不是电视雪花,而是"想说真心话却卡在喉咙里"的憋压感 。
// 火山「屏幕」故障:一台坏掉的电视 。
//
// 只有 6 个宏观旋钮,内部的十几个系数都从它们推导——不要再往外暴露单项参数 ,
// 上一版 40 个属性的教训是:可调 ≠ 能调。
// _Scale 效果尺度(屏幕小就调大,一切跟着变粗)
// _ColorAmount 颜色
// _Warp 撕裂位移
// _Chaos 错误感(块级损坏 + 突发时机 )
// _Grit 颗粒与扫描线
// 上一版的教训:把原画面整个换成调色板生成的彩虹信号,出来是"彩虹汤" ,
// 不是"电视坏了"。这版反过来——底下的画面永远保留,损坏全部叠在它上面,
// 语汇只用真实电视的故障:
// · 场同步失锁:整幅画面向上滚,接缝处一条消隐黑条,条附近画面横向弯折
// · 行同步误差:逐行细抖 + 成带的横向撕裂(带内亮度/色相跟着错)
// · 色度不稳:色相漂移 + 色度增益忽大忽小(那块绿会往黄/青方向晃 )
// · 雪花:静噪颗粒、行级雪花条;「掉台」时整屏塌成雪花再闪回
// · 拟真骨架:扫描线、隔行闪烁、亮点划痕、亮度泵、暗角
//
// 仍然只有 6 个宏观旋钮 + 3 个基调,内部系数全部从它们推导。
// 不要再往外暴露单项参数:可调 ≠ 能调。
// _Scale 效果尺度(屏幕小就调大,扫描线/雪花跟着变粗)
// _ColorDamage 色彩损伤(色相漂移 + 串色 + 彩色镶边)
// _Warp 撕裂 / 滚屏
// _Chaos 掉台频率(信号中断爆发)
// _Grit 雪花与扫描线
// _SurgeRate 情绪节奏
//
// 结构要点:
// 1. 一切先汇成一条「信号」标量:亮度 + 副载波 + 逐行相位误差 + 颗粒 + 块损坏 。
// 颜色由色散调色板对信号取值(连续渐变,不是纯 RGB 色块=不卡通)。
// 屏幕贴图那块均匀的绿只位移是看不见的,所以它由 _Desaturate 随强度退场。
// 2. 密度按屏幕像素计(fwidth 换算),乘 _Scale;屏幕物件很小时把 _Scale 调大即可,
// 不会因为 UV 空间频率被挤成一团细线。
// 3. 错误感来自「块级损坏」:随机时隙里整块整块地错位、换色、通道旋转,
// 而不是均匀连续地抖——均匀 = 规律 = 假。
// 密度一律按屏幕像素计(fwidth 换算)再乘 _Scale,屏幕物件很小时调大
// _Scale 即可,不会被 UV 空间频率挤成细线 。
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
@@ -45,14 +48,12 @@ CBUFFER_START(UnityPerMaterial)
float _GlitchIntensity ;
float _Speed ;
float _Scale ;
float _ColorAmount ;
float _ColorDamage ;
float _Warp ;
float _Chaos ;
float _Grit ;
float _SurgeRate ;
float _Dispersion ;
float _PalPhase ;
float _Desaturate ;
float _Ghost ;
float _BloomBoost ;
float _Vignette ;
CBUFFER_END
@@ -72,7 +73,7 @@ float hash21(float2 p)
return frac ( ( p3 . x + p3 . y ) * p3 . z ) ;
}
// 值噪声:正弦叠正弦永远是周期性壁纸, 需要非周期调制的地方一律用它。
// 值噪声:需要非周期调制的地方一律用它,正弦叠正弦永远是周期性壁纸 。
float vnoise ( float2 p )
{
float2 i = floor ( p ) ;
@@ -84,36 +85,31 @@ float vnoise(float2 p)
f . y ) ;
}
// 色散调色板:三通道相位错开地对同一条信号取值 。
// 相位差小 = 轻微色偏(很老的机器),接近 1/3 = 满彩虹。输出连续渐变,不会出现卡通纯色块 。
half3 AnalogPalette ( float sig )
// NTSC 味道的色彩不稳定都在 YIQ 空间做:Y 是亮度,IQ 是色度平面 。
// 色相漂移 = 旋转 IQ,色度增益不稳 = 缩放 IQ,亮度细节始终不动 。
float3 RGBToYIQ ( float3 c )
{
float3 ph = 6.2831853 * ( sig * 1.15 + float3 ( 0.0 , 1.0 , 2.0 ) * _Dispersion + _PalPhase ) ;
return 0.5 + 0.5 * cos ( ph ) ;
return float3 (
dot ( c , float3 ( 0.299 , 0.587 , 0.114 ) ) ,
dot ( c , float3 ( 0.596 , - 0.274 , - 0.322 ) ) ,
dot ( c , float3 ( 0.211 , - 0.523 , 0.312 ) ) ) ;
}
// 横向拖抹 + 通道分离:亮度信号被硬拽出拖尾。
half3 SampleSmeared ( float2 uv , float smear , float dir , float chroma )
float3 YIQToRGB ( float3 c )
{
half3 acc = 0 ;
float wsum = 0 ;
return float3 (
c . x + 0.956 * c . y + 0.621 * c . z ,
c . x - 0.272 * c . y - 0.647 * c . z ,
c . x - 1.106 * c . y + 1.703 * c . z ) ;
}
[ unroll ]
for ( int i = 0 ; i < 4 ; i + + )
{
float k = ( float ) i / 3.0 ;
float w = 1.0 - k * 0.72 ;
float2 p = uv - float2 ( smear * k * dir , 0 ) ;
half r = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , p + float2 ( chroma , 0 ) ) . r ;
half g = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , p ) . g ;
half b = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , p - float2 ( chroma , 0 ) ) . b ;
acc + = half3 ( r , g , b ) * w ;
wsum + = w ;
}
return acc / max ( wsum , 1 e - 4 ) ;
// 彩色镶边:R/B 通道横向拉开。串色最便宜也最"模拟信号"的一层。
half3 SampleFringed ( float2 uv , float fringe )
{
half r = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , uv + float2 ( fringe , 0 ) ) . r ;
half g = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , uv ) . g ;
half b = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex , uv - float2 ( fringe , 0 ) ) . b ;
return half3 ( r , g , b ) ;
}
Varyings HuoshanScreenGlitchVert ( Attributes input )
@@ -143,201 +139,134 @@ half4 HuoshanScreenGlitchFrag(Varyings input) : SV_Target
return clean ;
}
// ── 从 6 个旋钮推导内部系数 ─────────────────────────────
float scale = max ( 0.25 , _Scale ) ;
float ribbonPx = 90.0 * scale ; // 彩带周期
float scanPx = 8.0 * scale ; // 扫描线周期
float grainPx = 3.0 * scale ; // 颗粒格子
float rowPx = 14.0 * scale ; // 逐行相位误差 / 掉行的行高
float blockPx = 56.0 * scale ; // 块损坏的基准块高
float jagPx = 26.0 * scale ; // 锯齿周期
float tearAmt = 0.16 * _Warp ;
float jagAmt = 0.05 * _Warp ;
float smearAmt = 0.07 * _Warp ;
float squeezeAmt = 0.05 * _Warp ;
float splitAmt = 0.05 * _Warp ;
float blockShift = 0.11 * _Warp ;
float grainAmt = 0.16 * _Grit ;
float scanAmt = saturate ( 0.5 * _Grit ) ;
// 每个屏幕像素对应多少 UV。
float2 texel = float2 ( max ( fwidth ( uv . x ) , 1 e - 7 ) , max ( fwidth ( uv . y ) , 1 e - 7 ) ) ;
float2 pixelPos = uv / texel ;
float t = _Time . y * _Speed ;
// 模拟信号按"帧"刷新:雪花、行抖动、撕裂带都在帧粒度上重抽,
// 逐渲染帧刷新会快成一片糊,反而没有电视感。
float frameSeed = floor ( _Time . y * 18.0 * max ( 0.05 , _Speed ) ) ;
// ── 情绪泵:越用力越急促。build 慢慢积压,vent 一下泄掉,然后重来 。
float rate = max ( 0.02 , _SurgeRate ) * lerp ( 0.55 , 1.9 , intensity ) ;
float phase = _Time . y * rate ;
float cyc = frac ( phase ) ;
float beat = floor ( phase ) ;
float build = pow ( smoothstep ( 0.0 , 0.78 , cyc ) , 2.2 ) ;
float vent = 1.0 - smoothstep ( 0.80 , 0.96 , cyc ) ;
float surge = saturate ( build * vent ) ;
// ── 情绪节奏:呼吸式起伏,不给 0,保证 hold 住时画面仍在小幅挣扎 。
float rate = max ( 0.05 , _SurgeRate ) * lerp ( 0.7 , 1.7 , intensity ) ;
float ph = _Time . y * rate ;
float beat = floor ( ph ) ;
float cyc = frac ( ph ) ;
float breathe = vnoise ( float2 ( ph * 1.3 , 7.7 ) ) ;
float drive = intensity * ( 0.35 + 0.65 * breathe ) ;
// ── 卡喉 :随机某几拍把画面"憋住"——时间种子冻结、力度更大 。
float choke = step ( 1.0 - saturate ( 0.35 * intensity ) , hash11 ( beat * 7.31 + 0.17 ) ) ;
float timeSeed = lerp ( floor ( t * 10.0 ) , beat * 13.0 , choke ) ;
// ── 掉台 :随机某几拍信号短暂中断——画面塌成雪花,然后闪回 。
// 这是"坏掉"最有说服力的一下,频率由 _Chaos 控制。
float dropGate = step ( 1.0 - saturate ( _Chaos * ( 0.2 + 0.45 * intensity ) ) ,
hash11 ( beat * 5.13 + 0.71 ) ) ;
float drop = dropGate
* smoothstep ( 0.30 , 0.34 , cyc )
* ( 1.0 - smoothstep ( 0.58 , 0.66 , cyc ) ) ;
// 信号回来那一瞬电平过冲,交给场景 Bloom 推出去。
float recover = dropGate
* smoothstep ( 0.60 , 0.64 , cyc )
* ( 1.0 - smoothstep ( 0.64 , 0.80 , cyc ) ) ;
// 持续档位也一直在呼吸:不给 0,保证 hold 住时屏幕仍在小幅挣扎 。
float drive = intensity * lerp ( 0.42 , 1.0 , surge ) * lerp ( 1.0 , 1.3 , choke * build ) ;
// ── 场同步失锁:某几拍整幅画面向上滚一整圈 。
float rollGate = step ( 1.0 - saturate ( _Warp * ( 0.12 + 0.38 * intensity ) ) ,
hash11 ( beat * 3.77 + 1.31 ) ) ;
float rollPos = rollGate * cyc ;
float vJit = ( hash11 ( frameSeed * 0.713 ) - 0.5 ) * 0.006 * _Warp * drive ;
// ── 自下而上的行波:一口气往上顶。结构性的,跟着屏幕比例走 。
float waveCenter = frac ( - _Time . y * 0.45 * lerp ( 0.45 , 1.35 , intensity ) ) ;
float d = uv . y - waveCenter ;
d - = floor ( d + 0.5 ) ;
float waveEnv = exp ( - ( d * d ) / 0.0484 ) ;
waveEnv = saturate ( waveEnv + intensity * 0.5 ) ;
// 消隐黑条骑在滚动接缝上;条附近画面横向弯折(同步电路在挣扎) 。
float dSeam = uv . y - frac ( 1.0 - rollPos - vJit ) ;
dSeam - = floor ( dSeam + 0.5 ) ;
float barMask = rollGate * exp ( - dSeam * dSeam / 0.0009 ) ;
float skewDir = hash11 ( beat * 9.13 + 0.4 ) < 0.5 ? - 1.0 : 1.0 ;
float skew = rollGate * skewDir * exp ( - abs ( dSeam ) * 22.0 ) * 0.10 * _Warp ;
// ── 块级损坏:随机时隙里成片出错。这是"错误感"的来源——
// 均匀连续的抖动读作机器在正常地坏,成块的突发才读作数据出错。
float chaosSlot = floor ( _Time . y * 7.0 ) ;
float burst = step ( 1.0 - saturate ( _Chaos * 0.55 ) , hash11 ( chaosSlot * 3.71 + 0.5 ) ) ;
float chaosDrive = saturate ( drive * ( 0.3 + burst * 1.7 ) * _Chaos ) ;
// ── 行同步误差:逐行细抖。drive 平方压住低档,别一开就满屏毛刺。
float rowPx = 3.0 * scale ;
float row = floor ( pixelPos . y / max ( 1.0 , rowPx ) ) ;
float rowJit = ( hash21 ( float2 ( row , frameSeed ) ) - 0.5 )
* 0.005 * _Warp * ( drive * drive + drop * 2.0 ) ;
// 大块:宽而扁,像整段数据行被搬走。格子原点和大小每个时隙重抽——
// 固定网格上的块一眼就看出是"贴上去的"。
float sizeJit = lerp ( 0.7 , 1.7 , hash11 ( chaosSlot * 4.31 ) ) ;
float2 gridJit = float2 ( hash11 ( chaosSlot * 1.71 ) , hash11 ( chaosSlot * 2.93 ) ) * 17.0 ;
float2 cellA = floor ( float2 ( pixelPos . x / ( blockPx * 2.2 * sizeJit ) ,
pixelPos . y / ( blockPx * sizeJit ) ) + gridJit ) ;
float hitA = step ( 1.0 - chaosDrive * 0.9 , hash21 ( cellA + chaosSlot ) ) ;
float shiftA = ( hash11 ( dot ( cellA , float2 ( 7.31 , 13.17 ) ) + chaosSlot ) - 0.5 ) * 2.0
* blockShift * hitA ;
// 块内逐行错位:真数据损坏里块内部每行错得不一样,整块刚体平移读作贴图。
float innerRow = floor ( pixelPos . y / max ( 2.0 , rowPx * 0.5 ) ) ;
shiftA * = 0.55 + 0.9 * hash21 ( float2 ( innerRow , cellA . x * 3.1 + chaosSlot ) ) ;
// ── 撕裂带:带高每帧重抽,带内整段错位。等高带太机械。
float bandH = lerp ( 0.05 , 0.16 , hash11 ( frameSeed * 1.71 ) ) ;
float bandId = floor ( ( uv . y + hash11 ( frameSeed ) * 0.37 ) / bandH ) ;
float bandRand = hash21 ( float2 ( bandId , frameSeed ) ) ;
float bandOn = step ( 0.90 - 0.32 * saturate ( drive + drop ) , bandRand ) ;
float tear = bandOn * ( hash11 ( bandId * 13.7 + frameSeed ) - 0.5 ) * 2.0
* 0.09 * _Warp * ( 0.25 + drive + drop ) ;
// 小块:碎片,负责细节上的"啃咬"
float2 cellB = floor ( float2 ( pixelPos . x / ( blockPx * 0.55 ) , pixelPos . y / ( blockPx * 0.4 ) )
+ gridJit . yx ) ;
float hitB = step ( 1.0 - chaosDrive * 0.7 , hash21 ( cellB * 1.7 + chaosSlot * 2.3 ) ) ;
float shiftB = ( hash11 ( dot ( cellB , float2 ( 3.71 , 9.13 ) ) + chaosSlot ) - 0.5 ) * 2.0
* blockShift * 0.45 * hitB ;
// 掉台时整帧还会横向乱跳一下。
float xOff = rowJit + tear + skew
+ ( hash11 ( frameSeed * 3.31 ) - 0.5 ) * 0.08 * drop ;
float2 sampleUV = float2 ( uv . x + xOff , frac ( uv . y + rollPos + vJit ) ) ;
// ── 位移:锯齿 + 分带撕裂 + 块位移 + 整帧推挤 。
// 锯齿的频率和幅度都用噪声调制,等周期的全屏锯齿太规律。
float jagNoise = vnoise ( float2 ( uv . y * 3.0 , _Time . y * 1.3 ) ) ;
float zig = sin ( pixelPos . y * 6.2831853 / max ( 2.0 , jagPx * ( 0.6 + jagNoise ) ) + t * 3.0 ) ;
zig = sign ( zig ) * pow ( abs ( zig ) , 0.55 ) ;
float jag = zig * jagAmt * drive * waveEnv * ( 0.35 + 0.65 * jagNoise ) ;
// ── 采样:彩色镶边随损坏加深,撕裂带内额外拉开 。
float fringe = ( 0.0012 + 0.005 * drive + 0.015 * drop )
* _ColorDamage * ( 1.0 + bandOn * 1.5 ) ;
half3 col = SampleFringed ( sampleUV , fringe ) ;
// 撕裂带:带高每个时隙重抽,粗细两层叠加——固定 22 条等高带太机械 。
float bandScale = lerp ( 7.0 , 24.0 , hash11 ( floor ( timeSeed * 0.37 ) * 5.13 ) ) ;
float band = floor ( uv . y * bandScale + hash11 ( timeSeed ) * 4.0 ) ;
float bandRand = hash21 ( float2 ( band , timeSeed ) ) ;
float bandActive = step ( 1.0 - saturate ( drive * 0.85 ) , bandRand ) ;
float tear = ( hash11 ( band * 17.13 + timeSeed ) - 0.5 ) * 2.0
* tearAmt * drive * bandActive * ( 0.35 + waveEnv * 0.85 ) ;
// 重影:微弱的错位副本(天线反射的鬼影) 。
half3 ghost = SAMPLE_TEXTURE2D ( _MainTex , sampler_MainTex ,
sampleUV + float2 ( 0.025 + 0.03 * drive , 0 ) ) . rgb ;
col + = ghost * _Ghost * 0.22 * ( 0.5 + drive ) ;
// 细撕裂:几像素高的毛刺行,让撕裂边缘"起毛"而不是整齐的粗条 。
float bandF = floor ( uv . y * bandScale * 4.3 + hash11 ( timeSeed + 2.0 ) * 9.0 ) ;
float fineActive = step ( 1.0 - saturate ( drive * 0.5 ) , hash21 ( float2 ( bandF , timeSeed + 5.0 ) ) ) ;
tear + = ( hash11 ( bandF * 7.77 + timeSeed ) - 0.5 ) * 2.0
* tearAmt * 0.35 * drive * fineActive ;
// ── 撕裂带内亮度错档:带和带之间电平接不上,撕裂在平色区域也看得见 。
col * = 1.0 + bandOn * ( hash11 ( bandId * 7.7 + frameSeed ) - 0.4 )
* 0.6 * saturate ( drive + drop ) ;
float shove = ( hash11 ( beat + 0.5 ) - 0.5 ) * 2.0 * tearAmt * 0.3 * surge * intensity ;
// ── 色度不稳:色相慢漂 + 撕裂带内色相硬错 + 色度增益忽大忽小。
// 对着那块绿,这层就是"往黄/青方向晃、时浓时淡"。
float3 yiq = RGBToYIQ ( col ) ;
float hueAng = ( vnoise ( float2 ( _Time . y * 0.9 , row * 0.02 ) ) - 0.5 )
* 2.4 * _ColorDamage * drive ;
hueAng + = bandOn * ( bandRand - 0.5 ) * 1.4 * _ColorDamage * saturate ( drive + drop ) ;
float cs = cos ( hueAng ) ;
float sn = sin ( hueAng ) ;
yiq . yz = float2 ( yiq . y * cs - yiq . z * sn , yiq . y * sn + yiq . z * cs ) ;
yiq . yz * = 1.0 + ( vnoise ( float2 ( _Time . y * 1.7 , 13.1 ) ) - 0.5 )
* 1.6 * _ColorDamage * drive ;
col = max ( 0.0 , YIQToRGB ( yiq ) ) ;
// 隔行抖动:奇偶行反向的细小错位,CRT 失锁最便宜也最有效的一层 。
float odd = fmod ( floor ( pixelPos . y / max ( 1.0 , rowPx * 0.25 ) ) , 2.0 ) * 2.0 - 1.0 ;
float interlace = odd * 0.0045 * _Warp * drive * ( 0.3 + waveEnv * 0.7 ) ;
// ── 雪花:常驻底噪 + 行级雪花条;掉台时雪花吃掉整个画面 。
float grainPx = 1.6 * scale ;
float2 cell = floor ( pixelPos / max ( 1.0 , grainPx ) ) ;
float snowVal = hash21 ( cell + frameSeed * float2 ( 11.13 , 5.71 ) ) ;
float rowSnow = step ( 0.96 - 0.10 * drive , hash21 ( float2 ( row , frameSeed + 3.0 ) ) ) ;
float snowAmt = saturate (
saturate ( _Grit ) * ( 0.05 + 0.16 * drive + rowSnow * 0.45 * drive )
+ drop * 0.9 ) ;
col = lerp ( col , snowVal . xxx * lerp ( 0.75 , 1.1 , hash21 ( cell . yx + frameSeed ) ) , snowAmt ) ;
// 场滚动:泄压瞬间垂直保持失锁,整幅画面向上滑一口——电视坏掉 的标志性动作 。
float rollGate = step ( 0.7 , hash11 ( beat * 3.77 + 1.3 ) ) * intensity ;
float roll = rollGate * smoothstep ( 0.78 , 1.0 , cyc ) * 0.055 * _Warp ;
// ── 亮点划痕:细行上随机的短横白痕,磁带/信号损伤 的标志性亮斑 。
float fineRow = floor ( pixelPos . y / max ( 1.0 , 1.5 * scale ) ) ;
float segPx = 26.0 * scale ;
float seg = floor ( pixelPos . x / segPx ) ;
float spark = step ( 0.995 - 0.015 * drive * _Grit - 0.03 * drop ,
hash21 ( float2 ( fineRow * 1.7 + seg * 0.31 , frameSeed ) ) ) ;
col + = spark * ( 1.0 - frac ( pixelPos . x / segPx ) ) * 1.3 * saturate ( drive + drop ) ;
float squeeze = squeezeAmt * surge * intensity ;
float2 uvDist ;
uvDist . x = uv . x + tear + jag + shove + shiftA + shiftB + interlace ;
uvDist . y = 0.5 + ( uv . y - 0.5 ) * ( 1.0 - squeeze ) + roll ;
float2 pixelDist = uvDist / texel ;
// ── 消隐黑条压暗。
col * = 1.0 - barMask * 0.8 ;
float smearDir = ( hash11 ( band * 5.71 + timeSeed ) < 0.5 ) ? - 1.0 : 1.0 ;
float smear = smearAmt * drive * ( 0.3 + waveEnv * 1.0 ) * ( 0.4 + bandActive ) ;
float chromaSplit = splitAmt * drive * ( 0.4 + waveEnv * 1.05 ) * ( 1.0 + hitA * 2.0 ) ;
half3 tornSprite = SampleSmeared ( uvDist , smear , smearDir , chromaSplit ) ;
// ── 亮度信号。原本那块绿随强度退场,否则怎么撕都还是一片绿。
float luma = dot ( tornSprite , half3 ( 0.299 , 0.587 , 0.114 ) ) ;
float signalY = lerp ( luma , saturate ( ( luma - 0.5 ) * 1.6 + 0.5 ) , drive ) ;
// ── 颗粒:满屏细噪,是"被拍下来"最关键的一层,没有它就是矢量图。
float2 grainCell = floor ( pixelPos / max ( 0.5 , grainPx ) ) ;
float grain = ( hash21 ( grainCell + floor ( _Time . y * 24.0 ) * 0.137 ) - 0.5 ) * grainAmt ;
// ── 副载波:多个八度 + 逐行相位误差(真实模拟信号每一行相位都不同,
// 这是规则几何波形和真实故障的分界线)。相位按位移后的像素坐标取,
// 所以撕裂和块位移会自动把色带错开。
float scanRow = floor ( pixelPos . y / max ( 1.0 , rowPx ) ) ;
float rowPhase = ( hash21 ( float2 ( scanRow , timeSeed ) ) - 0.5 ) * 1.6 ;
// 纵向调制改用两个八度的值噪声:之前的 sin(y*14)+sin(y*37.8) 是固定周期,
// 出来的彩带像壁纸一样重复。每行的载波频率也随机抖,彩带宽度不再全屏一致。
float phNoise = vnoise ( float2 ( uvDist . y * 7.0 , _Time . y * 0.8 ) ) * 2.0
+ vnoise ( float2 ( uvDist . y * 23.0 + 11.0 , _Time . y * 1.6 ) ) ;
float rowFreq = 0.72 + 0.56 * hash21 ( float2 ( scanRow , floor ( _Time . y * 2.0 ) ) ) ;
float ribbon =
pixelDist . x * 6.2831853 / max ( 4.0 , ribbonPx * rowFreq )
+ ( phNoise - 1.5 ) * 3.2
+ rowPhase
+ waveEnv * 2.4
- _Time . y * 2.2 ;
float sI = sin ( ribbon ) ;
sI = sign ( sI ) * pow ( abs ( sI ) , 0.85 ) ;
// 高强度时把逐像素噪声灌进信号——出来是密集彩点而不是纯色方块。
float speckGate = saturate ( ( intensity - 0.5 ) / 0.5 ) * surge
* saturate ( bandActive * 0.75 + waveEnv * 0.55 ) ;
float2 speckCell = floor ( pixelDist / max ( 1.0 , grainPx * 2.0 ) ) ;
float speck = ( hash21 ( speckCell + timeSeed * 0.31 ) - 0.5 ) * 2.0 ;
float gain = _ColorAmount * drive * ( 0.55 + waveEnv * 0.8 ) ;
float sig = signalY + sI * gain + speck * 0.55 * speckGate + grain * 0.6 ;
// ── 亮度包络:暗处保持暗、亮处冲白,色带自身也有明暗起伏。
float envelope = saturate ( signalY * 1.15 + sI * gain * 0.35 + grain ) ;
envelope + = smoothstep ( 0.55 , 1.0 , envelope ) * 0.35 ; // 光晕溢出
half3 analog = AnalogPalette ( sig ) * envelope * 1.32 ;
// ── 混合:原色随强度退场,色散层顶上来。
half3 desatBase = lerp ( tornSprite , half3 ( signalY , signalY , signalY ) ,
saturate ( _Desaturate * drive * 1.35 ) ) ;
half3 col = lerp ( desatBase , analog , saturate ( drive * 1.35 ) ) ;
// ── 块损坏的上色:块必须带着底下的画面坏,整块换成无关颜色就是"贴纸"。
// 量化(马赛克色阶)+ 通道旋转 + 淡淡的调色板偏色,明暗仍来自原信号。
float recolorSeed = hash11 ( dot ( cellA , float2 ( 19.3 , 5.7 ) ) + chaosSlot * 1.3 ) ;
float post = lerp ( 5.0 , 2.0 , recolorSeed ) ;
half3 blockCol = floor ( col * post + 0.5 ) / post ;
blockCol = lerp ( blockCol , blockCol . gbr , step ( 0.6 , recolorSeed ) ) ;
blockCol = lerp ( blockCol , AnalogPalette ( recolorSeed * 3.0 + signalY ) * envelope ,
0.35 * saturate ( _Chaos ) ) ;
col = lerp ( col , blockCol , hitA * saturate ( _Chaos * 0.85 ) ) ;
// 小碎块直接旋转通道:硬色偏,参考图里那种突兀的品红/青块。
col = lerp ( col , col . brg , hitB * saturate ( _Chaos * 0.5 ) ) ;
// ── 掉行:黑色横缝与偶发白色爆行,行高跟着尺度走。
float blackRow = step ( 0.985 - chaosDrive * 0.14 , hash21 ( float2 ( scanRow , timeSeed + 3.0 ) ) ) ;
col * = 1.0 - blackRow * 0.9 ;
float whiteRow = step ( 0.996 - chaosDrive * 0.03 , hash21 ( float2 ( scanRow , timeSeed + 7.0 ) ) ) ;
col + = whiteRow * drive * 0.9 ;
// ── 扫描线:拟真感的骨架。低于约 2 像素分辨不出来只会闪,自动淡出。
// ── 扫描线:低于约 2 像素分辨不出来只会闪,自动淡出。
float scanPx = 4.0 * scale ;
float scanFade = saturate ( ( scanPx - 1.6 ) / 1.4 ) ;
float scanWave = 0.5 + 0.5 * sin ( pixelPos . y * 6.2831853 / max ( 1.0 , scanPx ) + _Time . y * 1.5 ) ;
col * = lerp ( 1.0 , scanWave , saturate ( scanAmt * scanFade ) ) ;
float scan = 0.5 + 0.5 * sin ( pixelPos . y * 6.2831853 / max ( 1.0 , scanPx ) ) ;
col * = lerp ( 1.0 , 0.78 + 0.30 * scan , saturate ( _Grit * 0.6 ) * scanFade ) ;
// 隔行闪烁:奇偶场亮度轮流差一点。
float parity = fmod ( floor ( _Time . y * 29.97 ) , 2.0 ) ;
float field = fmod ( floor ( pixelPos . y / max ( 1.0 , scanPx * 0.5 ) ) , 2.0 ) ;
col * = 1.0 - abs ( field - parity ) * 0.08 * saturate ( _Grit ) * drive ;
// ── 整体电平:亮度泵着走,掉台时电平跌,恢复瞬间过冲。
col * = 1.0 + ( vnoise ( float2 ( _Time . y * 2.3 , 31.7 ) ) - 0.5 ) * 0.3 * drive ;
col * = 1.0 - drop * 0.25 ;
col * = 1.0 + _BloomBoost * recover ;
// ── 暗角:照片感。
float2 vp = uv - 0.5 ;
col * = 1.0 - _Vignette * saturate ( dot ( vp , vp ) * 2.0 ) ;
// ── 波峰过曝:接现有 Bloom,把"顶到嗓子眼"那一下推出去。
float crest = waveEnv * surge * intensity ;
col * = 1.0 + _BloomBoost * crest ;
half4 result ;
result . rgb = max ( 0.0 , col ) * input . color . rgb ;
result . a = spriteAlpha ;