Tile Identifier/Tile Position within TileSet Shader?

Godot Version

4.2.1 (and 4.3 Dev 5)

Question

Context:

  • In a 2D TileMap based game, I am adding some basic fluffy level intro animations, such as the tiles individually dropping down from the sky, or warping in one by one with a dissolve shader, to form the final map. The important point here being that each tile would discretely animate at slightly offset/different time. This normally would be an easy task to accomplish with shaders, however…

The Problem:

  • In order to have tiles individually fall or animate, they would each need a unique StartTime, either supplied directly to the shader on a per-tile basis, or calculated within the shader dynamically from some sort of tile identifying information.

  • Attempting to calculate a value based on Vertex position is invalid, as it would result in a different value per vertex, warping the tiles in the process, instead of keeping them as discrete, cohesive tiles.

  • Attempting to supply a value/uniform to the Shader Instance is non-viable due to:

    1. TileMaps reuse their shader instances for every tile of the same type.
    2. canvas_item Shaders do not support instanced uniforms at present anyways.
  • Attempting to use MODEL_MATRIX/Local coordinate (0,0) as a base does not work, as the matrix supplied to the shader is identical across all tiles outside of the Y transform being used for Y Sort ordering/The matrix does not include data for the X coordinate of the Tile in question.

  • Attempting to use INSTANCE_ID/INSTANCE_CUSTOM fails as they are not populated in this use case as far as I can tell. (Nor do I know what range the values would be in to make use of them if they are)

  • MODULATE used to be usable as a pass-through for data like this in Godot 3, but seems to no longer be accessible in the canvas_item shader in Godot 4.

Help?:

  • In short, is there any way to derive or supply an identifying value to the Shader used in the rendering of TileMaps/TileSets such that:
    • The value is unique among all rendered tiles (of the same layer).
    • But the value is the same within all vertices of the same tile.
  • Ex. Tile ID or Tile Position.

Relevant references:

  • I wrote a quick shader here to color tiles based on their X(Red)/Y(Green) coordinates via MODEL_MATRIX. As you can see in this image, the Y axis data works, but the X axis data is identical across tiles. (The floating squares in the corners are individual sprites not in the tile map used as comparison points)

Thanks for any help you can supply. The only other alternative I can think of atm is dynamically replacing the tilemap with a bunch of individual sprites during the intro animation, then replacing them with the final tilemap after the animations are over, but that seems like both a ton of excess legwork, and extremely poor performance.

1 Like

After a little more brain-scrubbing, I came up with something that works as a proof of concept at least:

  1. Pass the tile size/dimensions in as a uniform var to the Shader.
  2. Since all tiles are rendered as rectangles with the same vertex order (top-left first, counter-clockwise rotation), we can then use VERTEX_ID and the size uniform in order to compute the center of the tile.
  3. We can then use the tile center as the basis for our shader variation (Ex. compute distance to an arbitrary point)

I’m not sure if this will work with all possible TileMap/TileSet settings, since it may be possible there are some cases where tiles would be rendered other than with a simple 4 vertex rectangle, but for my purposes this does the trick.

Sample shader below that uses the tile screen-space position to set the tile color, in discrete tile-specific increments.

shader_type canvas_item;

varying vec2 tile_center;
varying vec2 tile_color;
uniform vec2 tile_size;

void vertex() {
	
	float xSize = tile_size[0];
	float ySize = tile_size[1];
	
	vec2 origin = VERTEX;
	
	if(VERTEX_ID == 0) {
		origin.x = origin.x + (xSize / 2.0);
		origin.y = origin.y + (ySize / 2.0);
	} else if(VERTEX_ID == 1) {
		origin.x = origin.x + (xSize / 2.0);
		origin.y = origin.y - (ySize / 2.0);
	} else if(VERTEX_ID == 2) {
		origin.x = origin.x - (xSize / 2.0);
		origin.y = origin.y - (ySize / 2.0);
	} else if(VERTEX_ID == 3) {
		origin.x = origin.x - (xSize / 2.0);
		origin.y = origin.y + (ySize / 2.0);
	}
	
	vec4 worldspace = (MODEL_MATRIX * vec4(origin, 0.0, 1.0));
	vec4 canvasspace = (CANVAS_MATRIX * worldspace);
	vec4 screenspace = (SCREEN_MATRIX * canvasspace);
	
	tile_center = vec2(screenspace[0], screenspace[1]);
	
	
	float min1 = -1.0;
	float max1 = 1.0;
	
	float min2 = 0.0;
	float max2 = 1.0;
	
	float __input_range = max1 - min1;
	float __output_range = max2 - min2;
	
	float valx = screenspace.x;
	tile_color[0] = min2 + __output_range * ((valx - min1) / __input_range);
	float valy = screenspace.y;
	tile_color[1] = min2 + __output_range * ((valy - min1) / __input_range);
	

}

void fragment() {
  	COLOR = texture(TEXTURE, UV) * vec4(tile_color,0.0,1.0);
}

A little more tinkering, and I was able to successfully create a prototype of the effect I was looking for, with a basic bounce ease in on each tile, driven by their distance from an arbitrary screenspace point (-1,-1).

2 Likes

For the kind of effect you want to achieve you don’t really need shaders. Here is an example I did a while ago Tilemap unique per tile "visual" offset/displace - #3 by mrcdk It’s not the same effect but you can tweak it to make it so.

I saw that topic, and considered adjusting the texture offset as an option, however, that only works for the very narrow range of vfx involving visibly moving the tile without other alteration, such as a dissolve shader or similar effects. Since my goal was to have a more holistic solution that could serve as a starting point for a wider range of effects, finding a shader driven solution was ideal.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.