Animated Pixel Art Tileset Texture Mapping (Godot 4)

This shader is a modified version of Pixel Art Tileset Texture Mapping that supports animation. 

Shader Parameters:

  • top/bottom/front/back/left/right: These are arrays of indexes, that determine the order of frame playback, limited to 10 frames but you can adjust the maximum in the shader code.   See note about indexes of 0
  • animation_speed: how fast the frame by frame animation will play

**A tile index of 0 is reserved and won’t render any tile that’s in that index (top-left corner of tileset).  This is because int uniform arrays are initialized with 0 values, and the shader is using that information to find how many frames of animation there are by counting which indexes are defined and not 0.  

Shader code
shader_type spatial;
render_mode cull_disabled;

uniform vec3 model_dimensions = vec3(1.0, 1.0, 1.0);

uniform vec2 tile_size = vec2(16.0, 16.0);
uniform float texture_scale : hint_range(0.1, 10.0) = 1.0;
uniform bool blend_tiles = false;
uniform float blend_sharpness : hint_range(0.0, 10.0) = 2.0;
uniform int top[10];
uniform int bottom[10];
uniform int front[10];
uniform int back[10];
uniform int left[10];
uniform int right[10];
uniform float animation_speed;
uniform sampler2D tileset_texture: source_color, filter_nearest, repeat_enable;

varying vec3 instance_pos;
varying mat4 model_matrix;

void vertex() {
    instance_pos = MODEL_MATRIX[3].xyz; //Position of Instance, supports MultiMeshInstances in the use of GridMaps
    model_matrix = MODEL_MATRIX;
}

int getArraySize(int[10] arr) {
    int size = 0;
    for (int i = 0; i < arr.length(); i++) {
        if (arr[i] != 0) {
            size++;
        }
		else {
			break;
		}
    }
    return size;
}

vec4 triplanar_texture(vec3 world_position, vec3 normal) {
	vec3 weights = abs(normal);
	weights = pow(weights, vec3(blend_sharpness));
	weights /= dot(weights, vec3(1.0));
	if (!blend_tiles) {
		float threshold = 0.7071;
		vec3 equal_weights = vec3(0.0, 1.0, 0.0); // Higher weight for top texture
		weights = mix(equal_weights, weights, step(threshold, max(weights.x, max(weights.y, weights.z))));
	}
	vec2 tileset_dimensions = vec2(textureSize(tileset_texture,0)) / tile_size;
	vec2 tile_uv_scale = tile_size / vec2(textureSize(tileset_texture,0));
	vec2 tile_offset[6];
	
    int frontSize = getArraySize(front);
    int backSize = getArraySize(back);
    int topSize = getArraySize(top);
    int bottomSize = getArraySize(bottom);
    int leftSize = getArraySize(left);
    int rightSize = getArraySize(right);

    int frontFrame = int(mod(TIME * animation_speed, float(frontSize)));
    int backFrame = int(mod(TIME * animation_speed, float(backSize)));
    int topFrame = int(mod(TIME * animation_speed, float(topSize)));
    int bottomFrame = int(mod(TIME * animation_speed, float(bottomSize)));
    int leftFrame = int(mod(TIME * animation_speed, float(leftSize)));
    int rightFrame = int(mod(TIME * animation_speed, float(rightSize)));

    tile_offset[0] = vec2(mod(float(front[frontFrame]), tileset_dimensions.x), floor(float(front[frontFrame]) / tileset_dimensions.x)) * tile_uv_scale;
    tile_offset[1] = vec2(mod(float(back[backFrame]), tileset_dimensions.x), floor(float(back[backFrame]) / tileset_dimensions.x)) * tile_uv_scale;
    tile_offset[2] = vec2(mod(float(top[topFrame]), tileset_dimensions.x), floor(float(top[topFrame]) / tileset_dimensions.x)) * tile_uv_scale;
    tile_offset[3] = vec2(mod(float(bottom[bottomFrame]), tileset_dimensions.x), floor(float(bottom[bottomFrame]) / tileset_dimensions.x)) * tile_uv_scale;
    tile_offset[4] = vec2(mod(float(left[leftFrame]), tileset_dimensions.x), floor(float(left[leftFrame]) / tileset_dimensions.x)) * tile_uv_scale;
    tile_offset[5] = vec2(mod(float(right[rightFrame]), tileset_dimensions.x), floor(float(right[rightFrame]) / tileset_dimensions.x)) * tile_uv_scale;
	
	float margin = 0.001;
	vec4 colors[6];
	vec2 uv;
	uv = (-world_position.zy / model_dimensions.zy + 0.5 - vec2(-((model_dimensions.z - 1.0) / (2.0 * model_dimensions.z)))) * texture_scale;
	colors[0] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[0]);
	uv = (-world_position.zy / model_dimensions.zy + 0.5 - vec2(-((model_dimensions.z - 1.0) / (2.0 * model_dimensions.z)))) * texture_scale;
	colors[1] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[1]);
	uv = (world_position.zx / model_dimensions.zx + 0.5 + vec2(-((model_dimensions.z - 1.0) / (2.0 * model_dimensions.z)), -((model_dimensions.x - 1.0) / (2.0 * model_dimensions.x)))) * texture_scale;
	colors[2] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[2]);
	uv = (world_position.zx / model_dimensions.zx + 0.5 + vec2(-((model_dimensions.z - 1.0) / (2.0 * model_dimensions.z)), -((model_dimensions.x - 1.0) / (2.0 * model_dimensions.x)))) * texture_scale;
	colors[3] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[3]);
	uv = (-world_position.xy / model_dimensions.xy + 0.5 + vec2(((model_dimensions.x - 1.0) / (2.0 * model_dimensions.x)),((model_dimensions.y - 1.0) / (2.0 * model_dimensions.y)))) * texture_scale;
	colors[4] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[4]);
	uv = (-world_position.xy / model_dimensions.xy + 0.5 + vec2(((model_dimensions.x - 1.0) / (2.0 * model_dimensions.x)),((model_dimensions.y - 1.0) / (2.0 * model_dimensions.y)))) * texture_scale;
	colors[5] = texture(tileset_texture, fract(uv) * tile_uv_scale + tile_offset[5]);
	
	vec4 color = weights.x * (normal.x > 0.0 ? colors[0] : colors[1]) +
				 weights.y * (normal.y > 0.0 ? colors[2] : colors[3]) +
				 weights.z * (normal.z > 0.0 ? colors[4] : colors[5]);
	return color;
}

void fragment() {
	vec3 world_position = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz - instance_pos;
	vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
	vec4 albedo = triplanar_texture(inverse(mat3(model_matrix)) * world_position,inverse(mat3(model_matrix)) *  world_normal);
	if (albedo.a == 0.0){
		discard;}
	ALBEDO = albedo.rgb;
}
Tags
mapping, pixel-art, tileset, triplanar
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 lefty_spurlock

Pixel Art Tileset Texture Mapping (Godot 4)

Pixel Art Tileset Texture Mapping with Trim + Animation (Godot 4)

Related shaders

Pixel Art Tileset Texture Mapping with Trim + Animation (Godot 4)

Pixel Art Tileset Texture Mapping (Godot 4)

Pixel Art Wind Sway (MeshInstance3D, Godot 4.0)

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments