In a shader how to map UV coordinates of texture to the UV coordinates of a tile in a TileSet?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By haimat

I am writing a fragment shader for a TileSet. In the shader I have the UV coordinates of the whole texture, however, I am interested in the UV coordinates of the current tile that is being rendered. In other words: in my shader I am interested to know where the current pixel is within the current tile, not within the whole texture.

So given the overall texture size and the rectangle that defines the tile to render within that texture, and given the UV coordinates of the current pixel corresponding to the whole texture, how can I convert these UV coordinates to the “inner” UV coordinates of the current tile, but also in the range of [0.0,1.0]?

:bust_in_silhouette: Reply From: blaze_the_star

I was beginning to think this was impossible in Godot 3.2, as I’ve been looking for a way to do this too, but I think I’ve found how to do it.

shader_type canvas_item;
uniform vec2 node_size = vec2(400.0, 400.0);

varying vec2 vert;

void vertex(){
	vert = VERTEX;
}

void fragment() {
	vec2 full_uv = vert/node_size;
	COLOR.rgb = vec3(full_uv, 0.0);
}

It seems that VERTEX divided by the size of a node will give you the UV of the node. You can use this to apply some shader across all tiles of a tilemap. Setting node_size to cell_size * cell_quadrant_size will result in full_uv looping after every quadrant, so if you want a UV that stretches across the full tilemap you will have to set the quadrant to the number of tiles in the tilemap and be limited to square tilemaps. However this should be fine if you don’t mind the UV not perfectly lining up with the shape of the map.

Hope this helps!

Thanks for your reply. In the meantime I came up with my own solution, will post it here as reference.

haimat | 2021-01-17 16:11

1 Like
:bust_in_silhouette: Reply From: haimat

So I solved this now by first - in GDscript - calculate the ratio and offset values of the sprite to draw vs. the whole tile set texture. Then I instance create the shader material

var ratio = sprite.texture.get_size() / sprite.region_rect.size
var offset = sprite.region_rect.position / sprite.texture.get_size() * ratio
var material = ShaderMaterial.new()
material.shader = construction_shader  # loaded previously via preload()
material.set_shader_param("ratio", ratio)
material.set_shader_param("offset", offset)
sprite.material = material

Then in the fragment shader itself I do the following to calculate the UV coordinates mapped to the current tile:

uniform vec2 ratio = vec2(0.0);
uniform vec2 offset = vec2(0.0);

void fragment() {
	vec2 uv = UV * ratio - offset;
	// do whatever you want with the mapped UVs ...
}

Hope that helps!

Got this problem, and Google is not useful… I have workaround now.
Basically we could use VERTEX_ID, because it always has 4 vertex from 0 to 3, where 0 is the top left, 1 is bottom left, 2 is bottom right, and 3 is top right. So I made a varying variable for it, named LOCAL_UV.

shader_type canvas_item;

float circle(vec2 position, float radius, float feather)
{
	return smoothstep(radius, radius + feather, length(position - vec2(0.5)));
}

const vec2 local_uv[] = {vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)};

varying vec2 LOCAL_UV;

void vertex() {
	LOCAL_UV = local_uv[VERTEX_ID];
}
 
void fragment() {
	COLOR.rgb = vec3(LOCAL_UV, 0);
}

Basically I create array that map from VERTEX_ID to the UV that I want. Then I pass it through vertex shader so I could access it through fragment shader.
Hope it helps.