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;
}