Efeito Glitch de NieR:Automata
Recentemente joguei NieR:Automata e fiquei simplesmente impressionado, então eu decidi replicar um dos efeitos de glitch que aparecem no final do jogo.
Breakdown do Shader
Primeiro precisamos quebrar o shader em pequenas partes, para que seja fácil replicar uma por uma.

Pixelização

Separação de Cor (Dano)

Corrupção de Cor e Separação de Cor

Dessaturação, Distorção de Lente e Aberração Cromática
Certo, agora que temos as pequenas partes podemos começar a replicar uma de cada vez.
Pixelização
Parece que o efeito de pixelização só acontece em linhas verticais, então para fazer isso precisamos fazer o sample da tela em uma resolução diferente no eixo U.
float glitchStep = lerp(4, 32, _Rnd);
float glitchUV = round(i.uv.x * glitchStep) / glitchStep;
fixed4 glitchCol = tex2D(_MainTex, float2(glitchUV, i.uv.y));
Primeiro pegamos um valor aleatório (de um script C#) para mudar a resolução em tempo real, note como no shader original ele fica mudando.
Depois posterizamos o UV com a quantidade de steps que precisamos.
E finalmente fazemos o sample da tela usando nosso novo UV (mas só o U, o V fica intocado)

O efeito está meio forte, porém, para resolver isso podemos fazer um lerp entre a cor do glitch e a cor da tela.
fixed4 glitchFinal = lerp(scrCol, glitchCol, 0.25);
Vamos usar 0.25 por enquanto, mas depois vamos mudar para um valor controlado por um script C#.

Bem melhor!
Separação de Cor
Quando o jogador toma dmg no jogo há um efeito de separação de cor, isso também é bem simples de fazer, basta fazer o sample da cor da cena 2 vezes extras, uma para o canal R e outra para o canal B, depois deslocar por uma textura de ruído e combinar com a cor da tela não modificada.
fixed chrNoise = tex2D(_Noise, frac(i.uv + _Rnd2 * 10)*0.5);
fixed chrNoise2 = tex2D(_Noise, frac(i.uv + _Rnd * 10)*0.75);
Primeiro fazemos o sample da textura de ruído 2 vezes, _Rnd e _Rnd2 são valores aleatórios gerados pela CPU, fazemos isso para evitar que padrões óbvios apareçam.

A textura de ruído
Depois aplicamos step nessas 2 texturas para obter um valor binário e remapear para -1 ~ 1.
float chrOffset = step(0.5*(chrNoise + chrNoise2), 0.5);
chrOffset = (2 * chrOffset + 1) * 0.005 * _Dmg;
O _Dmg também é calculado na CPU, apenas um timer simples para que ele possa ser disparado como no jogo.
Isso vai nos dar um resultado como este.

Agora tudo o que precisamos fazer é o sample da textura com os 2 offsets.
fixed3 chrColR = tex2D(_MainTex, float2(i.uv.x + chrOffset, i.uv.y));
fixed3 chrColB = tex2D(_MainTex, float2(i.uv.x - chrOffset, i.uv.y));

Mas também há uma separação de cor que acontece mesmo sem dmg, então vamos adicionar isso também.
fixed3 chrCol2R = tex2D(_MainTex, float2(i.uv.x + step(0.5*(chrNoise + chrNoise2), 0.2) * 0.005, i.uv.y));
fixed3 chrCol2B = tex2D(_MainTex, float2(i.uv.x - step(0.5*(chrNoise + chrNoise2), 0.1) * 0.005, i.uv.y));
E então fazemos o lerp dos dois usando a variável _Dmg.
fixed4 finalScrCol = fixed4(0, 0, 0, 0);
finalScrCol.r = lerp(chrCol2R.r, chrColR.r, _Dmg) - step(chrNoise2, 0.2);
finalScrCol.g = scrCol.g + step(chrNoise, 0.2);
finalScrCol.b = lerp(chrCol2B.b, chrColB.b, _Dmg) - step(chrNoise2, 0.2);
O step serve para fazer a máscara da separação de cor, para que ela não fique na tela inteira.

Corrupção de Cor
A maior parte do trabalho para esse efeito já está feita na separação de cor, basta adicionar um multiplicador ao nosso finalScrCol.
finalScrCol.r = lerp(chrCol2R.r, chrColR.r, _Dmg) - step(chrNoise2, 0.2) * _Corruption;
finalScrCol.g = scrCol.g + step(chrNoise, 0.2) * _Corruption;
finalScrCol.b = lerp(chrCol2B.b, chrColB.b, _Dmg) - step(chrNoise2, 0.2) * _Corruption;
_Corruption é apenas um float, só lembre de mantê-lo pequeno!

O efeito exagerado
Distorção de Lente
Finalmente chegamos à última parte.
float aberration = pow(((length(i.uv * 2 - 1)) - (_SinTime.z)*0.1), 2);
Aqui remapeamos o uv para -1 ~ 1, depois pegamos o comprimento dele, isso vai resultar em uma máscara circular

Note que a distorção tem um movimento de pulso no jogo, então aqui subtraímos o _SintTime para conseguir esse movimento.
Agora que temos a máscara pronta, vamos fazer ela distorcer nossa tela.
fixed4 lensBlur = tex2D(_MainTex, i.uv - (i.uv* 2 - 1) * aberration * 0.0125*2);
fixed4 lensBlur2 = tex2D(_MainTex, i.uv - (i.uv * 2 - 1) * aberration * 0.0125);
fixed4 lensBlur3 = tex2D(_MainTex, i.uv - (i.uv * 2 - 1) * aberration * 0.025);
Há um pequeno desfoque no efeito original, estou fazendo um aqui aplicando um offset diferente a um sample extra.
Agora pegamos um desses samples, dessaturamos ele, e subtraímos para obter a diferença em relação à cor original da tela.
fixed lensAbrR = dot((lensBlur3 - scrCol), float3(0.3, 0.59, 0.11));

A máscara de aberração cromática
Então tudo o que falta é combinar tudo o que fizemos até agora!
fixed4 compositeCol = lerp(finalScrCol, glitchFinal, _GlitchAmmount);
fixed4 desaturatedCol = dot((scrCol + lensBlur + lensBlur2) / 3, float3(0.3, 0.59, 0.11));
desaturatedCol += fixed4(lensAbrR*2, 0, 0, 0);
return lerp(compositeCol, desaturatedCol, _Desaturate);

E terminamos!!
Aqui está o shader finalizado em ação.
