bevy
bevy copied to clipboard
Texture atlases with offset don't work properly in UI
Bevy version
0.12.1 recent main (0275508 is the last recent commit where texture atlases in UI aren't broken by a different bug)
Relevant system information
AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }
SystemInfo { os: "MacOS 13.6.2 ", kernel: "22.6.0", cpu: "", core_count: "10", memory: "64.0 GiB" }
scale_factor
of 1.0
and 2.0
were tested.
What you did
I first noticed this during Bevy Jam 4 as a slight offset in some of my UI images. Below you can see an example, with adjacent sprites showing at the right and bottom edges of the E
and the right edge of the F
. This is 103x50 texture atlas with 1px of offset and padding. This seemed to get worse at the sprites get further right/down in the sheet.
Here's an example that shows the same exact TextureAtlas
both as a sprite and in the UI.
use bevy::{prelude::*, winit::WinitSettings};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_systems(Startup, setup)
.add_systems(Update, increment_atlas_index)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
commands.spawn(Camera2dBundle::default());
let texture_handle = asset_server.load("sheettest.png");
let texture_atlas = TextureAtlas::from_grid(
texture_handle,
Vec2::new(16.0, 16.0),
3,
4,
Some(Vec2::splat(16.)),
Some(Vec2::splat(40.)),
);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
commands.spawn(AtlasImageBundle {
style: Style {
width: Val::Px(256.),
height: Val::Px(256.),
..default()
},
texture_atlas: texture_atlas_handle.clone(),
..default()
});
commands.spawn(SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
transform: Transform::from_scale(Vec3::splat(16.0)),
..default()
});
}
fn increment_atlas_index(
mut atlas_images: Query<&mut UiTextureAtlasImage>,
mut atlas_sprites: Query<&mut TextureAtlasSprite>,
keyboard: Res<ButtonInput<KeyCode>>,
) {
let frames = 12;
if keyboard.just_pressed(KeyCode::Space) {
for mut atlas_image in &mut atlas_images {
atlas_image.index = (atlas_image.index + 1) % frames;
}
for mut atlas_sprite in &mut atlas_sprites {
atlas_sprite.index = (atlas_sprite.index + 1) % frames;
}
}
}
And sheettest.png
. It has exaggerated offset/padding with those areas highlighted for debugging purposes.
What went wrong
Both images should look the same. Instead, we see this:
The image in the middle is the sprite and looks correct.
Additional information
This works as expected with a texture atlas without offset/padding such as the one used in the ui_texture_atlas
example.
I believe the issues is either in
-
TextureAtlas::from_grid
not calculating size as expected. -
extract_atlas_uinodes
usingTextureAtlas::size
instead of the size of the actual texture.
I'm not really sure what a "correct" fix would be.
1
seems slightly problematic if TextureAtlas::size
is supposed to be exactly the texture size, because the offset
param is ambiguous about whether the texture has the "offset area" on its right and bottom sides as well. In practice, I would expect that it's far more common for users to have atlases with the "offset" on all sides. It would probably be worth investigating whether from_grid
can be used to load a "sub-sheet" from a larger atlas, and how that behaves in a non-ui TextureAtlasSprite
. If that needs to work, then fixing 1
feels pointless.
2
seems problematic, because after #10520, we can no longer access the actual texture size where it is needed.
So I think the right call is probably to fix 1
by adding 2. * offset
to size in TextureAtlas::from_grid
. But I am very unfamiliar with UI rendering so there may be some better solution.
There is a workaround for end users, which is to set the TextureAtlas
's size to the correct value after it is created:
let mut atlas = TextureAtlasLayout::from_grid(
UVec2::new(12, 12),
103,
50,
Some(UVec2::splat(1)),
Some(UVec2::splat(1)),
);
// Workaround for https://github.com/bevyengine/bevy/issues/11219
atlas.size = UVec2::new(1340, 651);
After #11212, fixing 2
is on the table. But it would be good to understand what TextureAtlas::size
is expected to be and if that's also something that needs fixing.
I've run into the same problem. I checked the Rect
s stored in TextureAtlas::textures
by from_grid
, the calculated areas look correct based on padding and offset - which makes sense, given that SpriteSheetBundle
renders the correct sprite for each tested index.
I did notice however that in case of AtlasImageBundle
, the original texture looks scaled to match the calculated height in TextureAtlas::size
, in other words, unwanted scaling is applied before obtaining the sprites from the texture, which explains padding and offsets not matching. If I set the tile_size
to match the texture height in TextureAtlas::from_grid
, the rendering result from AtlasImageBundle
looks identical.
Updated the repro for Bevy 0.13 / confirmed this is still an issue.
I believe the issues is either in
1. `TextureAtlas::from_grid` not calculating size as expected. 2. `extract_atlas_uinodes` using `TextureAtlas::size` instead of the size of the actual texture.
It is 2
. I worked around it by adding a fake texture_size to TextureAtlasLayout and used it here, surely enough works as expected.
I will look around to properly fix it or how bevy_sprites gets around this issue.