Pixel Perfect Shader


BEFORE WE BEGIN

You can find some useful snippets code snippets in my gists. And there is a complete solution. You'll need to download the enire tools folder but feel free to ignore EXAMPLES subfolder. This will give you a solution for some tricky pixel perfect effects. For example: a single pixel line that scales with screen resolution. I think the only way it can be done is if you send an exact coordinate of the line to the shader trough a vertex data. Which is what my RoundedGraphic.cs is doing. So all the shaders in that folder require that component exclusively. But this article is not about that. Well, not exactly...

OVERVIEW

In this article I'll talk about my favorite shader. Screen Space pixel perfect sampling. Which doesn't require any of the things listed above.

I use it very often and it works out of the box easily. Idea is simple: if there is a picture then it's 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 lets unpack this. Firstly, here is a link to shaders with this. A big part of it is pretty standard for any Unity UI shader. So lets focus on the part that makes this effect:

vert():

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

ComputeScreenPos(o.vertex) - This is a standard way to get screen position in [0,1] space. Multiply it by screen resolution (_ScreenParams,xy). So right upper courner 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 size of our sprite.

Worth noting that when you get a float4 value in shader, Unity standard is to have X and Y value be 1/width and 1/height, respectively. Refer to this doc. This is because 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 often we need to divide by B, we instead gonna multiply by C which equals = 1/B.

And that is basically it. 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 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 aspect ration fo the sprite. Unity Canvas has Convas Scaler Component that will help your UI look right on different screen sizes. But by having that calculation be done in shader, you can have 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.

Social:
Facebook icon
unnamed.png
Instagram icon
Asocial:
GitHub icon
unity-Connet-Logo.png
Discord icon