Adjustable Strength (Sharp) Linear Interpolation

Are you dissatisfied with forcing your pixel art game to be always displayed at an integer scale, but can’t stand unevenly sized pixels? You’re in luck! With this shader, you can keep your game looking pretty sharp (especially at high resolutions), without having to worry about ugly shimmering artifacts when the camera moves around.

You can also set the sharpness of the interpolation on the X and Y axes individually, if you, for example, wanted to have stronger interpolation on the X axis for a CRT filter.

In the demo project, I show this shader applied to a SubViewportContainer scene, where the interpolation gets disabled if the window size is an integer scale of the base resolution, and gets turned on at the sharpest possible setting otherwise.

1 is the minimum value for sharpness, and will fully blur each pixel into the next.

The highest value is dependent on the scale of the window compared to the base game resolution. If the largest integer scale that can fit into the current window size is 4, then the highest sharpness is 4. Values exceeding the largest integer scale produce some slight artifacts.

Shader code
shader_type canvas_item;

uniform bool linear_interp = false;
uniform ivec2 interp_sharpness = ivec2(1);

void fragment() {
	if (linear_interp) {
		vec2 tex_size = vec2(textureSize(TEXTURE,0)) * vec2(interp_sharpness);
		vec2 px_size = TEXTURE_PIXEL_SIZE / vec2(interp_sharpness);

		vec2 a_uv = UV - px_size;

		if (interp_sharpness.x < 1) {
			tex_size.x = vec2(textureSize(TEXTURE,0)).x * 1.0;
			px_size.x = TEXTURE_PIXEL_SIZE.x * 1.0;
			a_uv.x = UV.x;
		if (interp_sharpness.y < 1) {
			tex_size.y = vec2(textureSize(TEXTURE,0)).y * 1.0;
			px_size.y = TEXTURE_PIXEL_SIZE.y * 1.0;
			a_uv.y = UV.y;

		vec2 uv1 = a_uv + vec2(0.0, px_size.y);
		vec2 uv2 = a_uv + vec2(px_size.x, 0.0 );
		vec2 uv3 = a_uv + vec2(px_size.x, px_size.y);
		vec2 uv4 = a_uv;

		vec4 s1 = vec4(texture(TEXTURE, uv1));
		vec4 s2 = vec4(texture(TEXTURE, uv2));
		vec4 s3 = vec4(texture(TEXTURE, uv3));
		vec4 s4 = vec4(texture(TEXTURE, uv4));

		vec2 dimensions = vec2(UV) * tex_size;

		float fu = fract(dimensions.x);
		float fv = fract(dimensions.y);

		vec4 tmp1 = mix(s4, s2, fu);
		vec4 tmp2 = mix(s1, s3, fu);

		vec4 t0 = mix(tmp1, tmp2, fv);
		COLOR = t0;
filter, interpolation, pixel scaling, pixel-art, sharp filter
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.

Related shaders

Simple Clipping with Adjustable Softedge

Linear Gradient

Notify of

1 Comment
Newest Most Voted
Inline Feedbacks
View all comments
2 months ago

This works perfectly in every scaling mode, thanks so much!