Unity屏幕故障

RGB颜色分离故障(RGB Split Glitch)

这种效果的基本思路是将RGB三个颜色通道分别进行偏移,从而产生颜色分离的视觉效果。(本文采用的是Shader + 脚本的方法)

初步实现的效果

//扰动函数
 float randomNoise(float x, float y) 
 {
     return frac(sin(dot(float2(x, y), float2(12.9898, 78.233))) * 43758.5453);
 }


fixed4 frag (v2f i) : SV_Target {

     float splitAmount = _Intensity * randomNoise(_Time.x, 2);
   
    // 采样各通道
    half4 colorR = tex2D(_MainTex,  float2(i.uv.x + splitAmount, i.uv.y));
    half4 colorG = tex2D(_MainTex,  i.uv);
    half4 colorB = tex2D(_MainTex,  float2(i.uv.x - splitAmount, i.uv.y));

    // 组合颜色并调整亮度
    half3 finalColor = half3(colorR.r, colorG.g, colorB.b);
  
    return half4(finalColor, 1.0);
}

也可以用三角函数和pow方法控制抖动的间隔、幅度,以及抖动的曲线,更自然

通过多个正弦函数相乘得到一个随时间变化的噪声值(实际上是模拟一种不规则的波动),然后通过`pow`函数和参数`_Amplitude`调整其形状,再乘以基础参数`_Amount`(控制整体强度)得到初始的偏移量。

fixed4 frag (v2f i) : SV_Target {

     
    // 计算动态偏移量(三角函数波动)
    float splitAmount = (1.0 + sin(_Time.x * 6.0)) * 0.5;
    splitAmount *= (1.0 + sin(_Time.x * 16.0)) * 0.5;
    splitAmount *= (1.0 + sin(_Time.x * 19.0)) * 0.5;
    splitAmount *= (1.0 + sin(_Time.x * 27.0)) * 0.5;
    splitAmount = pow(splitAmount, _Amplitude);
    splitAmount *= (0.05 * _Amount); // 结合参数
    //计算的是当前像素的UV坐标到屏幕中心(0.5,0.5)的距离。UV坐标的范围是(0,0)到(1,1),所以中心就是(0.5,0.5)。这个距离用来表示像素离屏幕中心的远近。
    float distance = length(i.uv.xy - float2(0.5, 0.5));
    splitAmount *=  _Fading ;//_Fading`来控制整个效果的整体强度(包括时间和空间)。
    splitAmount *= lerp(1, distance, _CenterFading);//控制效果在屏幕中心减弱(甚至消失),而在边缘增强。这样可以使故障效果主要出现在屏幕边缘,中心区域则比较正常,模拟一些镜头边缘色散的效果。
    
   
    // 采样各通道
    half3 colorR = tex2D(_MainTex,  float2(i.uv.x + splitAmount, i.uv.y)).rgb;
    half4 sceneColor= tex2D(_MainTex,  i.uv);
    half3 colorB= tex2D(_MainTex,  float2(i.uv.x - splitAmount, i.uv.y)).rgb;

    // 组合颜色并调整亮度
    
    half3 splitColor = half3(colorR.r, sceneColor.g, colorB.b);
    half3 finalColor = lerp(sceneColor.rgb, splitColor, _Fading);

    return half4(finalColor, 1.0);
}

可以结合脚本实现动态方向变化效果让RGB分离故障更加生动和多样化,模拟出真实设备故障时方向不固定的效果。

错位图块故障(Image Block Glitch)

错位图块故障效果的核心在于将图像分割成块状区域,并基于噪声函数对这些区域进行随机位移和颜色通道分离,模拟数字信号传输故障或显示设备故障的视觉效果。

初步效果

  // 随机噪声函数 (低成本)
 float randomNoise(float2 seed)
 {
     return frac(sin(dot(seed * floor(_Time.y * _Speed), float2(17.13, 3.71))) * 43758.5453123);
 }


float2 blockGlitch(float2 uv)
{
    // 第一步:基于uv和噪声函数生成方格块
    float2 block = floor(uv * _BlockSize);
    float noise = randomNoise(block);
    
    // 第二步:强度二次筛选
    float displaceNoise = pow(noise, 8.0) * pow(noise, 3.0);
    displaceNoise *= _Intensity;
    
    // 创建随机偏移方向
    float randomAngle = noise * 6.283185; // 0-2PI
    float2 offsetDir = float2(cos(randomAngle), sin(randomAngle));
    
    // 返回位移向量
    return offsetDir * displaceNoise * _OffsetScale;
}


 fixed4 frag (v2f i) : SV_Target
 {
    
     // 计算块状故障位移
     float2 glitchOffset = blockGlitch(i.uv);
    
     
     // 第三步:对颜色通道进行采样
     half colorR = tex2D(_MainTex, i.uv).r;
     half colorG = tex2D(_MainTex, i.uv + float2(glitchOffset.x, 0)).g;
     half colorB = tex2D(_MainTex, i.uv - float2(glitchOffset.y, 0)).b;

     return fixed4(colorR, colorG, colorB, 1.0);
 }

结合RGB颜色分离故障(RGB Split Glitch)

 fixed4 frag (v2f i) : SV_Target
 {
     // 1. 生成块状噪声
     half2 block = randomNoise(floor(i.uv * _BlockSize));
     
     // 2. 计算位移噪声
     float displaceNoise = pow(block.x, _DisplacePower);
     
     // 3. 计算RGB分离噪声
     float splitRGBNoise = pow(randomNoise(7.2341), 17.0);
     
     // 4. 计算RGB分离偏移
     float offsetX = displaceNoise - splitRGBNoise * _MaxRGBSplitX;
     float offsetY = displaceNoise - splitRGBNoise * _MaxRGBSplitY;
     
     // 5. 添加额外噪声扰动
     float noiseX = _NoiseScale * randomNoise(13.0);
     float noiseY = _NoiseScale * randomNoise(7.0);
     
     // 6. 组合最终偏移向量
     float2 offset = float2(offsetX * noiseX, offsetY * noiseY);
     
     // 7. 采样各通道
     half4 colorR = tex2D(_MainTex, i.uv);
     half4 colorG = tex2D(_MainTex, i.uv + offset);
     half4 colorB = tex2D(_MainTex, i.uv - offset);
     
       // 8. 混合RGB分离效果
     half3 combinedColor = half3(colorR.r, colorG.g, colorB.b);
     half3 finalColor = lerp(colorR.rgb, combinedColor, _SplitIntensity);
     
     return half4(finalColor, colorR.a);
 }

错位线条故障(Line Block Glitch)

错位线条故障(Line Block Glitch)的核心是生成随机的线条块(沿y方向/沿x方向) 。对UV进行随机偏移(主要是x方向) 在YUV颜色空间调整颜色,增强故障效果 ,将故障后的颜色与原图混合即可

  float trunc(float x, float num_levels)
  {
      return floor(x * num_levels) / num_levels;
  }
  float randomNoise(float2 c)
  {
       return frac(sin(dot(c.xy, float2(12.9898, 78.233))) * 43758.5453);
  }

  float3 rgb2yuv(float3 rgb)
  {
       float3 yuv;
       yuv.x = dot(rgb, float3(0.299, 0.587, 0.114));
       yuv.y = dot(rgb, float3(-0.14713, -0.28886, 0.436));
       yuv.z = dot(rgb, float3(0.615, -0.51499, -0.10001));
       return yuv;
  }

  float3 yuv2rgb(float3 yuv)
  {
       float3 rgb;
       rgb.r = yuv.x + yuv.z * 1.13983;
       rgb.g = yuv.x + dot(float2(-0.39465, -0.58060), yuv.yz);
       rgb.b = yuv.x + yuv.y * 2.03211;
       return rgb;
  }

  fixed4 frag (v2f i) : SV_Target
  {
      //生成随机强度梯度线条
      float truncTime = trunc(_Time.x* _LineSpeed, 4.0);       
      float uv_trunc = randomNoise(trunc(i.uv.yy, float2(8, 8)) + 100.0 * truncTime);
      float uv_randomTrunc = 6.0 * trunc(_Time.x, 24.0 * uv_trunc);

      //生成随机梯度的非等宽线条
      float blockLine_random = 0.5 * randomNoise(trunc(i.uv.yy + uv_randomTrunc, float2(8 * _LinesWidth, 8 * _LinesWidth)));
      blockLine_random += 0.5 * randomNoise(trunc(i.uv.yy + uv_randomTrunc, float2(7, 7)));
      blockLine_random = blockLine_random * 2.0 - 1.0;    
      blockLine_random = sign(blockLine_random) * saturate((abs(blockLine_random) - _Amount) / (0.4));
      blockLine_random = lerp(0, blockLine_random, _Offset);

      // 生成源色调的blockLine Glitch
      float2 uv_blockLine = i.uv;
      uv_blockLine = saturate(uv_blockLine + float2(0.1 * blockLine_random, 0));
      float4 blockLineColor = tex2D(_MainTex, abs(uv_blockLine));

      // 将RGB转到YUV空间,并做色调偏移
      // RGB -> YUV
      float3 blockLineColor_yuv = rgb2yuv(blockLineColor.rgb);
      // adjust Chrominance | 色度
      blockLineColor_yuv.y /= 1.0 - 3.0 * abs(blockLine_random) * saturate(0.5 - blockLine_random);
      // adjust Chroma | 浓度
      blockLineColor_yuv.z += 0.125 * blockLine_random * saturate(blockLine_random - 0.5);
      float3 blockLineColor_rgb = yuv2rgb(blockLineColor_yuv);

      // 与源场景图进行混合
      float4 sceneColor =tex2D(_MainTex,  i.uv);
      return lerp(sceneColor, float4(blockLineColor_rgb, blockLineColor.a), _Alpha);
  }

图块抖动故障(Tile Jitter Glitch)

图块抖动故障的核心原理是通过将屏幕分割成多个图块区域,并让部分图块随时间产生位置偏移,模拟显示设备故障或信号干扰的效果。

 float randomNoise(float2 seed)
 {
     return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
 }

  // 获取像素尺寸
 float2 getPixelSize()
 {
     return float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y);
 }

 fixed4 frag (v2f i) : SV_Target
 {
   
      //计算像素尺寸
     float2 pixelSize = getPixelSize();
     
    
     float timeFactor = _Time.y * _JitterSpeed;
     float randomness = lerp(1.0, randomNoise(float2(timeFactor, i.uv.y)), _Randomness);
     float strength = 0.5 + 0.5 * cos(timeFactor * _Frequency) * randomness;
     
   
     float splitFactor;
     if(_Direction == 0) // 水平方向
         splitFactor = fmod(i.uv.y * _SplittingNumber, 2.0);
     else // 垂直方向
         splitFactor = fmod(i.uv.x * _SplittingNumber, 2.0);
     

     float2 jitterOffset = float2(0, 0);
     
     if(splitFactor < 1.0)
     {
         float jitterValue = cos(timeFactor * 10.0) * _JitterAmount * strength;
         
         if(_Direction == 0) // 水平方向抖动
             jitterOffset.x = pixelSize.x * jitterValue;
         else // 垂直方向抖动
             jitterOffset.y = pixelSize.y * jitterValue;
     }

     float2 jitterUV = i.uv + jitterOffset;
     half4 sceneColor = tex2D(_MainTex, jitterUV);
      //添加RGB分离
     half4 colorR = tex2D(_MainTex, jitterUV + float2(pixelSize.x * 2, 0));
     half4 colorB = tex2D(_MainTex, jitterUV - float2(pixelSize.x * 2, 0));
     return half4(colorR.r, sceneColor.g, colorB.b, sceneColor.a);
     
    
 }

扫描线抖动故障(Scan Line Jitter Glitch)

扫描线抖动故障的核心原理是通过在水平方向随机偏移扫描线的位置,模拟显示同步故障的效果。


float randomNoise(float2 seed)
{
    return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
}

fixed4 frag (v2f i) : SV_Target
{
   
    float jitter = randomNoise(float2(i.uv.y, _Time.x)) * 2.0 - 1.0;
           
    jitter = step(_JitterThreshold, abs(jitter)) * jitter * _JitterAmount;

   
   // float verticalJitter = sin(i.uv.x * 100 + _Time.y * _JitterSpeed * 2) * _VerticalJitter;


   // float2 jitterOffset = float2(jitter, verticalJitter);

     //half4 sceneColor = tex2D(_MainTex,  frac(i.uv + float2(jitterOffset, 0)));
    float2 jitterUV = i.uv + float2(jitter, 0);
    half4 sceneColor = tex2D(_MainTex, jitterUV);

     return sceneColor;

}

数字条纹故障(Digital Stripe Glitch)

数字条纹故障(Digital Stripe Glitch)核心原理:​​通过CPU生成动态噪声纹理,在GPU中进行UV偏移和颜色混合​​,模拟数字信号故障的视觉效果

先生成一个包含随机颜色条纹的噪声纹理

using UnityEngine;

public class GlitchEffectController : MonoBehaviour
{
    [Header("纹理设置")]
    public int noiseTextureWidth = 64;
    public int noiseTextureHeight = 64;
    [Range(0.7f, 0.99f)] public float stripLength = 0.98f; 
    public float updateInterval = 0.2f; 
    [Header("Shader参数")]
    public Material glitchMaterial;

    private Texture2D _noiseTexture;
    private float _updateTimer;

    private void Start()
    {
        // 创建噪声纹理
        CreateNoiseTexture();

        // 将纹理传递给Shader
        if (glitchMaterial != null)
            glitchMaterial.SetTexture("_NoiseTex", _noiseTexture);
    }

    private void Update()
    {
        // 定时更新噪声纹理
        _updateTimer += Time.deltaTime;
        if (_updateTimer >= updateInterval)
        {
            UpdateNoiseTexture();
            _updateTimer = 0f;
        }
    }

    private void CreateNoiseTexture()
    {
        // 创建新纹理
        _noiseTexture = new Texture2D(noiseTextureWidth, noiseTextureHeight,
                                     TextureFormat.RGBA32, false);
        _noiseTexture.wrapMode = TextureWrapMode.Repeat;
        _noiseTexture.filterMode = FilterMode.Point; // 保持锐利边缘

        // 初始填充纹理
        UpdateNoiseTexture();
    }

    private void UpdateNoiseTexture()
    {
        if (_noiseTexture == null) return;

        Color currentColor = Random.ColorHSV(0f, 1f, 0.8f, 1f, 0.8f, 1f, 1f, 1f);

        for (int y = 0; y < _noiseTexture.height; y++)
        {
            for (int x = 0; x < _noiseTexture.width; x++)
            {
               
                if (Random.value > stripLength)
                {
                    currentColor = Random.ColorHSV(0f, 1f, 0.8f, 1f, 0.8f, 1f, 1f, 1f);
                }
                _noiseTexture.SetPixel(x, y, currentColor);
            }
        }

        _noiseTexture.Apply();

       
        if (glitchMaterial != null)
            glitchMaterial.SetTexture("_NoiseTex", _noiseTexture);
    }

    private void OnDestroy()
    {
        
        if (_noiseTexture != null)
            Destroy(_noiseTexture);
    }
}

然后利用生成的噪声纹理对主纹理进行扭曲

 fixed4 frag (v2f i) : SV_Target
 {
     
      half4 stripNoise = tex2D(_NoiseTex,  i.uv);
      half threshold = 1.001 - _Indensity * 1.001;

      
      half uvShift = step(threshold, pow(abs(stripNoise.x), 3)) * _Indensity;
      float2 uv = frac(i.uv + stripNoise.yz * uvShift);
      half4 source = tex2D(_MainTex,  uv);

      #ifndef NEED_TRASH_FRAME
         return source;
       #endif

      // 基于废弃帧插值
      half stripIndensity = step(threshold, pow(abs(stripNoise.w), 3)) * _ColorIntensity;
      half3 color = lerp(source, _StripColor, stripIndensity).rgb;
      return float4(color, source.a);
 }

模拟噪点故障(Analog Noise Glitch)

模拟噪点故障(Analog Noise Glitch)的核心原理是用随机噪声搞坏画面,模仿老电视信号故障的雪花点画面。就想象一个老电视,看着看着突然「刺啦」闪雪花(撒噪点)色彩突然褪成黑白(亮度抖动)坏了,坏得还要有点规律(用UV位置控制)

float randomNoise(float2 seed)
{
    return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
}

fixed4 frag (v2f i) : SV_Target
{
    half4 sceneColor = tex2D(_MainTex, i.uv);
    half4 noiseColor = sceneColor;
   
         //当某一刻的随机强度值大于亮度抖动阈值时,将原先的RGB颜色对应的luminance强度,呈现出黑白灰度的表现。
    half luminance = dot(noiseColor.rgb, fixed3(0.22, 0.707, 0.071));
    if (randomNoise(float2(_Time.x * _Speed, _Time.x * _Speed)) > _LuminanceJitterThreshold)
    {
          noiseColor = float4(luminance, luminance, luminance, luminance);
    }

    //用noise去扰动原先场景图的颜色值

    float noiseX = randomNoise(_Time.x * _Speed + i.uv/ float2(-213, 5.53));
    float noiseY = randomNoise(_Time.x * _Speed - i.uv / float2(213, -5.53));
    float noiseZ = randomNoise(_Time.x * _Speed + i.uv / float2(213, 5.53));

    noiseColor.rgb += 0.25 * float3(noiseX,noiseY,noiseZ) - 0.125;

    noiseColor = lerp(sceneColor, noiseColor, _Fading);
		
    return noiseColor;
}

屏幕跳跃故障(Screen Jump Glitch)

屏幕跳跃故障(Screen Jump Glitch)的核心是使用时间偏移来影响UV坐标,校正的UV小数部分与原始UV插值,创建均匀梯度式扰动效果。

fixed4 frag (v2f i) : SV_Target
{
     
    float timeFactor = _Time.y * _JumpSpeed;
    float jumpTime = frac(timeFactor);
    
    float2 jumpUV = i.uv;
    
    // 对每个方向分量单独处理
    if (_Direction.x > 0.5)
    {
        float perturbedX = frac(i.uv.x + jumpTime * _JumpAmount);
        jumpUV.x = lerp(i.uv.x, perturbedX, _JumpIntensity);
    }
    
    if (_Direction.y > 0.5)
    {
        float perturbedY = frac(i.uv.y + jumpTime * _JumpAmount);
        jumpUV.y = lerp(i.uv.y, perturbedY, _JumpIntensity);
    }
    

    float smoothFactor = smoothstep(0.0, _Smoothness, frac(timeFactor));
    jumpUV = lerp(i.uv, jumpUV, smoothFactor * _JumpIntensity);
    
 
    half4 sceneColor = tex2D(_MainTex, jumpUV);
    
    return sceneColor;
}

屏幕抖动故障(Screen Shake Glitch)

屏幕抖动故障的核心原理是通过在水平和垂直方向同时施加随时间变化的随机偏移量,模拟设备震动或信号干扰导致的屏幕整体不规则晃动效果。

 float randomNoise(float2 seed)
 {
     return frac(sin(dot(seed + float2(_RandomSeed, _RandomSeed), float2(127.1, 311.7))) * 43758.5453);
 }

 fixed4 frag (v2f i) : SV_Target
 {
     
     float time = _Time.y * _ShakeSpeed;
     
     float shakeX = (randomNoise(float2(time, 2.0)) - 0.5) * _ShakeIntensity * _HorizontalAmount;
     

     float shakeY = (randomNoise(float2(time, 3.0)) - 0.5) * _ShakeIntensity * _VerticalAmount;
     

     float2 shakeUV = i.uv + float2(shakeX, shakeY);
     half4 sceneColor = tex2D(_MainTex, shakeUV);
     
     return sceneColor;
 }

波动抖动故障(Wave Jitter Glitch)

波动抖动故障的核心原理是通过在垂直/水平方向生成基于噪声的波形函数,将其转换为屏幕坐标的偏移量,并应用RGB通道分离采样,从而创建动态的波浪状画面扭曲效果。

float noise(float2 seed)
{
    return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453);
}

fixed4 frag (v2f i) : SV_Target
{
   
    half strength = 0.5 + 0.5 * cos(_Time.y * _Frequency);	
    
    float2 screenSize = float2(_ScreenParams.x, _ScreenParams.y);
   
  
     
    if (_Direction == 0) // 水平抖动
    {
        // 准备UV (Y坐标 * 屏幕高度)
        float uv_y = i.uv.y * screenSize.y;
        
        // 计算波形噪声
        float noise_wave_1 = noise(float2(uv_y *_WaveScale, _Time.y * _Speed * 20)) * (strength * _Amount *_WaveAmplitude );
        float noise_wave_2 = noise(float2(uv_y *(_WaveScale * 2), _Time.y * _Speed * 10)) * (strength * _Amount * (_WaveAmplitude * 0.125));
        float noise_wave = noise_wave_1 * noise_wave_2 / screenSize.x;
        
        //计算水平偏移
        float uv_x = i.uv.x + noise_wave;
        
        //计算RGB分离偏移
        float rgbSplit_offset = (_RGBSplit * 50 + (20.0 * strength + 1.0)) * noise_wave / screenSize.x;
        
        //采样RGB通道
        half4 colorG = tex2D(_MainTex, float2(uv_x, i.uv.y));
        half4 colorRB = tex2D(_MainTex, float2(uv_x + rgbSplit_offset, i.uv.y));
        
        //组合颜色
        return half4(colorRB.r, colorG.g, colorRB.b, colorG.a);
    }
    
    else // 垂直抖动
    {
       
        float uv_x = i.uv.x * screenSize.x;
        
        float noise_wave_1 = noise(float2(uv_x * _WaveScale, _Time.y * _Speed * 20)) * (strength * _Amount * _WaveAmplitude);
        float noise_wave_2 = noise(float2(uv_x *(_WaveScale * 2), _Time.y * _Speed * 10)) * (strength * _Amount *(_WaveAmplitude * 0.125));
        float noise_wave = noise_wave_1 * noise_wave_2 / screenSize.y;
        
 
        float uv_y = i.uv.y + noise_wave;
        
        float rgbSplit_offset = (_RGBSplit * 50 + (20.0 * strength + 1.0)) * noise_wave / screenSize.y;
        

        half4 colorG = tex2D(_MainTex, float2(i.uv.x, uv_y));
        half4 colorRB = tex2D(_MainTex, float2(i.uv.x, uv_y + rgbSplit_offset));
        

        return half4(colorRB.r, colorG.g, colorRB.b, colorG.a);
       
    }
    
}

以上就是全部的内容了,非常感谢大佬的文章,我只是站在巨人的肩膀上学习了他的方法。

 高品质后处理:十种故障艺术(Glitch Art)算法的总结与实现 - 知乎https://zhuanlan.zhihu.com/p/148256756

更多推荐