Dithering / Screen Door Transparency

An alternative to alpha transparency using order dithering / screen-door technique

Uniforms:
Player Position: The (normalized) position of the player – from (0,0) to (1,0)
Player Influence: How strong the player affects the transparency (higher = more transparent)
Radius: How far the player’s influence goes

Intensity: How transparent the image is (higher = more transparent)

Note:
This shader includes commented-out code for a “player position” parameter. To activate this functionality, uncomment the code in the uniform secion, and in the fragment function.

Shader code
shader_type canvas_item;

// --- Uniforms --- //
//global uniform vec2 player_position;

uniform float intensity: hint_range(0.0, 1.0, 0.05) = 0.0;

//group_uniforms player;
//uniform float player_influence: hint_range(0.0, 1.0, 0.05) = 0.60;
//uniform float radius: hint_range(0.0, 1.0, 0.05) = 0.05;

// --- Constants --- //
const mat4 THRESHOLD_MATRIX = mat4(
		vec4(1.0 / 17.0,  9.0 / 17.0,  3.0 / 17.0, 11.0 / 17.0),
		vec4(13.0 / 17.0,  5.0 / 17.0, 15.0 / 17.0,  7.0 / 17.0),
		vec4(4.0 / 17.0, 12.0 / 17.0,  2.0 / 17.0, 10.0 / 17.0),
		vec4(16.0 / 17.0,  8.0 / 17.0, 14.0 / 17.0,  6.0 / 17.0));

// --- Functions --- //
void fragment() {
	vec2 uv = UV / TEXTURE_PIXEL_SIZE;
	//float player_dist = (clamp(1.0 - distance(SCREEN_UV / SCREEN_PIXEL_SIZE, player_position / SCREEN_PIXEL_SIZE) * SCREEN_PIXEL_SIZE.x / radius, 0.0, 1.0)) * player_influence * step(0.01, intensity);
	//COLOR.a *= step(0.0, THRESHOLD_MATRIX[int(uv.x) % 4][int(uv.y) % 4] - intensity - player_dist);
	COLOR.a *= step(0.0, THRESHOLD_MATRIX[int(uv.x) % 4][int(uv.y) % 4] - intensity);
}
Live Preview
Tags
dither, dithering, screen door, Transparency
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from KingToot

Related shaders

guest

3 Comments
Oldest
Newest Most Voted
SagiriHimoto
1 year ago

amazing shader!!! Works great for my pixel art game!!
I had a bit of an issue with it, specifically, the dithering effect was done in 2×2 portions, instead of affecting single pixels, but I figured out that this can be fixed by adding “/ vec2(0.5, 0.5)” at the end of the first line in fragment() function:

vec2 uv = UV / TEXTURE_PIXEL_SIZE / vec2(0.5, 0.5);
SagiriHimoto
1 year ago
Reply to  SagiriHimoto

you could probably include variables to increase and decrease pixel size, if that’s an effect people might want

ben_at_work
ben_at_work
1 month ago

This can be adapted for use on a spatial shader using screen UVs and the viewport’s size (or probably a number of other options I’m unfamiliar with):

vec2 uv = SCREEN_UV * VIEWPORT_SIZE;

This maintains access to the matrix used in the above shader which gives the consistent and aesthetic pixel falloff pattern, while keeping pixels directly tied to the screen. Mesh UVs in 3D space result in inconsistent pixel sizing which produces a lot of aliasing. As long as dithered pixels are 1 perfect screen pixel in size, the sliding that occurs when you move the camera isn’t noticeable.

float distance_mask = distance(world_vertex, CAMERA_POSITION_WORLD);
distance_mask = clamp((distance_mask - fade_start) / fade_range, 0, 1);

vec2 uv = SCREEN_UV * VIEWPORT_SIZE;

ALPHA = step(0.0, THRESHOLD_MATRIX[int(uv.x) % 4][int(uv.y) % 4] - distance_mask );