NieR:Automata Glith Effect

Thomaz Nardelli | Shader Study

Recently I played NieR:Automata and was simply amazed, so I decide to replicate one of the glitch effects that appear in the end of the game.


Shader Breakdown

First we need to breakdown the shader into small pieces, so it's easy to replicate one by one.

nier automata glitch shader

Pixelization

nier automata glitch shader

Color Separation (Damage)

nier automata glitch shader

Color Corruption and Color Separation

nier automata glitch shader

Desaturation, Lens Distortion and Chromatic Aberration

Alright, now that we have the small pieces we can start replicating one at a time.


Pixelization

It appears that the pixelization effect only happens in vertical lines, so to do that we need to sample the screen in a different resolution in the U axis.

float glitchStep = lerp(4, 32, _Rnd);
float glitchUV = round(i.uv.x * glitchStep) / glitchStep;
fixed4 glitchCol = tex2D(_MainTex, float2(glitchUV, i.uv.y));

We first get a random (from a C# script) value to change the resolution on the fly, notice how in the original shader it keep changing.

Then we posterize the UV with the ammount of steps we need.

And finally we sample the screen using our new UV (but only the U, the V is untouched) 

nier automata glitch shader

The effect is kinda strong though, to solve that we can lerp between the glitch color and the screen color.

fixed4 glitchFinal = lerp(scrCol, glitchCol, 0.25);

Let's use 0.25 for now, but later we will change to a value controled by a C# script.

nier automata glitch shader

Way better!


Color Separation

When the player take dmg in the game there is a color separation effect, this is also really simple to make, just sample the scene color 2 extra times, one for the R channel and other for the B channel, then offset by a noise teture and combine with the unmodified screen color.

fixed chrNoise = tex2D(_Noise, frac(i.uv + _Rnd2 * 10)*0.5);
fixed chrNoise2 = tex2D(_Noise, frac(i.uv + _Rnd * 10)*0.75);

First we sample the noise texture 2 times, the _Rnd and _Rnd2 are random values generated by the CPU, we do this to prevent obvious patterns from showing up.

The noise texture

Then we step those 2 textures to get a binary value and remap to -1 ~ 1.

float chrOffset = step(0.5*(chrNoise + chrNoise2), 0.5);
chrOffset = (2 * chrOffset + 1) * 0.005 * _Dmg;

The _Dmg is also calculated on the CPU, just a simple timer so it can be triggered like in the game.

This will give us a result like this one.

nier automata glitch shader

Now all we got to do is sample the texture with the 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));

nier automata glitch shader

But there is also a color separation that happens even without dmg, so let's also add that.

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));

And then we lerp both using the _Dmg variable.

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);

The step is to make the a mask for the color separation, so it's not on the whole screen.

nier automata glitch shader


Color Corruption

Most of the work for this effect is already done in the color separation, simply add a multiplier to our 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 is just a float, just remember to keep it small!

nier automata glitch shader

The exagerated effect


Lens Distortion

Finally we got to the last bit.

float aberration = pow(((length(i.uv * 2 - 1)) - (_SinTime.z)*0.1), 2);

Here we remap the uv to -1 ~ 1, then we get it's length, this will result in a circular mask

Notice that the distortion have a pulse motion in the game, so here we subtract the _SintTime to get that movement.

Now that we have the mask ready, let's get it to distort our screen.

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);

There is a small blur in the original effect, I'm doing one here by applying a different offset to an extra sample.

We now get one of those samples, desaturate it, and subtract to get the difference from the original screen color.

fixed lensAbrR = dot((lensBlur3 - scrCol), float3(0.3, 0.59, 0.11));

The chromatic aberration mask

Then all that's left is to combine everything we've done so far!

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);

And we are done!!

Here is the finished shader in action.

Comments