2D tilemap tile blending (texture splatting)

I’ve decided to drop the shader here. It’s kinda raw, and I don’t know if it’s optimal (it’s my first shader ever), but it’s simple and it works. Maybe someone with bigger brain than mine could improve it 🙂

Works best with seamless textures and atlases, additional textures better be the size of the original one, but probably textures with the size of a tile from the original atlas will work.

Shader code
shader_type canvas_item;

uniform float blending_strength = 0.5;
uniform float blending_length = 0.3;
uniform bool use_top_texture;
uniform bool use_bottom_texture;
uniform bool use_left_texture;
uniform bool use_right_texture;
uniform bool use_top_left_texture;
uniform bool use_top_right_texture;
uniform bool use_bottom_left_texture;
uniform bool use_bottom_right_texture;
uniform sampler2D top_texture;
uniform sampler2D bottom_texture;
uniform sampler2D left_texture;
uniform sampler2D right_texture;
uniform sampler2D top_left_texture;
uniform sampler2D top_right_texture;
uniform sampler2D bottom_left_texture;
uniform sampler2D bottom_right_texture;
uniform vec2 tile_size;
uniform vec2 atlas_size;

vec2 getPixelPosInTile(vec2 uv) {
    float exactPosX = floor(uv.x * atlas_size.x * tile_size.x);
    float exactPosY = floor(uv.y * atlas_size.y * tile_size.y);
    float relativePosX = mod(exactPosX, tile_size.x);
    float relativePosY = mod(exactPosY, tile_size.y);
    return vec2(relativePosX, relativePosY);
}

void fragment() {
    vec4 original_color = texture(TEXTURE, UV);
    vec2 pixel_pos = getPixelPosInTile(UV);
    vec2 normalized_pixel_pos = pixel_pos / tile_size;

    vec4 blended_color = vec4(0.0);
    float total_weight = 0.0;

    // Calculate factors and accumulate blended color and total weight
    if (use_top_left_texture) {
        vec4 top_left_color = texture(top_left_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, length(vec2(pixel_pos.x, pixel_pos.y) / tile_size));
        blended_color += top_left_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_top_texture) {
        vec4 top_color = texture(top_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, pixel_pos.y / tile_size.y);
        blended_color += top_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_top_right_texture) {
        vec4 top_right_color = texture(top_right_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, length(vec2(tile_size.x - pixel_pos.x, pixel_pos.y) / tile_size));
        blended_color += top_right_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_right_texture) {
        vec4 right_color = texture(right_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, (tile_size.x - pixel_pos.x) / tile_size.x);
        blended_color += right_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_bottom_right_texture) {
        vec4 bottom_right_color = texture(bottom_right_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, length(vec2(tile_size.x - pixel_pos.x, tile_size.y - pixel_pos.y) / tile_size));
        blended_color += bottom_right_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_bottom_texture) {
        vec4 bottom_color = texture(bottom_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, (tile_size.y - pixel_pos.y) / tile_size.y);
        blended_color += bottom_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_bottom_left_texture) {
        vec4 bottom_left_color = texture(bottom_left_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, length(vec2(pixel_pos.x, tile_size.y - pixel_pos.y) / tile_size));
        blended_color += bottom_left_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }
    if (use_left_texture) {
        vec4 left_color = texture(left_texture, normalized_pixel_pos);
        float factor = smoothstep(0.0, blending_length, pixel_pos.x / tile_size.x);
        blended_color += left_color * (1.0 - factor);
        total_weight += (1.0 - factor);
    }

    // Normalize the blended color by the total weight, with clamping to avoid excessive blending
    if (total_weight > 0.0) {
        blended_color /= total_weight;
        total_weight = min(total_weight, 1.0); // Clamp total weight to a maximum of 1.0
    }

    // Mix the original color with the blended color only where there is significant weight
    COLOR = mix(original_color, blended_color, blending_strength * total_weight);
}
Tags
blending, tile, tile blending
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

Hexagonal tilemap with blending

Tile Based Triangles & Rects

Tile UVs

Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments