Repeated texture overlay for tilemaps

This shader can be used to apply a repeated texture to a tilemap. It will only apply the texture to fully red parts of the tiles, so that edges can be preserved if you wish. Check out the demo project for the full set up.

Technically you can use this shader for any sprites, not just tiles, but tiles would be the most common use case.

Shader code
shader_type canvas_item;

uniform sampler2D overlay_tex: repeat_enable, filter_nearest;
uniform int texture_width = 64; // width of the texture in number of pixels
uniform int texture_height = 64; // height of the texture in number of pixels
uniform float scale = 1.0; // default 1.0 for pixel perfect result; increase/decrease to upscale/downscale
varying vec2 world_position;

uniform float red_threshold = 0.9;
uniform float non_red_threshold = 0.1;
uniform float alpha_threshold = 0.5;

void vertex(){
	// calculate the world position for use in the fragment shader
    world_position = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}

void fragment() {
	// only apply overlay_tex on the fully red parts of the original tiles
	// filter white which is (R=1,G=1,B=1) cause it would otherwise also trigger
	// also filter out alpha values for the case that red pixels are next to transparent pixels (cause godot saves the color values in transparent pixel of neighboring color pixels)
	float mix_amount = step(red_threshold, COLOR.r)
	             	* (1.0 - step(non_red_threshold, COLOR.g))
	             	* (1.0 - step(non_red_threshold, COLOR.b))
	             	* step(alpha_threshold, COLOR.a);

	// adjust X and Y world position coordinates to preserve overlay_tex aspect ratio
	vec2 adjusted_world_position = vec2(world_position.x * (1.0 / float(texture_width)), world_position.y * (1.0 / float(texture_height)));

	// sample the overlay_tex using worldPos
	vec4 overlay_color = texture(overlay_tex, adjusted_world_position * scale);

	// combine original color and overlay color together
	COLOR = mix(COLOR, overlay_color, mix_amount);
}
Live Preview
Tags
grass, grass tiles, repeated texture, seamless, tilemap
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 jess.codes

Related shaders

guest

6 Comments
Oldest
Newest Most Voted
Russell
Russell
1 year ago

this is very nice, thanks for sharing

Sebastian
Sebastian
1 year ago

I love your videos. Can you show us the pixelart water shader sometime?

Zinic
Zinic
1 year ago

If you get texture bleeding (texture is applied over a large area when a red pixel is adjacent to a transparent pixel) then I found there are a couple ways to address this:

  • If the red pixel(s) is from a tile and is adjacent to another tile that has transparent pixels, then you can fix the bleeding by padding the tiles with a colored stripe first and importing the atlas and configuring it to have 1 pixel separation within Godot.
  • Fill transparent pixels with a color set to an opacity of 20%. This will prevent bleeding onto the transparent pixel. This may be an acceptable solution if you know the color below the transparent layer and setting the color to match the color below it. This can make the opacity nearly unnoticeable.
  • Use the double texture method from the video. Set the transparent pixels to blue and then just convert it back to transparent.
Advisor
Advisor
1 year ago
Reply to  Zinic

use built-in method of Image.fix_alpha_edges()

everything is done already

EDIT: unrelated to shader (just raw image data of texture)

Last edited 1 year ago by Advisor
RKI3000
5 months ago
Reply to  Zinic

I found another fix. The core problem is that right now the shader only checks the red channel in the rbg. By checking if b and g are equal to 0.0, and a (alpha) is equal to 1.0, it fixes the problem.
The problem is that sometimes you transparent pixels, even though they have an alpha value of 0.0, can still have a r value of 1.0.

Here is a version of the shader with this fix, as well as the scale variable changed to just being the size of the texture in px (the 1/px_size formula is done for you)

shader_type canvas_item;

uniform sampler2D overlay_tex : repeat_enable, filter_nearest;
uniform float tex_scale = 90.0;
varying vec2 world_position;

void vertex() {
  world_position = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}

void fragment() {
	float scale = 1.0 / tex_scale;
	
  bool is_red = (COLOR.r == 1.0 && COLOR.b == 0.0 && COLOR.g == 0.0 && COLOR.a == 1.0);

  if (is_red) {
    vec4 overlay_color = texture(overlay_tex, world_position * scale);
    COLOR = overlay_color;
  }
}
Zinic
Zinic
5 months ago
Reply to  RKI3000

Oh that’s really good to know. I was pretty confused back then.