feat(huoshan): 添加 Sprite 噪波 Glitch 效果

Made-with: Cursor
This commit is contained in:
2026-03-13 22:07:36 +08:00
parent 421fc9944c
commit c2f4c0f83b
16 changed files with 989 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
using UnityEngine;
using UnityEditor;
using System.IO;
/// <summary>
/// 生成与 SpriteNoiseGlitch shader 三阶段对应的程序化音效。
/// 菜单: Tools > Generate Glitch Noise Audio
/// </summary>
public static class GlitchNoiseAudioGenerator
{
private const int SampleRate = 44100;
private const float Duration = 4f; // 每段音效时长(秒),便于循环使用
private const string OutputFolder = "Assets/RawResources/Audio/GlitchNoise";
[MenuItem("Tools/Generate Glitch Noise Audio")]
public static void Generate()
{
string dir = Path.Combine(Application.dataPath, "RawResources", "Audio", "GlitchNoise");
Directory.CreateDirectory(dir);
GenerateStage1(dir);
GenerateStage2(dir);
GenerateStage3(dir);
AssetDatabase.Refresh();
Debug.Log($"[GlitchNoise] 已生成三阶段音效至 Assets/RawResources/Audio/GlitchNoise");
}
/// <summary>Stage 1: 轻微静态噪点 - 柔和白噪声</summary>
private static void GenerateStage1(string dir)
{
int samples = (int)(SampleRate * Duration);
float[] data = new float[samples];
var rnd = new System.Random(12345);
float gain = 0.12f;
for (int i = 0; i < samples; i++)
{
data[i] = ((float)rnd.NextDouble() * 2f - 1f) * gain;
}
SaveWav(dir, "GlitchNoise_Stage1_Light.wav", data);
}
/// <summary>Stage 2: 中等 - 更多噪点 + 偶尔 glitch 爆音</summary>
private static void GenerateStage2(string dir)
{
int samples = (int)(SampleRate * Duration);
float[] data = new float[samples];
var rnd = new System.Random(23456);
float noiseGain = 0.22f;
int glitchInterval = SampleRate / 4; // 约每 0.25 秒一次 glitch 爆音
for (int i = 0; i < samples; i++)
{
float n = ((float)rnd.NextDouble() * 2f - 1f) * noiseGain;
// 随机 glitch 爆音
if (i > 0 && i % glitchInterval < 120)
{
float burst = ((float)rnd.NextDouble() * 2f - 1f) * 0.5f;
n += burst * (1f - (i % glitchInterval) / 120f);
}
data[i] = Mathf.Clamp(n, -1f, 1f);
}
SaveWav(dir, "GlitchNoise_Stage2_Medium.wav", data);
}
/// <summary>Stage 3: 严重 - 强烈噪点 + 频繁 glitch + 数字故障感</summary>
private static void GenerateStage3(string dir)
{
int samples = (int)(SampleRate * Duration);
float[] data = new float[samples];
var rnd = new System.Random(34567);
float noiseGain = 0.4f;
int blockSize = 2205; // 约 0.05 秒一块
int glitchBlockEvery = 4;
for (int i = 0; i < samples; i++)
{
int block = i / blockSize;
float n = ((float)rnd.NextDouble() * 2f - 1f) * noiseGain;
// 块状 glitch:整块随机反转/爆音
if (block % glitchBlockEvery == 0)
{
int posInBlock = i % blockSize;
float t = (float)posInBlock / blockSize;
n += ((float)rnd.NextDouble() * 2f - 1f) * 0.6f * (1f - t);
}
// 随机“卡顿”短静音后爆音
if (rnd.NextDouble() < 0.0003)
{
int silenceLen = 100 + rnd.Next(300);
int end = Mathf.Min(i + silenceLen, samples);
for (int j = i; j < end; j++)
{
data[j] = j == end - 1 ? ((float)rnd.NextDouble() * 2f - 1f) * 0.8f : 0f;
}
i = end - 1;
}
else
{
data[i] = Mathf.Clamp(n, -1f, 1f);
}
}
SaveWav(dir, "GlitchNoise_Stage3_Heavy.wav", data);
}
private static void SaveWav(string dir, string filename, float[] samples)
{
string path = Path.Combine(dir, filename);
using (var fs = new FileStream(path, FileMode.Create))
using (var bw = new BinaryWriter(fs))
{
// RIFF header
bw.Write(new[] { 'R', 'I', 'F', 'F' });
int dataSize = samples.Length * 2; // 16-bit
bw.Write(36 + dataSize);
bw.Write(new[] { 'W', 'A', 'V', 'E' });
// fmt chunk
bw.Write(new[] { 'f', 'm', 't', ' ' });
bw.Write(16); // chunk size
bw.Write((short)1); // PCM
bw.Write((short)1); // mono
bw.Write(SampleRate);
bw.Write(SampleRate * 2);
bw.Write((short)2);
bw.Write((short)16);
// data chunk
bw.Write(new[] { 'd', 'a', 't', 'a' });
bw.Write(dataSize);
foreach (float s in samples)
{
short sample = (short)Mathf.Clamp((int)(s * 32767), -32768, 32767);
bw.Write(sample);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd5ec77f7adcc584ebc901ac1f73fab6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+47
View File
@@ -0,0 +1,47 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: M_noiseScreen
m_Shader: {fileID: 4800000, guid: 505a54c499a926e419139bbed9fe5a81, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BlockSize: 12
- _ChromaticShift: 0.04
- _DisplaceStrength: 0.15
- _DropoutSize: 0.15
- _FlickerStrength: 0.4
- _GlitchIntensity: 0
- _NoiseBrightness: 1
- _NoiseDensity: 0.053
- _NoiseScale: 132
- _ScanlineCount: 80
- _ScanlineStrength: 0.3
- _Speed: 5
- _TearStrength: 0.3
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 77ae0e621bc487c439f7c6f9a41b08fe
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 94284a48ce2f0164a86f7f8a0c1b4859
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 324f307b690f8a546a58256f6a8b3117
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 9f4ca2e877b18ff46902c106f56ed672
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 5b5068defdc3579479bf0efa306a07ca
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: a965c5c0ea798db4787e279d5bef59fa
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,441 @@
Shader "AibisDream/SpriteNoiseGlitch"
{
Properties
{
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
_Color("Tint", Color) = (1,1,1,1)
[Header(Glitch Control)]
_GlitchIntensity("Glitch Intensity", Range(0, 1)) = 0
_Speed("Animation Speed", Float) = 5.0
[Header(Noise)]
_NoiseScale("Noise Scale (lower = bigger pixel dots)", Range(40, 500)) = 80
_NoiseDensity("Noise Density (white dot probability)", Range(0, 1)) = 0.1
_NoiseBrightness("Noise Brightness", Range(0, 2)) = 1.0
_ScanlineCount("Scanline Count", Float) = 80
_ScanlineStrength("Scanline Strength", Range(0, 1)) = 0.3
[Header(Block Glitch)]
_BlockSize("Block Row Count", Range(2, 40)) = 12
_DisplaceStrength("Displace Strength", Float) = 0.15
_ChromaticShift("Chromatic Shift", Float) = 0.04
[Header(Heavy Glitch)]
_TearStrength("Tear Strength", Float) = 0.3
_FlickerStrength("Flicker Strength", Range(0, 1)) = 0.4
_DropoutSize("Dropout Block Size", Range(0, 1)) = 0.15
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"RenderType" = "Transparent"
"RenderPipeline" = "UniversalPipeline"
"IgnoreProjector" = "True"
"CanUseSpriteAtlas" = "True"
}
Cull Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
Name "SpriteNoiseGlitch"
Tags { "LightMode" = "Universal2D" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#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 _GlitchIntensity;
float _Speed;
float _NoiseScale;
float _NoiseDensity;
float _NoiseBrightness;
float _ScanlineCount;
float _ScanlineStrength;
float _BlockSize;
float _DisplaceStrength;
float _ChromaticShift;
float _TearStrength;
float _FlickerStrength;
float _DropoutSize;
CBUFFER_END
float hash11(float p)
{
p = frac(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return frac(p);
}
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);
}
float hash31(float3 p)
{
p = frac(p * float3(0.1031, 0.1030, 0.0973));
p += dot(p, p.yxz + 33.33);
return frac((p.x + p.y) * p.z);
}
float remap01(float v, float lo, float hi)
{
return saturate((v - lo) / (hi - lo));
}
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
{
float intensity = _GlitchIntensity;
float t = _Time.y * _Speed;
float2 uv = input.uv;
half4 spriteColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
float spriteAlpha = spriteColor.a * input.color.a;
clip(spriteAlpha - 0.001);
float w1 = remap01(intensity, 0.0, 0.45);
float w2 = remap01(intensity, 0.3, 0.75);
float w3 = remap01(intensity, 0.6, 1.0);
// 噪点密度:强度 0 时几乎为 0,随强度增加逐渐出现
float noiseDensity = _NoiseDensity * remap01(intensity, 0.0, 0.5);
// ============================================================
// STAGE 1: black base + white noise dots + scanlines
// ============================================================
float timeSeed = floor(t * 8.0);
float2 cell = floor(uv * _NoiseScale);
float noiseVal = hash31(float3(cell, timeSeed));
float whiteDot = step(1.0 - noiseDensity, noiseVal) * _NoiseBrightness;
float scanline = sin(uv.y * _ScanlineCount * PI);
float scanMask = smoothstep(0.3, 1.0, scanline) * _ScanlineStrength * w1;
half3 baseColor = half3(whiteDot, whiteDot, whiteDot);
baseColor *= (1.0 - scanMask * 0.5);
// ============================================================
// STAGE 2: horizontal block displacement + chromatic aberration
// ============================================================
float blockRow = floor(uv.y * _BlockSize);
float blockTimeSeed = floor(t * 6.0);
float blockRand = hash21(float2(blockRow, blockTimeSeed));
float blockThreshold = lerp(1.1, 0.3, w2);
float blockActive = step(blockThreshold, blockRand);
float displaceDir = (hash11(blockRow * 7.3 + blockTimeSeed * 1.7) - 0.5) * 2.0;
float displace = displaceDir * _DisplaceStrength * w2 * blockActive;
float2 uvShifted = uv + float2(displace, 0);
float chromaOffset = _ChromaticShift * w2 * (1.0 + blockActive * 2.0);
float noiseR = hash31(float3(floor((uvShifted + float2( chromaOffset, 0)) * _NoiseScale), timeSeed));
float noiseG = hash31(float3(floor( uvShifted * _NoiseScale), timeSeed));
float noiseB = hash31(float3(floor((uvShifted + float2(-chromaOffset, 0)) * _NoiseScale), timeSeed));
float dotR = step(1.0 - noiseDensity, noiseR) * _NoiseBrightness;
float dotG = step(1.0 - noiseDensity, noiseG) * _NoiseBrightness;
float dotB = step(1.0 - noiseDensity, noiseB) * _NoiseBrightness;
half3 chromaColor = half3(dotR, dotG, dotB);
chromaColor *= (1.0 - scanMask * 0.5);
baseColor = lerp(baseColor, chromaColor, w2);
// add shifted solid color bands in displaced blocks
float bandBrightness = blockActive * w2 * 0.15
* hash11(blockRow + blockTimeSeed * 3.1);
baseColor += half3(bandBrightness, bandBrightness * 0.7, bandBrightness * 0.5);
// ============================================================
// STAGE 3: large tear blocks + flicker + dropout regions
// ============================================================
float tearRow = floor(uv.y * _BlockSize * 0.4);
float tearTimeSeed = floor(t * 10.0);
float tearRand = hash21(float2(tearRow, tearTimeSeed));
float tearActive = step(lerp(1.1, 0.4, w3), tearRand);
float tearDisplace = (hash11(tearRow * 13.7 + tearTimeSeed) - 0.5)
* _TearStrength * w3 * tearActive;
float2 uvTorn = uv + float2(tearDisplace, 0);
float tornNoise = hash31(float3(floor(uvTorn * _NoiseScale * 0.8), timeSeed * 1.3));
float tornDot = step(1.0 - noiseDensity * 1.5, tornNoise);
half3 tearColor = half3(tornDot, tornDot, tornDot) * _NoiseBrightness;
float tearChroma = _ChromaticShift * w3 * 3.0;
float tornR = hash31(float3(floor((uvTorn + float2( tearChroma, 0)) * _NoiseScale * 0.8), timeSeed * 1.3));
float tornB = hash31(float3(floor((uvTorn + float2(-tearChroma, 0)) * _NoiseScale * 0.8), timeSeed * 1.3));
tearColor.r = max(tearColor.r, step(1.0 - noiseDensity * 1.5, tornR) * _NoiseBrightness);
tearColor.b = max(tearColor.b, step(1.0 - noiseDensity * 1.5, tornB) * _NoiseBrightness);
baseColor = lerp(baseColor, tearColor, w3 * tearActive);
// flicker
float flicker = 1.0 + (hash11(floor(t * 15.0)) - 0.5) * _FlickerStrength * w3;
baseColor *= flicker;
// dropout: entire rectangular regions go white or black
float2 dropBlock = floor(uv * float2(6, _BlockSize * 0.3));
float dropSeed = hash21(dropBlock + floor(t * 12.0));
float dropActive = step(1.0 - _DropoutSize * w3, dropSeed);
float dropWhite = step(0.5, hash11(dropSeed * 77.7));
baseColor = lerp(baseColor, half3(dropWhite, dropWhite, dropWhite), dropActive * w3);
// random bright horizontal lines
float lineRow = floor(uv.y * 200.0);
float lineActive = step(0.995 - 0.02 * w3,
hash21(float2(lineRow, floor(t * 20.0))));
baseColor += lineActive * w3 * 0.6;
// ============================================================
// final output
// ============================================================
half4 result;
result.rgb = baseColor;
result.a = spriteAlpha;
result *= input.color;
result.a = spriteAlpha;
return result;
}
ENDHLSL
}
Pass
{
Name "SpriteNoiseGlitchUnlit"
Tags { "LightMode" = "SRPDefaultUnlit" }
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#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 _GlitchIntensity;
float _Speed;
float _NoiseScale;
float _NoiseDensity;
float _NoiseBrightness;
float _ScanlineCount;
float _ScanlineStrength;
float _BlockSize;
float _DisplaceStrength;
float _ChromaticShift;
float _TearStrength;
float _FlickerStrength;
float _DropoutSize;
CBUFFER_END
float hash11(float p)
{
p = frac(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return frac(p);
}
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);
}
float hash31(float3 p)
{
p = frac(p * float3(0.1031, 0.1030, 0.0973));
p += dot(p, p.yxz + 33.33);
return frac((p.x + p.y) * p.z);
}
float remap01(float v, float lo, float hi)
{
return saturate((v - lo) / (hi - lo));
}
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
{
float intensity = _GlitchIntensity;
float t = _Time.y * _Speed;
float2 uv = input.uv;
half4 spriteColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
float spriteAlpha = spriteColor.a * input.color.a;
clip(spriteAlpha - 0.001);
float w1 = remap01(intensity, 0.0, 0.45);
float w2 = remap01(intensity, 0.3, 0.75);
float w3 = remap01(intensity, 0.6, 1.0);
float timeSeed = floor(t * 8.0);
float noiseDensity = _NoiseDensity * remap01(intensity, 0.0, 0.5);
float noiseVal = hash31(float3(floor(uv * _NoiseScale), timeSeed));
float whiteDot = step(1.0 - noiseDensity, noiseVal) * _NoiseBrightness;
float scanline = sin(uv.y * _ScanlineCount * PI);
float scanMask = smoothstep(0.3, 1.0, scanline) * _ScanlineStrength * w1;
half3 baseColor = half3(whiteDot, whiteDot, whiteDot);
baseColor *= (1.0 - scanMask * 0.5);
float blockRow = floor(uv.y * _BlockSize);
float blockTimeSeed = floor(t * 6.0);
float blockRand = hash21(float2(blockRow, blockTimeSeed));
float blockThreshold = lerp(1.1, 0.3, w2);
float blockActive = step(blockThreshold, blockRand);
float displaceDir = (hash11(blockRow * 7.3 + blockTimeSeed * 1.7) - 0.5) * 2.0;
float displace = displaceDir * _DisplaceStrength * w2 * blockActive;
float2 uvShifted = uv + float2(displace, 0);
float chromaOffset = _ChromaticShift * w2 * (1.0 + blockActive * 2.0);
float noiseR = hash31(float3(floor((uvShifted + float2( chromaOffset, 0)) * _NoiseScale), timeSeed));
float noiseG = hash31(float3(floor( uvShifted * _NoiseScale), timeSeed));
float noiseB = hash31(float3(floor((uvShifted + float2(-chromaOffset, 0)) * _NoiseScale), timeSeed));
float dotR = step(1.0 - noiseDensity, noiseR) * _NoiseBrightness;
float dotG = step(1.0 - noiseDensity, noiseG) * _NoiseBrightness;
float dotB = step(1.0 - noiseDensity, noiseB) * _NoiseBrightness;
half3 chromaColor = half3(dotR, dotG, dotB);
chromaColor *= (1.0 - scanMask * 0.5);
baseColor = lerp(baseColor, chromaColor, w2);
float bandBrightness = blockActive * w2 * 0.15
* hash11(blockRow + blockTimeSeed * 3.1);
baseColor += half3(bandBrightness, bandBrightness * 0.7, bandBrightness * 0.5);
float tearRow = floor(uv.y * _BlockSize * 0.4);
float tearTimeSeed = floor(t * 10.0);
float tearRand = hash21(float2(tearRow, tearTimeSeed));
float tearActive = step(lerp(1.1, 0.4, w3), tearRand);
float tearDisplace = (hash11(tearRow * 13.7 + tearTimeSeed) - 0.5)
* _TearStrength * w3 * tearActive;
float2 uvTorn = uv + float2(tearDisplace, 0);
float tornNoise = hash31(float3(floor(uvTorn * _NoiseScale * 0.8), timeSeed * 1.3));
float tornDot = step(1.0 - noiseDensity * 1.5, tornNoise);
half3 tearColor = half3(tornDot, tornDot, tornDot) * _NoiseBrightness;
float tearChroma = _ChromaticShift * w3 * 3.0;
float tornR = hash31(float3(floor((uvTorn + float2( tearChroma, 0)) * _NoiseScale * 0.8), timeSeed * 1.3));
float tornB = hash31(float3(floor((uvTorn + float2(-tearChroma, 0)) * _NoiseScale * 0.8), timeSeed * 1.3));
tearColor.r = max(tearColor.r, step(1.0 - noiseDensity * 1.5, tornR) * _NoiseBrightness);
tearColor.b = max(tearColor.b, step(1.0 - noiseDensity * 1.5, tornB) * _NoiseBrightness);
baseColor = lerp(baseColor, tearColor, w3 * tearActive);
float flicker = 1.0 + (hash11(floor(t * 15.0)) - 0.5) * _FlickerStrength * w3;
baseColor *= flicker;
float2 dropBlock = floor(uv * float2(6, _BlockSize * 0.3));
float dropSeed = hash21(dropBlock + floor(t * 12.0));
float dropActive = step(1.0 - _DropoutSize * w3, dropSeed);
float dropWhite = step(0.5, hash11(dropSeed * 77.7));
baseColor = lerp(baseColor, half3(dropWhite, dropWhite, dropWhite), dropActive * w3);
float lineRow = floor(uv.y * 200.0);
float lineActive = step(0.995 - 0.02 * w3,
hash21(float2(lineRow, floor(t * 20.0))));
baseColor += lineActive * w3 * 0.6;
half4 result;
result.rgb = baseColor;
result.a = spriteAlpha;
result *= input.color;
result.a = spriteAlpha;
return result;
}
ENDHLSL
}
}
Fallback "Sprites/Default"
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 505a54c499a926e419139bbed9fe5a81
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,228 @@
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(SpriteRenderer))]
public class SpriteNoiseGlitchController : MonoBehaviour
{
public enum GlitchStage { None, Light, Medium, Heavy }
[Header("Runtime")]
[SerializeField] private GlitchStage _currentStage = GlitchStage.None;
[SerializeField, Range(0f, 1f)] private float _intensity;
[SerializeField] private float _transitionDuration = 0.5f;
[Header("Audio (Optional)")]
[Tooltip("由 Tools > Generate Glitch Noise Audio 生成")]
[SerializeField] private AudioClip _stage1Clip;
[SerializeField] private AudioClip _stage2Clip;
[SerializeField] private AudioClip _stage3Clip;
[SerializeField, Range(0f, 1f)] private float _audioVolume = 0.5f;
private SpriteRenderer _renderer;
private AudioSource _audioSource;
private MaterialPropertyBlock _mpb;
private Coroutine _transitionCoroutine;
private static readonly int PropGlitchIntensity = Shader.PropertyToID("_GlitchIntensity");
private static readonly float[] StageValues = { 0f, 0.25f, 0.55f, 0.95f };
public float Intensity
{
get => _intensity;
set
{
_intensity = Mathf.Clamp01(value);
ApplyIntensity();
}
}
public GlitchStage CurrentStage
{
get => _currentStage;
set => SetStage(value);
}
private void Awake()
{
_renderer = GetComponent<SpriteRenderer>();
_mpb = new MaterialPropertyBlock();
}
private void OnEnable()
{
ApplyIntensity();
}
public void SetStage(GlitchStage stage, bool instant = false)
{
_currentStage = stage;
float target = StageValues[(int)stage];
ApplyAudio(stage);
if (instant || !gameObject.activeInHierarchy)
{
Intensity = target;
return;
}
if (_transitionCoroutine != null)
StopCoroutine(_transitionCoroutine);
_transitionCoroutine = StartCoroutine(TransitionTo(target));
}
public void SetStageByIndex(int index)
{
index = Mathf.Clamp(index, 0, 3);
SetStage((GlitchStage)index);
}
/// <summary>
/// 从当前强度平滑过渡到目标值(类似 DOTween),不瞬移。
/// </summary>
/// <param name="target">目标强度 [0,1]</param>
/// <param name="durationSeconds">过渡时长(秒)</param>
public void TransitionTo(float target, float durationSeconds)
{
if (durationSeconds > 0f && gameObject.activeInHierarchy)
StartCoroutine(TransitionToAndWait(target, durationSeconds));
else
{
target = Mathf.Clamp01(target);
Intensity = target;
_currentStage = ValueToStage(target);
ApplyAudio(_currentStage);
}
}
/// <summary>
/// 从当前强度平滑过渡到目标值,等待完成后返回。供 Yarn 等需要阻塞的调用使用。
/// </summary>
public IEnumerator TransitionToAndWait(float target, float durationSeconds)
{
target = Mathf.Clamp01(target);
float startValue = _intensity;
if (_transitionCoroutine != null)
StopCoroutine(_transitionCoroutine);
if (durationSeconds <= 0f || !gameObject.activeInHierarchy)
{
Intensity = target;
_currentStage = ValueToStage(target);
ApplyAudio(_currentStage);
yield break;
}
_transitionCoroutine = StartCoroutine(TransitionToValue(startValue, target, durationSeconds));
yield return _transitionCoroutine;
_transitionCoroutine = null;
}
/// <summary>
/// 将 [0,1] 强度映射到对应阶段(用于音效匹配)
/// </summary>
private static GlitchStage ValueToStage(float value)
{
if (value < 0.125f) return GlitchStage.None;
if (value < 0.4f) return GlitchStage.Light;
if (value < 0.75f) return GlitchStage.Medium;
return GlitchStage.Heavy;
}
private IEnumerator TransitionTo(float target)
{
float start = _intensity;
float elapsed = 0f;
while (elapsed < _transitionDuration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / _transitionDuration);
Intensity = Mathf.Lerp(start, target, t);
yield return null;
}
Intensity = target;
_transitionCoroutine = null;
}
private IEnumerator TransitionToValue(float from, float to, float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.SmoothStep(0f, 1f, elapsed / duration);
float current = Mathf.Lerp(from, to, t);
Intensity = current;
// 过渡过程中,若跨越阶段边界则切换对应音效
var stage = ValueToStage(current);
if (stage != _currentStage)
{
_currentStage = stage;
ApplyAudio(stage);
}
yield return null;
}
Intensity = to;
_currentStage = ValueToStage(to);
ApplyAudio(_currentStage);
_transitionCoroutine = null;
}
private void ApplyIntensity()
{
if (_renderer == null) return;
_renderer.GetPropertyBlock(_mpb);
_mpb.SetFloat(PropGlitchIntensity, _intensity);
_renderer.SetPropertyBlock(_mpb);
}
private void ApplyAudio(GlitchStage stage)
{
if (_stage1Clip == null && _stage2Clip == null && _stage3Clip == null) return;
if (_audioSource == null)
{
_audioSource = GetComponent<AudioSource>();
if (_audioSource == null)
_audioSource = gameObject.AddComponent<AudioSource>();
}
_audioSource.Stop();
AudioClip clip = stage switch
{
GlitchStage.Light => _stage1Clip,
GlitchStage.Medium => _stage2Clip,
GlitchStage.Heavy => _stage3Clip,
_ => null
};
if (clip != null)
{
_audioSource.clip = clip;
_audioSource.volume = _audioVolume;
_audioSource.loop = true;
_audioSource.Play();
}
}
#if UNITY_EDITOR
private void OnValidate()
{
if (_renderer == null)
_renderer = GetComponent<SpriteRenderer>();
if (_renderer != null && _mpb == null)
_mpb = new MaterialPropertyBlock();
ApplyIntensity();
}
#endif
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85efd99becf18bd48a4cc39cc38100fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: