mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Add Sprite Flipping (#1407)
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!
This commit is contained in:
parent
e61d7920e3
commit
89217171b4
10 changed files with 178 additions and 19 deletions
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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)]
|
||||
|
@ -34,6 +67,8 @@ impl Sprite {
|
|||
Self {
|
||||
size,
|
||||
resize_mode: SpriteResizeMode::Manual,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -25,11 +25,44 @@ 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 {
|
||||
|
@ -37,12 +70,12 @@ impl Default for TextureAtlasSprite {
|
|||
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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
29
examples/2d/sprite_flipping.rs
Normal file
29
examples/2d/sprite_flipping.rs
Normal file
|
@ -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()
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue