Files

229 lines
8.8 KiB
HLSL

#ifndef HUOSHAN_MEMORY_INCLUDED
#define HUOSHAN_MEMORY_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float4 _Color;
float _Saturation;
float _Brightness;
float _Contrast;
float _Vignette;
float _Noise;
float _SimGlitch;
float _HorizontalTear;
float _Impact;
float _Damage;
float _Overdrive;
float _TearSeed;
float4 _ColorTint;
float4 _CalmCenter;
float _CalmRadius;
float _CalmFeather;
float _EffectStrength;
float _Opacity;
float _AnalogGlitch;
float _AnalogScale;
float _AnalogChroma;
CBUFFER_END
float hash21(float2 p)
{
float3 p3 = frac(float3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.x + p3.y) * p3.z);
}
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.color = input.color * _Color;
return output;
}
half4 frag(Varyings input) : SV_Target
{
float2 centered = (input.uv - _CalmCenter.xy);
float calmDistance = length(centered);
float calmMask = 1.0 - smoothstep(
_CalmRadius,
_CalmRadius + max(0.001, _CalmFeather),
calmDistance);
calmMask *= step(0.001, _CalmRadius);
float effect = saturate(_EffectStrength) * (1.0 - calmMask);
// Small persistent simulation disturbance, independent of the impact path.
float sim = saturate(_SimGlitch) * (1.0 - calmMask * 0.85);
float impact = saturate(_Impact);
float damage = saturate(_Damage);
float overdrive = saturate(_Overdrive);
float distortion = saturate(max(effect, damage * 0.72 + impact * 0.84 + overdrive));
distortion *= 1.0 - calmMask;
// Each large word punches the memory closer before tearing it into horizontal slices.
// 撕裂/噪点布局只跟 TearSeed、冲击走,不用 _Time 连续翻页(LOG3 平移白噪)。
float zoom = 1.0 + impact * 0.10 + overdrive * 0.035;
float2 sampleUV = (input.uv - 0.5) / zoom + 0.5;
float rowCount = lerp(22.0, 68.0, overdrive);
float row = floor(input.uv.y * rowCount);
float tearStep = floor(_TearSeed * 97.0 + overdrive * 31.0);
float rowNoise = hash21(float2(row + _TearSeed * 43.0, tearStep));
float tearThreshold = lerp(
0.91,
0.36,
saturate(damage * 0.55 + impact * 0.75 + overdrive));
float tearGate = step(tearThreshold, rowNoise);
float tearDirection =
hash21(float2(row * 1.71 + _TearSeed * 19.0, tearStep * 0.83)) * 2.0 - 1.0;
float tearAmount =
_HorizontalTear * 0.08 +
damage * 0.026 +
impact * 0.060 +
overdrive * 0.082;
sampleUV.x += tearDirection * tearGate * tearAmount * distortion;
// 常驻失真是整幅画面的亚像素级投影漂移,不再让大块噪波在画面上流动。
float projectionTime = _Time.y;
float2 projectionWeave = float2(
sin(projectionTime * 0.73) + sin(projectionTime * 1.91 + 1.7) * 0.35,
sin(projectionTime * 0.57 + 2.4) + sin(projectionTime * 1.37) * 0.25);
sampleUV += projectionWeave * float2(0.00055, 0.00038) * sim;
// 偶发的行同步偏差只移动一条很窄的带,并随 sim 平方衰减。
// 默认状态几乎不可察觉,剧情脉冲抬高 _SimGlitch 时才会被读到。
float simSlot = floor(projectionTime * 1.4 + _TearSeed * 7.0);
float syncRow = hash21(float2(simSlot, 4.3));
float syncBand = 1.0 - smoothstep(
0.006,
0.018,
abs(input.uv.y - syncRow));
float syncGate = step(0.82, hash21(float2(simSlot, 8.1)));
float syncDirection = hash21(float2(simSlot, 12.7)) * 2.0 - 1.0;
sampleUV.x += syncDirection * syncBand * syncGate * 0.0035 * sim * sim;
sampleUV.y = (sampleUV.y - 0.5) *
(1.0 + impact * 0.016 + overdrive * 0.025 + sim * 0.008) + 0.5;
sampleUV = saturate(sampleUV);
half4 original = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
float chromaOffset =
(damage * 0.0035 + impact * 0.009 + overdrive * 0.016 + sim * 0.00065) *
lerp(0.30, 1.0, max(tearGate, sim * 0.35));
half red = SAMPLE_TEXTURE2D(
_MainTex,
sampler_MainTex,
saturate(sampleUV + float2(chromaOffset, 0))).r;
half4 displaced = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, sampleUV);
half blue = SAMPLE_TEXTURE2D(
_MainTex,
sampler_MainTex,
saturate(sampleUV - float2(chromaOffset, 0))).b;
float3 memorySample = float3(red, displaced.g, blue);
float luminance = dot(memorySample, float3(0.2126, 0.7152, 0.0722));
float3 graded = lerp(luminance.xxx, memorySample, _Saturation);
graded = (graded - 0.5) * _Contrast + 0.5;
graded *= _Brightness;
graded *= _ColorTint.rgb;
// At the hit, collapse the busy memory into a near-uniform ink field so the
// white center word owns the frame. A little luminance remains as a ghost contour.
float flatten = saturate(impact * 0.82 + overdrive * 0.68);
float3 inkColor = float3(0.012, 0.035, 0.065);
float3 inkMemory = inkColor + luminance.xxx * lerp(0.13, 0.04, overdrive);
graded = lerp(graded, inkMemory, flatten);
float vignetteDistance = length(input.uv - 0.5) * 1.4142;
float vignette = 1.0 - smoothstep(0.38, 1.0, vignetteDistance) * _Vignette;
// 暗角对常驻 sim 也生效,保留“略暗一点”的记忆感;不再叠颗粒噪波。
graded *= lerp(1.0, vignette, max(distortion, sim * 0.85));
// dropout 仅过载瞬间;常驻不加任何噪波颗粒。
float dropoutRow = floor(input.uv.y * lerp(9.0, 21.0, overdrive));
float dropoutNoise = hash21(float2(
dropoutRow + _TearSeed * 61.0,
tearStep));
float dropoutGate = step(lerp(1.05, 0.78, overdrive), dropoutNoise);
float dropoutLevel = lerp(
0.18,
1.55,
hash21(float2(dropoutRow * 3.17, tearStep)));
graded *= lerp(1.0, dropoutLevel, dropoutGate * overdrive * 0.72);
float3 finalColor = lerp(original.rgb, graded, max(distortion, sim * 0.62));
// 常驻略压亮度,配合暗角形成记忆发暗,而不是靠噪波。
finalColor *= lerp(1.0, 0.90, sim * 0.55);
// ── 记忆放映层 ─────────────────────────────────────────────
// 只使用低频、全局或规则的信号变化。避免低频随机色块造成“油污”观感。
float analog = saturate(_AnalogGlitch) * (1.0 - calmMask * 0.85);
if (analog > 0.001)
{
float analogScale = max(0.25, _AnalogScale);
float2 analogTexel = float2(
max(fwidth(input.uv.x), 1e-7),
max(fwidth(input.uv.y), 1e-7));
float2 analogPixel = input.uv / analogTexel;
// 柔和扫描暗纹:只向下压亮度,且强度低于 1%,不会形成明亮条带。
float scanPeriodPx = 5.5 * analogScale;
float scanWave = 0.5 + 0.5 *
sin(analogPixel.y * 6.2831853 / scanPeriodPx);
float scanDarkening = (1.0 - scanWave) * 0.035 * analog;
finalColor *= 1.0 - scanDarkening;
// 旧投影灯泡的曝光呼吸作用于整幅画面,不生成局部斑块。
float exposureBreath =
sin(projectionTime * 1.13) * 0.55 +
sin(projectionTime * 2.71 + 1.2) * 0.20;
finalColor *= 1.0 + exposureBreath * 0.018 * analog;
// 亚像素色差只在水平方向分离红蓝通道;默认量级约为十分之一像素。
float chromaPixels = 0.42 * analog * saturate(_AnalogChroma);
float2 projectionChromaUV = float2(analogTexel.x * chromaPixels, 0.0);
half projectionRed = SAMPLE_TEXTURE2D(
_MainTex,
sampler_MainTex,
saturate(sampleUV + projectionChromaUV)).r;
half projectionBlue = SAMPLE_TEXTURE2D(
_MainTex,
sampler_MainTex,
saturate(sampleUV - projectionChromaUV)).b;
float chromaBlend = analog * saturate(_AnalogChroma) * 0.28;
finalColor.r = lerp(finalColor.r, projectionRed, chromaBlend);
finalColor.b = lerp(finalColor.b, projectionBlue, chromaBlend);
// 轻微压黑四角,建立“被放映出来”的取景边界。
float2 edge = abs(input.uv - 0.5) * 2.0;
float projectorEdge = smoothstep(0.68, 1.08, max(edge.x, edge.y));
finalColor *= 1.0 - projectorEdge * 0.055 * analog;
}
return half4(
finalColor * input.color.rgb,
original.a * input.color.a * _Opacity);
}
#endif // HUOSHAN_MEMORY_INCLUDED