- alexlpc2015
[Shader Notes] #05 - Pixelating
Hi and welcome to my Shader Notes Series!
This is a comprehensive project with notes about shaders of nearly every common effect included (implemented in Unity). I will explain the key points and theories behind them for you to understand them thoroughly and then enjoy creating your own shaders with proficiency.
Let's start learning together!
#05 - Pixelating
Difficulty: 3/10
Points covered:
UV Tiling
Pixelating shaders can have interesting effects in games, such as a postprocessing layer that renders a 3D scene into a pixel graph. It can also serve as a foundation for more advanced shaders.
Final effect:

Setup:
Create a new material and attach it to an object.
Prepare the properties and declare them in CGPROGRAM.
//Properties
_MainTex ("Main Texture", 2D) = "white" {}
_TileTex("Tile Texture", 2D) = "white" {}
_TileSize("Tile Size", Range(0, 100)) = 10
_DisplayModeID("Display Mode", Range(0, 4)) = 0
Vertex shader: nothing much to do.
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
Process:
Having the tile size defined, we can fist get the sum of tiles along the X and Y axes. Note that _ScreenParams is a built-in unity shader variable. Its x is the width of the render target, and its y is the height.
float2 TileSum = _ScreenParams / _TileSize;
Then we can group the uv coordinates by calling the ceil function and performing a division. Doing this will make all uv values be grouped into discrete segments.
float2 uv_Mosaic = ceil(i.uv * TileSum) / TileSum;
Then, sample the main texture using the tiled uv.
fixed4 col_Mosaic = tex2D(_MainTex, uv_Mosaic);
With the uv set up, we can designate a texture for the repeating pattern of the pixel, such as a circle or a square.

We can sample the tile texture using the frac function to generate a repeating uv.
float2 uv_Tile = frac(i.uv * TileSum);
fixed4 col_Tile = tex2D(_TileTex, uv_Tile);
Then, to make the tile texture more versatile, we can sample it from different color channels for different effects.
switch (_DisplayModeID)
{
case 1:
col *= col_Tile.r;
break;
case 2:
col *= col_Tile.g;
break;
case 3:
col *= col_Tile.b;
break;
case 4:
col *= col_Tile.a;
break;
}
Complete!
Full Shader Code:
Shader "AlexLiu/Pixelate_1"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {}
_TileTex("Tile Texture", 2D) = "white" {}
_TileSize("Tile Size", Range(0, 100)) = 10
_DisplayModeID("Display Mode", Range(0, 4)) = 0
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _TileTex;
uniform int _TileSize;
uniform int _DisplayModeID;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 TileSum = _ScreenParams / _TileSize;
float2 uv_Mosaic = ceil(i.uv * TileSum) / TileSum;
fixed4 col_Mosaic = tex2D(_MainTex, uv_Mosaic);
float2 uv_Tile = frac(i.uv * TileSum);
fixed4 col_Tile = tex2D(_TileTex, uv_Tile);
fixed4 col = col_Mosaic;
switch (_DisplayModeID)
{
case 1:
col *= col_Tile.r;
break;
case 2:
col *= col_Tile.g;
break;
case 3:
col *= col_Tile.b;
break;
case 4:
col *= col_Tile.a;
break;
}
return col;
}
ENDCG
}
}
}
Thank you for reading!