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);
}
Thank you! I’ve heard of rotsprite and thought I was going to have to figure out the implementation myself but this is a really helpful reference