RotSprite-Like Algorithm for Cleaner Pixel Art Rotation

This shader upscales the sprite using the Scale2x algorithm in GPU.

Here’s the gist if you prefer it.

To use: Simply add a shader material to your sprite with this shader code.

If you want to put more shaders on top of this, you’ll either need to manually merge them with mine, or use back buffers with screen shaders.

It’s not perfect but it a lot better than the regular rotation, I think.

Here’s a slightly slower version but also slightly better.

 

Shader code
shader_type canvas_item;

const vec4 background = vec4(1., 1., 1., 0.);

float dist(vec4 c1, vec4 c2) {
	return (c1 == c2) ? 0.0 : abs(c1.r - c2.r) + abs(c1.g - c2.g) + abs(c1.b - c2.b);
}

bool similar(vec4 c1, vec4 c2, vec4 input) {
	return (c1 == c2 || (dist(c1, c2) <= dist(input, c2) && dist(c1, c2) <= dist(input, c1)));
}

bool different(vec4 c1, vec4 c2, vec4 input) {
	return !similar(c1, c2, input);
}


// rotsprite 2x enlargement algorithm:
// suppose we are looking at input pixel cE which is surrounded by 8 other 
// pixels:
//  cA cB cC
//  cD cE cF
//  cG cH cI
// and for that 1 input pixel cE we want to output 4 pixels oA, oB, oC, and oD:
//  oA oB
//  oC oD
vec4 scale2x(sampler2D tex, vec2 uv, vec2 pixel_size) {
	vec4 input = texture(tex, uv);

	vec4 cD = texture(tex, uv + pixel_size * vec2(-1., .0));
	cD.a = 1.0;
	vec4 cF = texture(tex, uv + pixel_size * vec2(1., .0));
	cF.a = 1.0;
	vec4 cH = texture(tex, uv + pixel_size * vec2(.0, 1.));
	cH.a = 1.0;
	vec4 cB = texture(tex, uv + pixel_size * vec2(.0, -1.));
	cB.a = 1.0;
	vec4 cA = texture(tex, uv + pixel_size * vec2(-1., -1.));
	cA.a = 1.0;
	vec4 cI = texture(tex, uv + pixel_size * vec2(1., 1.));
	cI.a = 1.0;
	vec4 cG = texture(tex, uv + pixel_size * vec2(-1., 1.));
	cG.a = 1.0;
	vec4 cC = texture(tex, uv + pixel_size * vec2(1., -1.));
	cC.a = 1.0;

	if (different(cD,cF, input)
     && different(cH,cB, input)
     && ((similar(input, cD, input) || similar(input, cH, input) || similar(input, cF, input) || similar(input, cB, input) ||
         ((different(cA, cI, input) || similar(input, cG, input) || similar(input, cC, input)) &&
          (different(cG, cC, input) || similar(input, cA, input) || similar(input, cI, input))))))
    {
		vec2 unit = uv - (floor(uv / pixel_size) * pixel_size);
		vec2 pixel_half_size = pixel_size / 2.0;
		if (unit.x < pixel_half_size.x && unit.y < pixel_half_size.y) {
			return ((similar(cB, cD, input) && ((different(input, cA, input) || different(cB, background, input)) && (different(input, cA, input) || different(input, cI, input) || different(cB, cC, input) || different(cD, cG, input)))) ? cB : input);
		}

		if (unit.x >= pixel_half_size.x && unit.y < pixel_half_size.y) {
			return ((similar(cF, cB, input) && ((different(input, cC, input) || different(cF, background, input)) && (different(input, cC, input) || different(input, cG, input) || different(cF, cI, input) || different(cB, cA, input)))) ? cF : input);
		}

		if (unit.x < pixel_half_size.x && unit.y >= pixel_half_size.y) {
			return ((similar(cD, cH, input) && ((different(input, cG, input) || different(cD, background, input)) && (different(input, cG, input) || different(input, cC, input) || different(cD, cA, input) || different(cH, cI, input)))) ? cD : input);
		}

        return ((similar(cH, cF, input) && ((different(input, cI, input) || different(cH, background, input)) && (different(input, cI, input) || different(input, cA, input) || different(cH, cG, input) || different(cF, cC, input)))) ? cH : input);
    }

	return input;
}

void fragment() {
	COLOR = scale2x(TEXTURE, UV, TEXTURE_PIXEL_SIZE);
}
Tags
pixel, pixel-art, rotation, rotsprite
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

Pixel art trail

Sub-Pixel Accurate Pixel-Sprite Filtering

Water-like wavelet

guest

0 Comments
Inline Feedbacks
View all comments