How to implement this Pokemon shader in Godot?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Diet Estus

I am trying to implement the shader described here in Godot.

Basically, alternating rows of pixels composing the screen texture are pulled in opposite directions, leaving black in their place.

My native resolution is 640x360 and I have an appropriate sample texture, encoding the effect.

My shader is:

shader_type canvas_item;
render_mode unshaded;

uniform sampler2D transition_tex;
uniform float cutoff: hint_range(0,1);


void fragment()
{

	vec4 transit = texture(transition_tex, SCREEN_UV);
	vec2 direction = normalize( vec2((transit.r - 0.5) * 2.0, 0.0) ); 
	// when r = 0, direction = (-1, 0)
	// when r = 255, direction = (1, 0)

	vec4 color = texture(SCREEN_TEXTURE, SCREEN_UV + (cutoff * direction));

	if (transit.b < cutoff)
	{
	COLOR = vec4(0.0,0.0,0.0,1.0);
	}

	else
	{
	COLOR = color;
	}
}

I have this shader applied to a ColorRect which sits in a CanvasLayer that I use for my screen transitions. I animate the cutoff from 0 to 1 to achieve the effect.

I notice that when I am in fullscreen mode, the effect almost works perfectly, except that on each set of pulled rows, the rows alternate in groups of five, lighter and darker, creating horizontal bands of lighter and darker rows. (I can’t screenshot this since Godot won’t record screenshots in fullscreen mode.)

When I am not in fullscreen mode, the effect does not work. My game window simply blurs into a brownish color before the pixel rows are pulled apart.

So my question is:

How can I get this shader to work in both fullscreen and windowed mode?

:bust_in_silhouette: Reply From: MysteryGM

There is 3 bugs I see:

1.) This line vec4 transit = texture(transition_tex, SCREEN_UV); should be UV not SCREEN_UV

2.) Other bug I notice is: normalize( vec2((transit.r - 0.5) * 2.0, 0.0) )
normalize, tries to reach a radius of 1. It does not clamp the value to 1, that I think you want.
A example is vec(0.707 , 0.707) = radius 1. See: Sine, Cosine, Tangent
Edit: scroll down till you get to the interactive sin/cos wheel.

Instead use the Red channel for left, and blue for Right:
normalize( vec2(transit.r - transit.b, 0.0); much easier to do.
Edit: I mean one line red, one blue. Don’t mix the two.

3.) The brightness thing is probably from filtering on the transit texture. Because if the image gets stretched it will try to blend pixels to preserve the image.

What you want is to remove all filtering on import, also remove mips if used. Disable “Fix Alpha Border” also.

Thicker lines will also help reduce the effects of filtering and compression.

I hope this helps.

Thanks for the suggestions! Unfortunately, it’s still not working in windowed mode, and the banding is still present in full screen. I am not filtering, using mips, or fixing alpha border when I import my sample texture. Maybe I will try thicker rows on my sample texture to see if that helps.

Diet Estus | 2018-11-26 06:49

Mapping the texture to UV instead of ScreenUV, should have allowed the texture to scale with the screen.
However, the problem with this is that if the window is smaller than the texture you are “squishing” it to fit on screen.

So the ideal would be to make a line code, instead of a texture, this will allow it to fit on any screen.

If I have some free time later I will see if I can help with the formula.

MysteryGM | 2018-11-26 15:46

OK, so it is definitely sampling, but from the screen texture.
When (amount of lines/ screen.y pixels) = Integer everything is fine. But the moment the Y lines and Y pixels don’t line up it starts sampling.

shader_type canvas_item;
render_mode unshaded;

uniform float cutoff: hint_range(0,1);
uniform int Lines = 300;

void fragment()
{
	//We tile the UV map X times
	//sin(PI * Lines), because with a sin curve we get halfX as + and the other half as -
	float LineCalculation = sin(UV.y* (3.141 * float(Lines) ));
	//Because a UV ranges from 0-1 on X, we can use that to tint the line
	//floor gives us the int. 1.0 * UV = UV and 1-X is the formula to invert
	float LeftLines = floor(1.0 - LineCalculation) * (1.0-SCREEN_UV.x);
	float RightLines = floor(-LineCalculation) * -SCREEN_UV.x;
	//Because UV ranges from 0-1 it makes more sense to subtract than add 
	vec2 direction = vec2((LeftLines - RightLines), 0.0); 

	//Use a timer instead of cutoff for now
	float DubugTime = clamp(sin(TIME),0.0,1.0);
	
	//Finally bring it all togheter
    vec4 color = texture(SCREEN_TEXTURE, SCREEN_UV + ( (direction *0.5) *DubugTime) );//0.5 is speed
	COLOR = color;
	//Just overlap the already moved sprites with black ones
	if ( (LeftLines +RightLines) < DubugTime ){
		COLOR = vec4(vec3(0.0), 1.0);
	}
	
	//COLOR = vec4(vec3(LeftLines +RightLines),1.0); //Debug line math
}

One known BUG. Because Godot doesn’t have PI in the shader, you can’t get 1:1 precision. You need 2 Y pixels for every line.

Example if a screen is 1024x600 then use 300 Y lines.
You could use TextureSize() from the shader, to get the Y value and calculate the lines you need. If you want I can also add it to the shader.

MysteryGM | 2018-11-26 20:14

Thank you! I am trying to get the size in the shader, but float height = textureSize(SCREEN_TEXTURE).y; returns error “Invalid arguments for built-in function: textureSize(sampler2D)”. Same error if I input TEXTURE.

Diet Estus | 2018-11-26 23:36

That us because textureSize needs a sampler2D and a integer.

int height = textureSize(SCREEN_TEXTURE, 0).y

The reason is that texture “LODs” are just mipmaps, with custom filtering. And as such each LOD level will be half the size of the texture level before it.

LOD 0 means the texture with no LODing.

Edit: It also returns the value as a Int, just checked.

MysteryGM | 2018-11-27 22:39

Thanks for all your help, truly! Working perfectly now.

Diet Estus | 2018-11-28 03:32