Pixelate Full-Screen Shader for Unity URP

A URP full-screen pixel art shader for Unity 6: large pixels, a limited number of colour steps and ordered dithering for a retro look, as one Full Screen Pass Renderer Feature material.

Also called: pixel art filter, retro shader, posterize effect, dither shader, low-res effect.

Full-screen effects URP, Unity 6000.0+ beginner MIT licensed

Use it for: Retro and lo-fi 3D games, a PS1 or handheld look, transitions, or a stylized pause screen.

Inspector Try the properties
Shader Scripts For Unity/Full Screen/Pixelate
Pixelate Drag to rotate

The preview runs a WebGL copy of the effect over a small test scene. In Unity it applies to everything your camera renders.

What it does

  • Pixel Size in screen pixels, so the look is the same at any resolution
  • Colour Steps per channel, quantised in gamma space for even banding
  • Ordered 4x4 Bayer dithering with a strength slider
  • Runs after post-processing, so it pixelates the final, tonemapped image

How it works

The shader divides the screen into blocks of Pixel Size pixels and samples the camera image once at each block's centre with point filtering, so the whole block takes one colour.

It then rounds each colour channel to the nearest of Colour Steps levels. With dithering on, a value from a 4x4 Bayer pattern nudges each block before rounding, which mixes neighbouring levels into a pattern instead of a hard band.

Setup

  1. Save the file as Pixelate.shader under Assets and create a material that uses Scripts For Unity > Full Screen > Pixelate.
  2. On your URP Renderer asset, click Add Renderer Feature > Full Screen Pass Renderer Feature and assign the material to Pass Material.
  3. Keep Injection Point on After Rendering Post Processing and Requirements on Color.

Common mistakes

  • Pixels shimmer when the camera moves: that is how screen-space pixelation behaves. For a stable look, also move the camera in whole steps of a pixel, or render to a low-resolution Render Texture instead.
  • Text becomes unreadable: put UI on a Screen Space - Overlay canvas, which draws after this effect.

Questions

Is it pixel-perfect for 2D games?
For 2D pixel art, use the 2D Pixel Perfect Camera. This shader is a stylistic filter for 3D scenes or transitions.
Why do pixels shimmer when the camera moves?
Screen-space pixelation snaps to screen blocks, so moving content crosses block edges. Moving the camera in whole-pixel steps, or rendering to a low-resolution Render Texture, gives a steadier look.
How do I keep the UI sharp?
Use a Screen Space - Overlay canvas, which is drawn after this effect.

Source

Pixelate.shader · 75 lines · ShaderLab and HLSL for the Universal Render Pipeline 17 (Unity 6)

Pixelate.shader
cpp
// Pixelate: big pixels, a limited number of colour steps and optional
// ordered dithering for a retro look. Applied with URP's Full Screen Pass
// Renderer Feature. URP 17 (Unity 6). Scripts For Unity, MIT licensed.
Shader "Scripts For Unity/Full Screen/Pixelate"
{
    Properties
    {
        _PixelSize ("Pixel Size", Range(1, 32)) = 4
        _ColorSteps ("Color Steps", Range(2, 64)) = 12
        [Toggle(_DITHER)] _Dither ("Dither", Float) = 1
        _DitherStrength ("Dither Strength", Range(0, 1)) = 0.6
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }
        ZWrite Off
        ZTest Always
        Cull Off

        Pass
        {
            Name "Pixelate"

            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #pragma shader_feature_local_fragment _DITHER
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float _PixelSize;
                float _ColorSteps;
                float _Dither;
                float _DitherStrength;
            CBUFFER_END

            float SfuBayer2(float2 a)
            {
                a = floor(a);
                return frac(a.x * 0.5 + a.y * a.y * 0.75);
            }

            // 0..15/16 in a 4x4 Bayer pattern
            float SfuBayer4(float2 a)
            {
                return SfuBayer2(0.5 * a) * 0.25 + SfuBayer2(a);
            }

            half4 Frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                // Snap to the centre of a block of Pixel Size screen pixels
                float2 cells = max(_ScreenParams.xy / _PixelSize, 1.0);
                float2 cell = floor(input.texcoord * cells);
                half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, (cell + 0.5) / cells);

                // Quantise in gamma space so the steps look even
                float3 c = LinearToSRGB(saturate(color.rgb));
                float steps = max(_ColorSteps - 1.0, 1.0);
            #if defined(_DITHER)
                c += (SfuBayer4(cell) + 1.0 / 32.0 - 0.5) * _DitherStrength / steps;
            #endif
                c = floor(c * steps + 0.5) / steps;
                color.rgb = SRGBToLinear(saturate(c));
                return color;
            }
            ENDHLSL
        }
    }
}

More shaders