HQ4X Shader (like in Emulators)

Yo! NekotoArts here, and if I’m honest, I have no idea how actually useful this is.

 

Understanding how RetroArch and stuff are able to do the HQ4X was probably the worst nightmare I’ve had to sit through.

Seriously, take a look at this:

_x0063 = TEX0.xy*TextureSize;
_fp = fract(_x0063);
_a0065 = -5.00000000E-01 + _fp;
_val0065 = vec2(float((_a0065.x > 0.00000000E+00)), float((_a0065.y > 0.00000000E+00)));
_TMP0 = _val0065 - vec2(float((_a0065.x < 0.00000000E+00)), float((_a0065.y < 0.00000000E+00)));
_quad = vec2(float(_TMP0.x), float(_TMP0.y));
_TMP1 = COMPAT_TEXTURE(Texture, TEX0.xy);
_c0069 = TEX0.xy + vec2(VARps.x, VARps.y)*vec2(float(_quad.x), float(_quad.y));
_TMP2 = COMPAT_TEXTURE(Texture, _c0069);
_c0071 = TEX0.xy + vec2(VARps.x, 0.00000000E+00)*vec2(float(_quad.x), float(_quad.y));
_TMP3 = COMPAT_TEXTURE(Texture, _c0071);
_c0073 = TEX0.xy + vec2(0.00000000E+00, VARps.y)*vec2(float(_quad.x), float(_quad.y));

Ye… I have no idea either.

So if the shader has some artifacts… have mercy on me plz.

 

Made to be used with a Sprite node.

Shader code
shader_type canvas_item;

/*
Made with a whole lot of nightmare fuel.
Logic's Under Pressure and Bobby Tarantino II Albums was in rotation during
the making process.
Hope you enjoy!

You're auto-friended to me if you're a Logic fan
RattPack all day!
*/

//image mipmap level, for base upscaling
const int ML = 0;

//equality threshold of 2 colors before forming lines
uniform float THRESHOLD = 0.1;

//anti aliasing scaling, smaller value make lines more blurry
uniform float AA_SCALE = 10.0;

//draw diagonal line connecting 2 pixels if within threshold
bool diag(inout vec4 sum, vec2 uv, vec2 p1, vec2 p2, sampler2D iChannel0, float LINE_THICKNESS) {
    vec4 v1 = texelFetch(iChannel0,ivec2(uv+vec2(p1.x,p1.y)),ML),
        v2 = texelFetch(iChannel0,ivec2(uv+vec2(p2.x,p2.y)),ML);
    if (length(v1-v2) < THRESHOLD) {
    	vec2 dir = p2-p1,
            lp = uv-(floor(uv+p1)+.5);
    	dir = normalize(vec2(dir.y,-dir.x));
        float l = clamp((LINE_THICKNESS-dot(lp,dir))*AA_SCALE,0.,1.);
        sum = mix(sum,v1,l);
    	return true;
    }
    return false;
}

void fragment(){
	//line thickness
	float LINE_THICKNESS;
	vec2 ip = UV * (1.0 / TEXTURE_PIXEL_SIZE);
	
	
		//start with nearest pixel as 'background'
		vec4 s = texelFetch(TEXTURE,ivec2(int(ip.x),int(ip.y)),ML);
		
		//draw anti aliased diagonal lines of surrounding pixels as 'foreground'
		LINE_THICKNESS = 0.4;
		if (diag(s,ip,vec2(-1,0),vec2(0,1), TEXTURE, LINE_THICKNESS)) {
			LINE_THICKNESS = 0.3;
			diag(s,ip,vec2(-1,0),vec2(1,1), TEXTURE, LINE_THICKNESS);
			diag(s,ip,vec2(-1,-1),vec2(0,1), TEXTURE, LINE_THICKNESS);
		}
		LINE_THICKNESS = 0.4;
		if (diag(s,ip,vec2(0,1),vec2(1,0), TEXTURE, LINE_THICKNESS)) {
			LINE_THICKNESS = 0.3;
			diag(s,ip,vec2(0,1),vec2(1,-1), TEXTURE, LINE_THICKNESS);
			diag(s,ip,vec2(-1,1),vec2(1,0), TEXTURE, LINE_THICKNESS);
		}
		LINE_THICKNESS = 0.4;
		if (diag(s,ip,vec2(1,0),vec2(0,-1), TEXTURE, LINE_THICKNESS)) {
			LINE_THICKNESS = 0.3;
			diag(s,ip,vec2(1,0),vec2(-1,-1), TEXTURE, LINE_THICKNESS);
			diag(s,ip,vec2(1,1),vec2(0,-1), TEXTURE, LINE_THICKNESS);
		}
		LINE_THICKNESS = 0.4;
		if (diag(s,ip,vec2(0,-1),vec2(-1,0), TEXTURE, LINE_THICKNESS)) {
			LINE_THICKNESS = 0.3;
			diag(s,ip,vec2(0,-1),vec2(-1,1), TEXTURE, LINE_THICKNESS);
			diag(s,ip,vec2(1,-1),vec2(-1,0), TEXTURE, LINE_THICKNESS);
		}
		
		COLOR = s;
}
Tags
hq4x, hqx, upscale, upscaling
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 NekotoArts

Spatial Ice Shader

Wind Waker Water – NO Textures needed!

3D Edge Detection Shader (Borderlands-Style)

Related shaders

Earthbound-like battle background shader w/scroll effect and palette cycling

RotSprite-Like Algorithm for Cleaner Pixel Art Rotation

16 Bit Color (C64-Like) with Dither

Subscribe
Notify of
guest

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
nonunknown
3 years ago

I tried here but no result, maybe it only works with sprite instead of Control,ColorRect?

Declivever
Declivever
3 years ago

Hello, used your shader in https://declivever.itch.io/wizard-jump

Stealcase
2 years ago

Hey, this seems like it could be really useful for something like an Animal Crossing Texture designer.

I think their texture editor clothing patterns uses a shader like this.

Firerabbit
1 year ago

The number of texture samples can be reduced by saving and reusing the values to improve performance.

shader_type canvas_item;

/*
Made with a whole lot of nightmare fuel.
Logic's Under Pressure and Bobby Tarantino II Albums was in rotation during
the making process.
Hope you enjoy!

You're auto-friended to me if you're a Logic fan
RattPack all day!
*/

//image mipmap level, for base upscaling
const int ML = 0;

//equality threshold of 2 colors before forming lines
uniform float THRESHOLD = 0.1;

//anti aliasing scaling, smaller value make lines more blurry
uniform float AA_SCALE = 10.0;

//draw diagonal line connecting 2 pixels if within threshold
bool diag(inout vec4 sum, vec2 uv, vec2 p1, vec2 p2, vec4 v1, vec4 v2, float LINE_THICKNESS) {
    if (length(v1-v2) < THRESHOLD) {
        vec2 dir = p2-p1,
            lp = uv-(floor(uv+p1)+.5);
        dir = normalize(vec2(dir.y,-dir.x));
        float l = clamp((LINE_THICKNESS-dot(lp,dir))*AA_SCALE,0.,1.);
        sum = mix(sum,v1,l);
        return true;
    }
    return false;
}

void fragment(){
    //line thickness
    float LINE_THICKNESS;
    vec2 ip = UV * (1.0 / TEXTURE_PIXEL_SIZE);
    
        vec4 samples[9] = {
            texelFetch(TEXTURE, ivec2(ip+vec2(-1,-1)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(0,-1)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(1,-1)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(-1,0)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(0,0)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(1,0)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(-1,1)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(0,1)),ML),
            texelFetch(TEXTURE, ivec2(ip+vec2(1,1)),ML)
            };
        //start with nearest pixel as 'background'
        vec4 s = texelFetch(TEXTURE,ivec2(int(ip.x),int(ip.y)),ML);
        
        //draw anti aliased diagonal lines of surrounding pixels as 'foreground'
        LINE_THICKNESS = 0.4;
        if (diag(s,ip,vec2(-1,0),vec2(0,1), samples[3], samples[7], LINE_THICKNESS)) {
            LINE_THICKNESS = 0.3;
            diag(s,ip,vec2(-1,0),vec2(1,1), samples[3], samples[8], LINE_THICKNESS);
            diag(s,ip,vec2(-1,-1),vec2(0,1), samples[0], samples[7], LINE_THICKNESS);
        }
        LINE_THICKNESS = 0.4;
        if (diag(s,ip,vec2(0,1),vec2(1,0), samples[7], samples[5], LINE_THICKNESS)) {
            LINE_THICKNESS = 0.3;
            diag(s,ip,vec2(0,1),vec2(1,-1), samples[7], samples[2], LINE_THICKNESS);
            diag(s,ip,vec2(-1,1),vec2(1,0), samples[6], samples[5], LINE_THICKNESS);
        }
        LINE_THICKNESS = 0.4;
        if (diag(s,ip,vec2(1,0),vec2(0,-1), samples[5], samples[1], LINE_THICKNESS)) {
            LINE_THICKNESS = 0.3;
            diag(s,ip,vec2(1,0),vec2(-1,-1), samples[5], samples[0], LINE_THICKNESS);
            diag(s,ip,vec2(1,1),vec2(0,-1), samples[8], samples[1], LINE_THICKNESS);
        }
        LINE_THICKNESS = 0.4;
        if (diag(s,ip,vec2(0,-1),vec2(-1,0), samples[1], samples[3], LINE_THICKNESS)) {
            LINE_THICKNESS = 0.3;
            diag(s,ip,vec2(0,-1),vec2(-1,1), samples[1], samples[6], LINE_THICKNESS);
            diag(s,ip,vec2(1,-1),vec2(-1,0), samples[2], samples[3], LINE_THICKNESS);
        }
        
        COLOR = s;
}