Skip to content

Commit

Permalink
Add Sprite Flipping (#1407)
Browse files Browse the repository at this point in the history
OK, here's my attempt at sprite flipping. There are a couple of points that I need review/help on, but I think the UX is about ideal:

```rust
        .spawn(SpriteBundle {
            material: materials.add(texture_handle.into()),
            sprite: Sprite {
                // Flip the sprite along the x axis
                flip: SpriteFlip { x: true, y: false },
                ..Default::default()
            },
            ..Default::default()
        });
```

Now for the issues. The big issue is that for some reason, when flipping the UVs on the sprite, there is a light "bleeding" or whatever you call it where the UV tries to sample past the texture boundry and ends up clipping. This is only noticed when resizing the window, though. You can see a screenshot below.

![image](https://user-images.githubusercontent.com/25393315/107098172-397aaa00-67d4-11eb-8e02-c90c820cd70e.png)

I am quite baffled why the texture sampling is overrunning like it is and could use some guidance if anybody knows what might be wrong.

The other issue, which I just worked around, is that I had to remove the `#[render_resources(from_self)]` annotation from the Spritesheet because the `SpriteFlip` render resource wasn't being picked up properly in the shader when using it. I'm not sure what the cause of that was, but by removing the annotation and re-organizing the shader inputs accordingly the problem was fixed.

I'm not sure if this is the most efficient way to do this or if there is a better way, but I wanted to try it out if only for the learning experience. Let me know what you think!
  • Loading branch information
zicklag committed Mar 3, 2021
1 parent e61d792 commit 8921717
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 19 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ path = "examples/2d/contributors.rs"
name = "sprite"
path = "examples/2d/sprite.rs"

[[example]]
name = "sprite_flipping"
path = "examples/2d/sprite_flipping.rs"

[[example]]
name = "sprite_sheet"
path = "examples/2d/sprite_sheet.rs"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn build_sprite_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: CullMode::None,
cull_mode: CullMode::Back,
polygon_mode: PolygonMode::Fill,
},
..PipelineDescriptor::new(ShaderStages {
Expand Down
24 changes: 22 additions & 2 deletions crates/bevy_sprite/src/render/sprite.vert
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,32 @@ layout(set = 0, binding = 0) uniform Camera {
layout(set = 2, binding = 0) uniform Transform {
mat4 Model;
};
layout(set = 2, binding = 1) uniform Sprite_size {
layout(set = 2, binding = 1) uniform Sprite {
vec2 size;
uint flip;
};

void main() {
v_Uv = Vertex_Uv;
vec2 uv = Vertex_Uv;

// Flip the sprite if necessary by flipping the UVs

uint x_flip_bit = 1; // The X flip bit
uint y_flip_bit = 2; // The Y flip bit

// Note: Here we subtract f32::EPSILON from the flipped UV coord. This is due to reasons unknown
// to me (@zicklag ) that causes the uv's to be slightly offset and causes over/under running of
// the sprite UV sampling which is visible when resizing the screen.
float epsilon = 0.00000011920929;
if ((flip & x_flip_bit) == x_flip_bit) {
uv = vec2(1.0 - uv.x - epsilon, uv.y);
}
if ((flip & y_flip_bit) == y_flip_bit) {
uv = vec2(uv.x, 1.0 - uv.y - epsilon);
}

v_Uv = uv;

vec3 position = Vertex_Position * vec3(size, 1.0);
gl_Position = ViewProj * Model * vec4(position, 1.0);
}
50 changes: 42 additions & 8 deletions crates/bevy_sprite/src/render/sprite_sheet.vert
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,55 @@ layout(set = 2, binding = 0) uniform Transform {
};

layout(set = 2, binding = 1) uniform TextureAtlasSprite {
vec4 TextureAtlasSprite_color;
uint TextureAtlasSprite_index;
vec4 color;
uint index;
uint flip;
};

void main() {
Rect sprite_rect = Textures[TextureAtlasSprite_index];
Rect sprite_rect = Textures[index];
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions, 0.0);

// Specify the corners of the sprite
vec2 bottom_left = vec2(sprite_rect.begin.x, sprite_rect.end.y);
vec2 top_left = sprite_rect.begin;
vec2 top_right = vec2(sprite_rect.end.x, sprite_rect.begin.y);
vec2 bottom_right = sprite_rect.end;

// Flip the sprite if necessary
uint x_flip_bit = 1;
uint y_flip_bit = 2;

vec2 tmp;
if ((flip & x_flip_bit) == x_flip_bit) {
// Shuffle the corners to flip around x
tmp = bottom_left;
bottom_left = bottom_right;
bottom_right = tmp;
tmp = top_left;
top_left = top_right;
top_right = tmp;
}
if ((flip & y_flip_bit) == y_flip_bit) {
// Shuffle the corners to flip around y
tmp = bottom_left;
bottom_left = top_left;
top_left = tmp;
tmp = bottom_right;
bottom_right = top_right;
top_right = tmp;
}

vec2 atlas_positions[4] = vec2[](
vec2(sprite_rect.begin.x, sprite_rect.end.y),
sprite_rect.begin,
vec2(sprite_rect.end.x, sprite_rect.begin.y),
sprite_rect.end
bottom_left,
top_left,
top_right,
bottom_right
);

v_Uv = (atlas_positions[gl_VertexIndex]) / AtlasSize;
v_Color = TextureAtlasSprite_color;

v_Color = color;
gl_Position = ViewProj * SpriteTransform * vec4(vertex_position, 1.0);
}
41 changes: 38 additions & 3 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
use crate::ColorMaterial;
use bevy_asset::{Assets, Handle};
use bevy_core::Bytes;
use bevy_ecs::{Query, Res};
use bevy_math::Vec2;
use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid};
use bevy_render::{renderer::RenderResources, texture::Texture};
use bevy_render::{
renderer::{RenderResource, RenderResourceType, RenderResources},
texture::Texture,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, RenderResources, TypeUuid, Reflect)]
#[derive(Debug, Default, Clone, TypeUuid, Reflect, RenderResources)]
#[render_resources(from_self)]
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
#[repr(C)]
pub struct Sprite {
pub size: Vec2,
#[render_resources(ignore)]
pub flip_x: bool,
pub flip_y: bool,
pub resize_mode: SpriteResizeMode,
}

impl RenderResource for Sprite {
fn resource_type(&self) -> Option<RenderResourceType> {
Some(RenderResourceType::Buffer)
}

fn buffer_byte_len(&self) -> Option<usize> {
Some(12)
}

fn write_buffer_bytes(&self, buffer: &mut [u8]) {
// Write the size buffer
let (size_buf, flip_buf) = buffer.split_at_mut(8);
self.size.write_bytes(size_buf);

// First bit means flip x, second bit means flip y
flip_buf[0] = if self.flip_x { 0b01 } else { 0 } | if self.flip_y { 0b10 } else { 0 };
flip_buf[1] = 0;
flip_buf[2] = 0;
flip_buf[3] = 0;
}

fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}

/// Determines how `Sprite` resize should be handled
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
#[reflect_value(PartialEq, Serialize, Deserialize)]
Expand All @@ -34,6 +67,8 @@ impl Sprite {
Self {
size,
resize_mode: SpriteResizeMode::Manual,
flip_x: false,
flip_y: false,
}
}
}
Expand Down
43 changes: 38 additions & 5 deletions crates/bevy_sprite/src/texture_atlas.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::Rect;
use bevy_asset::Handle;
use bevy_core::Byteable;
use bevy_core::Bytes;
use bevy_math::Vec2;
use bevy_reflect::TypeUuid;
use bevy_render::{
color::Color,
renderer::{RenderResource, RenderResources},
renderer::{RenderResource, RenderResourceType, RenderResources},
texture::Texture,
};
use bevy_utils::HashMap;
Expand All @@ -25,24 +25,57 @@ pub struct TextureAtlas {
pub texture_handles: Option<HashMap<Handle<Texture>, usize>>,
}

#[derive(Debug, RenderResources, RenderResource, Clone)]
#[derive(Debug, Clone, RenderResources)]
#[render_resources(from_self)]
#[repr(C)]
pub struct TextureAtlasSprite {
pub color: Color,
pub index: u32,
pub flip_x: bool,
pub flip_y: bool,
}

impl RenderResource for TextureAtlasSprite {
fn resource_type(&self) -> Option<RenderResourceType> {
Some(RenderResourceType::Buffer)
}

fn buffer_byte_len(&self) -> Option<usize> {
Some(24)
}

fn write_buffer_bytes(&self, buffer: &mut [u8]) {
// Write the color buffer
let (color_buf, rest) = buffer.split_at_mut(16);
self.color.write_bytes(color_buf);

// Write the index buffer
let (index_buf, flip_buf) = rest.split_at_mut(4);
self.index.write_bytes(index_buf);

// First bit means flip x, second bit means flip y
flip_buf[0] = if self.flip_x { 0b01 } else { 0 } | if self.flip_y { 0b10 } else { 0 };
flip_buf[1] = 0;
flip_buf[2] = 0;
flip_buf[3] = 0;
}

fn texture(&self) -> Option<&Handle<Texture>> {
None
}
}

impl Default for TextureAtlasSprite {
fn default() -> Self {
Self {
index: 0,
color: Color::WHITE,
flip_x: false,
flip_y: false,
}
}
}

unsafe impl Byteable for TextureAtlasSprite {}

impl TextureAtlasSprite {
pub fn new(index: u32) -> TextureAtlasSprite {
Self {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_text/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ impl<'a> Drawable for DrawableText<'a> {
let sprite = TextureAtlasSprite {
index: tv.atlas_info.glyph_index,
color: self.sections[tv.section_index].style.color,
flip_x: false,
flip_y: false,
};

// To get the rendering right for non-one scaling factors, we need
Expand Down
1 change: 1 addition & 0 deletions examples/2d/contributors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ fn setup(
sprite: Sprite {
size: Vec2::new(1.0, 1.0) * SPRITE_SIZE,
resize_mode: SpriteResizeMode::Manual,
..Default::default()
},
material: materials.add(ColorMaterial {
color: COL_DESELECTED * col,
Expand Down
29 changes: 29 additions & 0 deletions examples/2d/sprite_flipping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use bevy::prelude::*;

fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.run();
}

fn setup(
commands: &mut Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let texture_handle = asset_server.load("branding/icon.png");
commands
.spawn(OrthographicCameraBundle::new_2d())
.spawn(SpriteBundle {
material: materials.add(texture_handle.into()),
sprite: Sprite {
// Flip the logo to the left
flip_x: true,
// And don't flip it upside-down ( the default )
flip_y: false,
..Default::default()
},
..Default::default()
});
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Example | Main | Description
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d
`sprite_flipping` | [`2d/sprite_flipping.rs`](./2d/sprite_flipping.rs) | Renders a sprite flipped along an axis
`texture_atlas` | [`2d/texture_atlas.rs`](./2d/texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites

## 3D Rendering
Expand Down

0 comments on commit 8921717

Please sign in to comment.