Pixel Quantization

I’ve gotten a few questions about quantization the past few weeks, so this is the pixel quantization effect pulled from my dither shader, in case only this effect is needed. Works with both textures and screen reading. Unfortunately, GodotShaders doesn’t allow you to paste multiple files, so please pay attention to the comments.

To use:

  • Add the .gdshaderinc portions to new shader include files.
  • Add the .gdshader portion to a new shader file.
  • Update the #include sections to match your filepaths.
  • Use the ColorRect shader for ColorRects, and the Sprite2D shader for Sprite2Ds.

The uniform parameters are:

  • Quantization
    • Quantize Size: Pixel resolution scale (0 is bypass)
    • Handle Scale: Determines how scaled images should display.
    • Handle Subpixels: Auto-scales the quantize size when zoom is < 1, preventing artifacts.

 

Shader code
/*
 * This should be your main shader file if using with a ColorRect.
*/

shader_type canvas_item;
// This file relies on a quantize shader include file, listed further down.
// The paths must match your file's location.
#include "res://shaders/quantize.gdshaderinc"
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, repeat_disable, filter_nearest;

void vertex() {
    g_q_size = getQuantizeSize(CANVAS_MATRIX);
    g_model_matrix = MODEL_MATRIX;
    g_world_to_clip = SCREEN_MATRIX * CANVAS_MATRIX;
    g_vertex = VERTEX;
}

void fragment() {
	COLOR = quantizeScreen(SCREEN_TEXTURE, SCREEN_UV, g_vertex);
}

/*
 * This should be your main shader file if using with a Sprite2D.
*/

shader_type canvas_item;
// This file relies on a quantize shader include file, listed further down.
// The paths must match your file's location.
#include "res://shaders/quantize.gdshaderinc"

void vertex() {
    g_q_size = getQuantizeSize(CANVAS_MATRIX);
    g_model_matrix = MODEL_MATRIX;
    g_texture_size = 1. / TEXTURE_PIXEL_SIZE;
    g_vertex = VERTEX;
    g_flat_vertex = VERTEX;
}

void fragment() {
	COLOR = quantizeTexture(TEXTURE, UV);
}

/*
 * This is the quantize shader-include file. Please make a copy in a
 * separate file, and update the #include in the file you plan to reference it in.
*/

group_uniforms Quantization;
uniform float quantize_size : hint_range(0,100, 1.0) = 1;
uniform bool handle_scale;
uniform bool handle_subpixels = true;
group_uniforms;

varying mat4 g_model_matrix;
varying mat4 g_world_to_clip;
varying vec2 g_texture_size;
varying vec2 g_vertex;
varying flat vec2 g_flat_vertex;
varying float g_q_size;

float getQuantizeSize(mat4 canvas_matrix) {
    vec2 g_zoom = vec2(length(canvas_matrix[0].xyz), length(canvas_matrix[1].xyz));
    float q_size = quantize_size;

    if (handle_subpixels && g_zoom.x < 1.) {
        q_size = round(quantize_size * (1. / g_zoom.x));
    }

    return q_size;
}

vec4 quantizeTexture(sampler2D in_texture,vec2 in_uv) {
    if (g_q_size == 0.) {
        return texture(in_texture, in_uv);
    }

    float q_size = g_q_size;

    // gets -1 if not flipped, 1 if flipped
    vec2 flipped = sign(g_flat_vertex - g_vertex);
    // gets the offset of the texture
    // adds 1 when flipped
    vec2 offset = g_flat_vertex / g_texture_size;

    vec2 uv = in_uv;
    // add offset so positioned back at 0,0 locally
    // so grid lines up
    uv *= flipped;
    uv = offset - uv;
    // scale to texture size
    uv *= g_texture_size;

    if (handle_scale) { // convert to world space
        uv = (g_model_matrix * vec4(uv, 0, 1)).xy;
    }

    // quantize to specified pixel size
    uv /= q_size;
    uv = floor(uv) + 0.5;
    uv *= q_size;

    if (handle_scale) { // convert back to local space
        uv = (inverse(g_model_matrix) * vec4(uv, 0, 1)).xy;
    }

    // normalize
    uv /= g_texture_size;
    // remove offset so pixel taken at correct location
    uv = offset - uv;
    uv *= flipped;
    return texture(in_texture, uv);
}

vec4 quantizeScreen(sampler2D in_screen_texture, vec2 in_screen_uv, vec2 in_vertex) {
    if (g_q_size == 0.) {
        return texture(in_screen_texture, in_screen_uv);
    }

    vec2 uv = (g_model_matrix * vec4(in_vertex, 0, 1)).xy;
    uv /= g_q_size;
    uv = floor(uv) + 0.5;
    uv *= g_q_size;
    uv = (g_world_to_clip * vec4(uv, 0, 1)).xy;
    uv = uv * 0.5 + 0.5;
    return texture(in_screen_texture, uv);
}
Tags
pixel, pixel-art, pixelize, quantize
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from markdibarry

Arbitrary Color Reduction and Palette Ordered Dithering

Related shaders

Sub-Pixel Accurate Pixel-Sprite Filtering

Pixel Art Tileset Texture Mapping with Trim + Animation (Godot 4)

Pixel Dissolve TextureRect

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments