Pixel Art Tileset Texture Mapping (Godot 4)

**You can find a version that supports animated tiles here: https://godotshaders.com/shader/animated-pixel-art-tileset-texture-mapping-godot-4/?post_id=5098&new_post=true

This shader will map a different tile to each side of a mesh based on a tileset index.  Works for uniform and non-uniform tiles including sloped tiles.  Works with GridMaps. 

Use Metric units on your preferred modelling software, haven’t tested using other units.

Shader Parameters:

  • model_dimensions: set to bounding box of model to stretch textures.  Optionally set to base tile size to repeat textures.  For instance if my base tile size is 1x1x1, but I have a tile model that is 2x1x1, I can keep this value as 1x1x1 to repeat textures, or set it to 2x1x1 to stretch textures
  • tileset_texture: the tileset you wish to use, tiles should be the same size and uniformly spaced
  • tile_size: the size of the tile in pixels
  • blend_tiles: leave false if your object has slopes
  • blend_sharpness: how sharp of a blend between tiles
  • top/bottom/front/back/left/right: the different tile indexes where 0 is the top_left of the tileset_texture

Optionally comment out the following line if you don’t want your faces to be double sided:

  • render_mode cull_disabled;
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;
uniform int bottom;
uniform int front;
uniform int back;
uniform int left;
uniform int right;
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;
}

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];
	
	tile_offset[0] = vec2(mod(float(front), tileset_dimensions.x), floor(float(front) / tileset_dimensions.x)) * tile_uv_scale;
	tile_offset[1] = vec2(mod(float(back), tileset_dimensions.x), floor(float(back) / tileset_dimensions.x)) * tile_uv_scale;
	tile_offset[2] = vec2(mod(float(top), tileset_dimensions.x), floor(float(top) / tileset_dimensions.x)) * tile_uv_scale;
	tile_offset[3] = vec2(mod(float(bottom), tileset_dimensions.x), floor(float(bottom) / tileset_dimensions.x)) * tile_uv_scale; 
	tile_offset[4] = vec2(mod(float(left), tileset_dimensions.x), floor(float(left) / tileset_dimensions.x)) * tile_uv_scale;
	tile_offset[5] = vec2(mod(float(right), tileset_dimensions.x), floor(float(right) / 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
texture mapping, tile, 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

Animated 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)

Animated 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