-
Apologies if this is more of a general graphics question and not really a Bevy question: let me know if you want me to move this somewhere else, but I am building a tilemap renderer for Bevy and I implemented it by rending the entire map layer to a single quad: pushing the required map layout info to custom fragment and vertex shaders, and then sampling the required section of the tileset texture to put exactly the right tile in the right orientation onto the quad. The technique works really well, except for the fact that I get these weird lines in between tiles. The lines kind of come and go as you pan around the map, but mostly they're pretty blatant like in the picture. It's also worse when you zoom in and out then when I leave the camera scale alone. I'm pretty certain I've got my UV calculations right because if I just output the UV instead of sampling the actual tilemap texture, it's perfectly hard lines without the weird artifacts: It looks like the problem only comes when I actually sample the texture. This is my very first custom shader ever and I'm completely new to modern APIs like Vulkan, though I've played around a teeny, tiny bit with OpenGL, so I'm pretty clueless in this sector. Any idea what's wrong? Here's my fragment shader: #version 450
// Input
layout(location = 0) in vec2 v_Uv;
// Output
layout(location = 0) out vec4 o_Color;
// Tilemap uniforms
layout(set = 2, binding = 0) uniform LdtkTilemapMaterial_scale {
float map_scale;
};
layout(set = 2, binding = 1) uniform LdtkTilemapMaterial_map_info {
uint map_width_tiles;
uint map_height_tiles;
};
layout(set = 2, binding = 2) uniform LdtkTilemapMaterial_tileset_info {
uint tileset_width_tiles;
uint tileset_height_tiles;
};
layout(set = 2, binding = 3) uniform texture2D LdtkTilemapMaterial_texture;
layout(set = 2, binding = 4) uniform sampler LdtkTilemapMaterial_texture_sampler;
struct TileInfo {
uint index;
uint flip_bits;
};
layout(set = 2, binding = 5) buffer LdtkTilemapMaterial_tiles {
TileInfo[] map_tiles;
};
void main() {
vec2 map_size = vec2(map_width_tiles, map_height_tiles);
// Get the x index of the tile in the map by rounding which square this fragment is in
uint map_tile_x = map_width_tiles - uint(floor(v_Uv.x * map_width_tiles)) - 1;
// Get the y index of the tile in the map
uint map_tile_y = uint(floor(v_Uv.y * map_height_tiles));
// combine that into our map tile vector
vec2 map_tile = vec2(map_tile_x, map_tile_y);
// Get the index of the tile in the map as counted left to right, top to bottom
uint map_tile_idx = uint(map_tile_x + (map_tile_y * map_width_tiles));
// Use that tile index to read into our map tiles buffer and get the info for the current
// tile.
TileInfo tile_info = map_tiles[map_tile_idx];
// Get the index of the tileset tile that we should fill this map tile with
uint tileset_tile_idx = tile_info.index;
// Calculate the tileset tile y value from the tileset tile index
uint tileset_tile_y = uint(floor(tileset_tile_idx / tileset_width_tiles));
// And the tileset tile x value
uint tileset_tile_x = tileset_tile_idx - tileset_tile_y * tileset_width_tiles;
// And combine that to our tileset tile vector
vec2 tileset_tile = vec2(tileset_tile_x, tileset_tile_y);
// Next calculate the size of a map tile, as a fraction of the total size of the mesh,
// which is betwen 0 and 1.
vec2 map_tile_size = vec2(1 / map_width_tiles, 1 / map_height_tiles);
// And calculate the size of a tileset tile as a fraction of its texture size
vec2 tileset_tile_size = vec2(1 / float(tileset_width_tiles), 1 / float(tileset_height_tiles));
// Flip the x UV of the whole tileset so that it lines up with our left-to-right interpretation
// of the tilesheet indexes
vec2 uv = vec2(1 - v_Uv.x, v_Uv.y);
// Get the Uv across the tile for this part of the map.
// For instance, 0, 0 for the tile_uv would mean that we need to sample the top left
// of the tile on the tilemap.
vec2 tile_uv = (uv * map_size - map_tile);
// If the flip x bit is set, flip the tile UV along the x axis
if ((tile_info.flip_bits & 1) != 0) {
tile_uv.x = 1 - tile_uv.x;
}
// And the same for the y axis
if ((tile_info.flip_bits & 2) != 0) {
tile_uv.y = 1 - tile_uv.y;
}
// Sample our fragment from the tileset texture
o_Color = texture(
sampler2D(LdtkTilemapMaterial_texture, LdtkTilemapMaterial_texture_sampler),
// The UV coordinate calculated here is the location from the tileset that we take
// our pixels. We calculate it by offsetting the UV according to the location of the
// tile in the tileset, and then adding the tile UV scaled to the size of a tilemap
// tile.
tileset_tile * tileset_tile_size + tile_uv * tileset_tile_size
);
}
For some extra background, I specifically tried to do the whole UV sampling and such on a single quad in the fragment shader so that I wouldn't get these weird gaps in the images that I got when I tried to spawn a bunch of individual Bevy sprites for every tile. I also assumed that it would be more performant to do all of the sampling in the shader and avoid having more than 4 vertices. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Praise the Lord, I just figured it out! texture.sampler = SamplerDescriptor {
min_filter: FilterMode::Nearest,
..Default::default()
}; I can't believe that worked. I've been trying to get this to work for quite some hours now. I figured it had to be some sampling error becaues the UVs looked great, so I just search the Bevy codebase for Sampler, found It's so pretty! |
Beta Was this translation helpful? Give feedback.
Praise the Lord, I just figured it out!
I can't believe that worked. I've been trying to get this to work for quite some hours now. I figured it had to be some sampling error becaues the UVs looked great, so I just search the Bevy codebase for Sampler, found
SamplerDescriptor
and then found that some filters were not set toNearest
by default.It's so pretty!