Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite_render/src/sprite_mesh/sprite_material.rs
9368 views
1
use core::f32;
2
3
use bevy_app::Plugin;
4
use bevy_color::{Color, ColorToComponents};
5
use bevy_image::{Image, TextureAtlas, TextureAtlasLayout};
6
use bevy_math::{vec2, Affine2, Mat3, Rect, Vec2, Vec4};
7
8
use bevy_asset::{embedded_asset, embedded_path, Asset, AssetApp, AssetPath, Handle};
9
10
use bevy_reflect::Reflect;
11
use bevy_render::{
12
render_asset::RenderAssets,
13
render_resource::{AsBindGroup, AsBindGroupShaderType, ShaderType},
14
};
15
use bevy_shader::ShaderRef;
16
use bevy_sprite::{
17
prelude::SpriteMesh, SliceScaleMode, SpriteAlphaMode, SpriteImageMode, SpriteScalingMode,
18
};
19
20
use crate::{AlphaMode2d, Material2d, Material2dPlugin};
21
use core::hash::Hash;
22
23
pub struct SpriteMaterialPlugin;
24
25
impl Plugin for SpriteMaterialPlugin {
26
fn build(&self, app: &mut bevy_app::App) {
27
embedded_asset!(app, "sprite_material.wgsl");
28
29
app.add_plugins(Material2dPlugin::<SpriteMaterial>::default())
30
.register_asset_reflect::<SpriteMaterial>();
31
}
32
}
33
34
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default, PartialEq)]
35
#[reflect(Debug, Clone)]
36
#[uniform(0, SpriteMaterialUniform)]
37
pub struct SpriteMaterial {
38
#[texture(1)]
39
#[sampler(2)]
40
pub image: Handle<Image>,
41
pub texture_atlas: Option<TextureAtlas>,
42
pub color: Color,
43
pub flip_x: bool,
44
pub flip_y: bool,
45
pub custom_size: Option<Vec2>,
46
pub rect: Option<Rect>,
47
pub image_mode: SpriteImageMode,
48
pub alpha_mode: AlphaMode2d,
49
pub anchor: Vec2,
50
pub texture_atlas_layout: Option<TextureAtlasLayout>,
51
pub texture_atlas_index: usize,
52
}
53
54
// NOTE: These must match the bit flags in bevy_sprite_render/src/sprite_mesh/sprite_materials.wgsl!
55
bitflags::bitflags! {
56
#[repr(transparent)]
57
pub struct SpriteMaterialFlags: u32 {
58
const FLIP_X = 1;
59
const FLIP_Y = 2;
60
const TILE_X = 4;
61
const TILE_Y = 8;
62
/// Bitmask reserving bits for the [`AlphaMode2d`]
63
/// Values are just sequential values bitshifted into
64
/// the bitmask, and can range from 0 to 3.
65
const ALPHA_MODE_RESERVED_BITS = Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS;
66
const ALPHA_MODE_OPAQUE = 0 << Self::ALPHA_MODE_SHIFT_BITS;
67
const ALPHA_MODE_MASK = 1 << Self::ALPHA_MODE_SHIFT_BITS;
68
const ALPHA_MODE_BLEND = 2 << Self::ALPHA_MODE_SHIFT_BITS;
69
const NONE = 0;
70
const UNINITIALIZED = 0xFFFF;
71
}
72
}
73
74
impl SpriteMaterialFlags {
75
const ALPHA_MODE_MASK_BITS: u32 = 0b11;
76
const ALPHA_MODE_SHIFT_BITS: u32 = 32 - Self::ALPHA_MODE_MASK_BITS.count_ones();
77
}
78
79
#[derive(ShaderType, Default)]
80
pub struct SpriteMaterialUniform {
81
pub color: Vec4,
82
pub flags: u32,
83
pub alpha_cutoff: f32,
84
pub vertex_scale: Vec2,
85
pub vertex_offset: Vec2,
86
pub uv_transform: Mat3,
87
88
// tile shader def
89
pub tile_stretch_value: Vec2,
90
91
// slice shader def
92
pub scale: Vec2,
93
pub min_inset: Vec2,
94
pub max_inset: Vec2,
95
pub side_stretch_value: Vec2,
96
pub center_stretch_value: Vec2,
97
}
98
99
impl AsBindGroupShaderType<SpriteMaterialUniform> for SpriteMaterial {
100
fn as_bind_group_shader_type(
101
&self,
102
images: &RenderAssets<bevy_render::texture::GpuImage>,
103
) -> SpriteMaterialUniform {
104
let Some(image) = images.get(self.image.id()) else {
105
return SpriteMaterialUniform::default();
106
};
107
108
let mut flags = SpriteMaterialFlags::NONE;
109
let mut alpha_cutoff = 0.5;
110
match self.alpha_mode {
111
AlphaMode2d::Opaque => flags |= SpriteMaterialFlags::ALPHA_MODE_OPAQUE,
112
AlphaMode2d::Mask(c) => {
113
alpha_cutoff = c;
114
flags |= SpriteMaterialFlags::ALPHA_MODE_MASK;
115
}
116
AlphaMode2d::Blend => flags |= SpriteMaterialFlags::ALPHA_MODE_BLEND,
117
};
118
119
if self.flip_x {
120
flags |= SpriteMaterialFlags::FLIP_X;
121
}
122
if self.flip_y {
123
flags |= SpriteMaterialFlags::FLIP_Y;
124
}
125
126
let mut image_size = image.size_2d().as_vec2();
127
128
let mut quad_size = image_size;
129
let mut quad_offset = Vec2::ZERO;
130
let mut uv_transform = Affine2::default();
131
132
if let Some(texture_atlas_layout) = &self.texture_atlas_layout {
133
let index = self
134
.texture_atlas_index
135
.clamp(0, texture_atlas_layout.textures.len() - 1);
136
137
let rect = texture_atlas_layout.textures[index].as_rect();
138
139
let ratio = rect.size() / image_size;
140
141
uv_transform *= Affine2::from_scale(ratio);
142
uv_transform *= Affine2::from_translation(vec2(
143
rect.min.x / rect.size().x,
144
rect.min.y / rect.size().y,
145
));
146
147
quad_size = rect.size();
148
image_size = rect.size();
149
}
150
151
// rect selects a slice of the image to render, map the uv and change the quad scale to match the rect
152
if let Some(rect) = self.rect {
153
let ratio = rect.size() / image_size;
154
155
uv_transform *= Affine2::from_scale(ratio);
156
uv_transform *= Affine2::from_translation(vec2(
157
rect.min.x / rect.size().x,
158
rect.min.y / rect.size().y,
159
));
160
161
quad_size = rect.size();
162
image_size = rect.size();
163
}
164
165
let mut tile_stretch_value = Vec2::ZERO;
166
167
let mut scale = Vec2::ZERO;
168
let mut min_inset = Vec2::ZERO;
169
let mut max_inset = Vec2::ZERO;
170
let mut side_stretch_value = Vec2::ZERO;
171
let mut center_stretch_value = Vec2::ZERO;
172
173
if let Some(custom_size) = self.custom_size {
174
match &self.image_mode {
175
SpriteImageMode::Auto => {
176
quad_size = custom_size;
177
}
178
SpriteImageMode::Scale(scaling_mode) => {
179
let quad_ratio = quad_size.x / quad_size.y;
180
let custom_ratio = custom_size.x / custom_size.y;
181
182
let fill_size = || {
183
if quad_ratio > custom_ratio {
184
vec2(custom_size.y * quad_ratio, custom_size.y)
185
} else {
186
vec2(custom_size.x, custom_size.x / quad_ratio)
187
}
188
};
189
190
let fit_size = || {
191
if quad_ratio > custom_ratio {
192
vec2(custom_size.x, custom_size.x / quad_ratio)
193
} else {
194
vec2(custom_size.y * quad_ratio, custom_size.y)
195
}
196
};
197
198
match scaling_mode {
199
// Filling requires scaling the texture and cutting out the 'overflow'
200
// which is why we need to manipulate the UV.
201
SpriteScalingMode::FillCenter => {
202
let fill_size = fill_size();
203
uv_transform *= Affine2::from_scale(custom_size / fill_size);
204
uv_transform *= Affine2::from_translation(
205
(fill_size - custom_size) * 0.5 / custom_size,
206
);
207
quad_size = custom_size;
208
}
209
SpriteScalingMode::FillStart => {
210
let fill_size = fill_size();
211
uv_transform *= Affine2::from_scale(custom_size / fill_size);
212
quad_size = custom_size;
213
}
214
SpriteScalingMode::FillEnd => {
215
let fill_size = fill_size();
216
uv_transform *= Affine2::from_scale(custom_size / fill_size);
217
uv_transform *=
218
Affine2::from_translation((fill_size - custom_size) / custom_size);
219
quad_size = custom_size;
220
}
221
222
// Fitting is easier since the whole texture will still be visible,
223
// so it's enough to just translate the quad and keep the UV as is.
224
SpriteScalingMode::FitCenter => {
225
let fit_size = fit_size();
226
quad_size = fit_size;
227
}
228
SpriteScalingMode::FitStart => {
229
let fit_size = fit_size();
230
quad_offset -= (custom_size - fit_size) * 0.5;
231
quad_size = fit_size;
232
}
233
SpriteScalingMode::FitEnd => {
234
let fit_size = fit_size();
235
quad_offset += (custom_size - fit_size) * 0.5;
236
quad_size = fit_size;
237
}
238
}
239
}
240
SpriteImageMode::Tiled {
241
tile_x,
242
tile_y,
243
stretch_value,
244
} => {
245
if *tile_x {
246
flags |= SpriteMaterialFlags::TILE_X;
247
}
248
if *tile_y {
249
flags |= SpriteMaterialFlags::TILE_Y;
250
}
251
252
// This is the [0-1] x and y of where the UV should start repeating.
253
// E.g. if the stretch_value x is 0.2 and the UV x is 0.5, it will be mapped to 0.1 (0.5 - 0.2 * 2)
254
// and then be stretched over [0, 0.2] by translating it to (0.1 / 0.2) = 0.5,
255
// so it corresponds to the center of the texture.
256
tile_stretch_value = (image_size * stretch_value) / custom_size;
257
quad_size = custom_size;
258
}
259
SpriteImageMode::Sliced(slicer) => {
260
let quad_ratio = quad_size.x / quad_size.y;
261
let custom_ratio = custom_size.x / custom_size.y;
262
263
if quad_ratio > custom_ratio {
264
scale = vec2(1.0, quad_ratio / custom_ratio);
265
} else {
266
scale = vec2(custom_ratio / quad_ratio, 1.0);
267
}
268
269
min_inset = slicer.border.min_inset / quad_size;
270
max_inset = slicer.border.max_inset / quad_size;
271
272
let corner_scale = slicer.max_corner_scale.clamp(f32::EPSILON, 1.0);
273
scale /= corner_scale;
274
275
if let SliceScaleMode::Tile { stretch_value } = slicer.sides_scale_mode {
276
side_stretch_value = stretch_value
277
* (image_size * (1.0 - max_inset - min_inset))
278
/ (custom_size * (1.0 - max_inset / scale - min_inset / scale));
279
}
280
281
if let SliceScaleMode::Tile { stretch_value } = slicer.center_scale_mode {
282
center_stretch_value = stretch_value
283
* (image_size * (1.0 - max_inset - min_inset))
284
/ (custom_size * (1.0 - max_inset / scale - min_inset / scale));
285
}
286
287
quad_size = custom_size;
288
}
289
}
290
}
291
292
quad_offset -= quad_size * self.anchor;
293
294
SpriteMaterialUniform {
295
color: self.color.to_linear().to_vec4(),
296
flags: flags.bits(),
297
alpha_cutoff,
298
vertex_scale: quad_size,
299
vertex_offset: quad_offset,
300
uv_transform: uv_transform.into(),
301
302
tile_stretch_value,
303
304
scale,
305
min_inset,
306
max_inset,
307
side_stretch_value,
308
center_stretch_value,
309
}
310
}
311
}
312
313
impl Material2d for SpriteMaterial {
314
fn vertex_shader() -> ShaderRef {
315
ShaderRef::Path(
316
AssetPath::from_path_buf(embedded_path!("sprite_material.wgsl"))
317
.with_source("embedded"),
318
)
319
}
320
321
fn fragment_shader() -> ShaderRef {
322
ShaderRef::Path(
323
AssetPath::from_path_buf(embedded_path!("sprite_material.wgsl"))
324
.with_source("embedded"),
325
)
326
}
327
328
fn alpha_mode(&self) -> AlphaMode2d {
329
self.alpha_mode
330
}
331
}
332
333
impl SpriteMaterial {
334
/// Use the [`SpriteMesh`] to build a new material.
335
pub fn from_sprite_mesh(sprite: SpriteMesh) -> Self {
336
// convert SpriteAlphaMode to AlphaMode2d.
337
// (see the comment above SpriteAlphaMode for why these are different)
338
let alpha_mode = match sprite.alpha_mode {
339
SpriteAlphaMode::Blend => AlphaMode2d::Blend,
340
SpriteAlphaMode::Opaque => AlphaMode2d::Opaque,
341
SpriteAlphaMode::Mask(x) => AlphaMode2d::Mask(x),
342
};
343
344
SpriteMaterial {
345
image: sprite.image,
346
texture_atlas: sprite.texture_atlas,
347
color: sprite.color,
348
flip_x: sprite.flip_x,
349
flip_y: sprite.flip_y,
350
custom_size: sprite.custom_size,
351
rect: sprite.rect,
352
image_mode: sprite.image_mode,
353
alpha_mode,
354
texture_atlas_layout: None,
355
texture_atlas_index: 0,
356
anchor: Vec2::ZERO,
357
}
358
}
359
}
360
361