Unity Shader 学习 Glitch Art
 
简介
Glitch Art(故障艺术)是通过破坏数字数据或物理操作电子设备,将数字或模拟错误用于美学目的的做法[1]。
近年来,故障艺术已经成为了赛博朋克风格的电影和游戏作品中主要的艺术风格之一。
故障艺术的实现:
源自毛星云大佬的十种故障艺术(Glitch Art)算法的总结与实现[2]
- RGB颜色分离故障(RGB Split Glitch)- 表现   
- 思路 - 通过不同UV对图形进行三次采样的进行RGB通道重新混合。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- float randomNoise(float x, float y) 
 {
 return frac(sin(dot(float2(x, y), float2(12.9898, 78.233))) * 43758.5453);
 }
 half4 Frag_Horizontal(VaryingsDefault i) : SV_Target
 {
 float splitAmount = _Indensity * randomNoise(_TimeX, 2);
 half4 ColorR = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(i.texcoord.x + splitAmount, i.texcoord.y));
 half4 ColorG = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
 half4 ColorB = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(i.texcoord.x - splitAmount, i.texcoord.y));
 return half4(ColorR.r, ColorG.g, ColorB.b, 1);
 }
 
- 错位图块故障(Image Block Glitch)- 表现   
- 思路 - 基于uv和噪声函数生成方格块,然后将方块强度随机,然后剔除部分。最后与UV进行混合再对图形进行采样。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30- inline float randomNoise(float2 seed) 
 {
 return frac(sin(dot(seed * floor(_Time.y * _Speed), float2(17.13, 3.71))) * 43758.5453123);
 }
 inline float randomNoise(float seed)
 {
 return randomNoise(float2(seed, 1.0));
 }
 half4 Frag(VaryingsDefault i) : SV_Target
 {
 half2 block = randomNoise(floor(i.texcoord * _BlockSize));
 float displaceNoise = pow(block.x, 8.0) * pow(block.x, 3.0);
 float splitRGBNoise = pow(randomNoise(7.2341), 17.0);
 float offsetX = displaceNoise - splitRGBNoise * _MaxRGBSplitX;
 float offsetY = displaceNoise - splitRGBNoise * _MaxRGBSplitY;
 float noiseX = 0.05 * randomNoise(13.0);
 float noiseY = 0.05 * randomNoise(7.0);
 float2 offset = float2(offsetX * noiseX, offsetY* noiseY);
 half4 colorR = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
 half4 colorG = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord + offset);
 half4 colorB = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord - offset);
 return half4(colorR.r , colorG.g, colorB.z, (colorR.a + colorG.a + colorB.a));
 }
 
- 错位线条故障(Line Block Glitch)- 表现   
- 思路 - 通过生成随机宽度的线条,再进行随机扰动以及剔除部分,然后和UV混合,最后进行采样。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20- float trunc(float x, float num_levels) 
 {
 return floor(x * num_levels) / num_levels;
 }
 //生成随机强度梯度线条
 float truncTime = trunc(_TimeX, 4.0);
 float uv_trunc = randomNoise(trunc(uv.yy, float2(8, 8)) + 100.0 * truncTime);
 float uv_randomTrunc = 6.0 * trunc(_TimeX, 24.0 * uv_trunc);
 //生成随机梯度的非等宽线条
 float blockLine_random = 0.5 * randomNoise(trunc(uv.yy + uv_randomTrunc, float2(8 * _LinesWidth, 8 * _LinesWidth)));
 blockLine_random += 0.5 * randomNoise(trunc(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 = uv;
 uv_blockLine = saturate(uv_blockLine + float2(0.1 * blockLine_random, 0));
 float4 blockLineColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, abs(uv_blockLine));
 
- 图块抖动故障(Tile Jitter Glitch)- 表现   
- 思路 - 通过余数的形式(fmod(x,y)方法可返回x/y的余数)来对uv进行分层,且对于层内的uv数值,进行三角函数形式的抖动。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 strength = 1;
 strength = 0.5 + 0.5 * cos(_Time.y * _Frequency);
 if(fmod(uv.y * _SplittingNumber, 2) < 1.0)
 {
 
 uv.x += pixelSizeX * cos(_Time.y * _JitterSpeed) * _JitterAmount * strength;
 
 uv.y += pixelSizeX * cos(_Time.y * _JitterSpeed) * _JitterAmount * strength;
 
 }
 half4 sceneColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
 
- 扫描线抖动故障(Scan Line Jitter Glitch)- 表现   
- 思路 - 通过对uv的一个方向进行噪波抖动,再进行图像的采样。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15- float randomNoise(float x, float y) 
 {
 return frac(sin(dot(float2(x, y), float2(12.9898, 78.233))) * 43758.5453);
 }
 half4 Frag_Horizontal(VaryingsDefault i): SV_Target
 {
 float jitter = randomNoise(i.texcoord.y, _Time.x) * 2 - 1;
 jitter *= step(_ScanLineJitter.y, abs(jitter)) * _ScanLineJitter.x;
 half4 sceneColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, frac(i.texcoord + float2(jitter, 0)));
 return sceneColor;
 }
 
- 数字条纹故障(Digital Stripe Glitch)- 表现   
- 思路 - 基于随机数进行随机颜色条纹贴图的生成 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 for (int y = 0; y < _noiseTexture.height; y++)
 {
 for (int x = 0; x < _noiseTexture.width; x++)
 {
 //随机值若大于给定strip随机阈值,重新随机颜色
 if (UnityEngine.Random.value > stripLength)
 {
 color = XPostProcessingUtility.RandomColor();
 }
 //设置贴图像素值
 _noiseTexture.SetPixel(x, y, color);
 }
 }
 half4 Frag(VaryingsDefault i): SV_Target
 {
 // 基础数据准备
 half4 stripNoise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, i.texcoord);
 half threshold = 1.001 - _Indensity * 1.001;
 // uv偏移
 half uvShift = step(threshold, pow(abs(stripNoise.x), 3));
 float2 uv = frac(i.texcoord + stripNoise.yz * uvShift);
 half4 source = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
 
 return source;
 
 // 基于废弃帧插值
 half stripIndensity = step(threshold, pow(abs(stripNoise.w), 3)) * _StripColorAdjustIndensity;
 half3 color = lerp(source, _StripColorAdjustColor, stripIndensity).rgb;
 return float4(color, source.a);
 }
 
- 模拟噪点故障(Analog Noise Glitch)- 表现   
- 思路 - 通过噪波函数混合贴图颜色。 
- 代码实现 - 1 
 2
 3
 4
 5- float noiseX = randomNoise(_TimeX * _Speed + i.texcoord / float2(-213, 5.53)); 
 float noiseY = randomNoise(_TimeX * _Speed - i.texcoord / float2(213, -5.53));
 float noiseZ = randomNoise(_TimeX * _Speed + i.texcoord / float2(213, 5.53));
 sceneColor.rgb += 0.25 * float3(noiseX,noiseY,noiseZ) - 0.125;
 
- 屏幕跳跃故障(Screen Jump Glitch)- 表现   
- 思路 - 通过对UV值进行均匀梯度式扰动再进行采样。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8- half4 Frag_Vertical(VaryingsDefault i): SV_Target 
 {
 float jump = lerp(i.texcoord.y, frac(i.texcoord.y + _JumpTime), _JumpIndensity);
 half4 sceneColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, frac(float2(i.texcoord.x, jump)));
 return sceneColor;
 }
 
- 屏幕抖动故障(Screen Shake Glitch)- 表现   
- 思路 - 基本和Screen Jump类似,不过抖动不是均匀梯度式。 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9- half4 Frag_Horizontal(VaryingsDefault i): SV_Target 
 {
 float shake = (randomNoise(_Time.x, 2) - 0.5) * _ScreenShake;
 half4 sceneColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, frac(float2(i.texcoord.x + shake, i.texcoord.y)));
 return sceneColor;
 }
 
- 波动抖动故障(Wave Jitter Glitch)- 表现   
- 思路 - 利用多重的noise进行实现 
- 代码实现 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24- float4 Frag_Horizontal(VaryingsDefault i): SV_Target 
 {
 half strength = 0.0;
 
 strength = 1;
 
 strength = 0.5 + 0.5 *cos(_Time.y * _Frequency);
 
 // Prepare UV
 float uv_y = i.texcoord.y * _Resolution.y;
 float noise_wave_1 = snoise(float2(uv_y * 0.01, _Time.y * _Speed * 20)) * (strength * _Amount * 32.0);
 float noise_wave_2 = snoise(float2(uv_y * 0.02, _Time.y * _Speed * 10)) * (strength * _Amount * 4.0);
 float noise_wave_x = noise_wave_1 * noise_wave_2 / _Resolution.x;
 float uv_x = i.texcoord.x + noise_wave_x;
 float rgbSplit_uv_x = (_RGBSplit * 50 + (20.0 * strength + 1.0)) * noise_wave_x / _Resolution.x;
 // Sample RGB Color
 half4 colorG = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(uv_x, i.texcoord.y));
 half4 colorRB = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(uv_x + rgbSplit_uv_x, i.texcoord.y));
 return half4(colorRB.r, colorG.g, colorRB.b, colorRB.a + colorG.a);
 }
 
总结
故障表现的要点
- 噪声函数的选择:噪声函数是生成各式的干扰信号的源头。
- uv抖动方式的选择:将噪声函数作用于屏幕空间uv后,基于新的uv进行采样,以产生故障的抖动表现。
- 采样通道的选择:对RGB分别采样,或者选取特定通道进行采样,以实现多种风格的故障表现。
- 颜色空间的转换:善用YUV、CMY、HSV、YIQ、YCbCr 、YC1C2等空间与RGB空间之间的转换,以实现多种风格的故障表现。
参考
[1] 维基百科
[2] 高品质后处理:十种故障艺术(Glitch Art)算法的总结与实现
[3] shadertoy
关于本文
本文作者 Master Gong Sheng, 许可由 CC BY-NC 4.0.