Pixel Perfect Shader


BEFORE WE BEGIN

You can find some useful code snippets in my gists. And there is a complete solution.

OVERVIEW

In this article, I'll talk about my favourite shader. Screen Space pixel-perfect sampling.

I use it very often and it works out of the box easily. The idea is simple: if there is a picture then its pixel [x:12 y: 34] for example, will be drawn on your physical screen in the same pixel position [x:12 y: 34]. I often use it with tiling and two versions of the same texture: the second one is blurred. It allows me to get an effect like this:

Those moving panels look like they are made out of glass.

HOW TO

It may not be clear what it does, so let's unpack this. Firstly, here is a link to shaders with this. A big part of it is pretty standard for any UI shader. So let's focus on the part that makes this effect:

vert() function

  o.texcoord.xy = ComputeScreenPos(o.vertex).xy * _ScreenParams.xy * _MainTex_TexelSize.xy; 

With ComputeScreenPos(o.vertex) we get screen position in [0,1] space (Bottom left of the screen is 0.0 and top right is 1.1). Next, multiply it by screen resolution (_ScreenParams.xy). So the right upper corner will be 1920 1080 (for Full Hd screen as an example). And then multiply it by TexName_TexelSize.xy. Which is the same as diving by the size of our sprite.

Worth noting that when you get a float4 value that represents Width & Height of some sort, the Unity standard is to have X and Y value be 1/width and 1/height, respectively. Refer to this doc. This is because the division is a bit more expensive operation and dividing by something can be replaced by multiplication. For example A/B = A*(1/B); So if we need to divide by B, we instead gonna pass dB which equals = 1/B.

That lest operation (dividing by the size of our texture) tells us where to sample it for that pixel-perfect precision.


SPECIAL CASE

Some settings for a canvas will slightly change things. So if the above method doesn't work, you need to correctly calculate screenPos in frag() function:

float2 screenUV = i.screenPos.xy/i.screenPos.w;

the examples I linked to above should have it this way.


A BONUS

One more shader in the same folder is ScreenFill. It does exactly what it sounds like: it fills your screen with a provided sprite, but instead of tiling, like our previous shader, it scales it to cover every part of the screen while preserving the aspect ratio of the sprite. Unity Canvas has Canvas Scaler Component that will help your UI look right on different screen sizes. But by having that calculation be done in the shader, you can have a perfectly positioned background and on top of it, have any UI element be able to serve as a mask - to show that exact same background on top of other UI elements making it look like they are fading out at the edge.