Skip to content

Commit 7d7c43d

Browse files
authored
Add uv_transform to ColorMaterial (bevyengine#17879)
# Objective Implements and closes bevyengine#17515 ## Solution Add `uv_transform` to `ColorMaterial` ## Testing Create a example similar to `repeated_texture` but for `Mesh2d` and `MeshMaterial2d<ColorMaterial>` ## Showcase ![image](https://github.com/user-attachments/assets/72943b9b-59a6-489a-96a2-f9c245f0dd53) ## Migration Guide Add `uv_transform` field to constructors of `ColorMaterial`
1 parent fa85a14 commit 7d7c43d

File tree

7 files changed

+136
-2
lines changed

7 files changed

+136
-2
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,17 @@ description = "Used to test alpha modes with mesh2d"
783783
category = "2D Rendering"
784784
wasm = true
785785

786+
[[example]]
787+
name = "mesh2d_repeated_texture"
788+
path = "examples/2d/mesh2d_repeated_texture.rs"
789+
doc-scrape-examples = true
790+
791+
[package.metadata.example.mesh2d_repeated_texture]
792+
name = "Mesh2d Repeated Texture"
793+
description = "Showcase of using `uv_transform` on the `ColorMaterial` of a `Mesh2d`"
794+
category = "2D Rendering"
795+
wasm = true
796+
786797
[[example]]
787798
name = "pixel_grid_snap"
788799
path = "examples/2d/pixel_grid_snap.rs"

crates/bevy_sprite/src/mesh2d/color_material.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy_app::{App, Plugin};
33
use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle};
44
use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba};
55
use bevy_image::Image;
6-
use bevy_math::Vec4;
6+
use bevy_math::{Affine2, Mat3, Vec4};
77
use bevy_reflect::prelude::*;
88
use bevy_render::{render_asset::RenderAssets, render_resource::*, texture::GpuImage};
99

@@ -45,6 +45,7 @@ impl Plugin for ColorMaterialPlugin {
4545
pub struct ColorMaterial {
4646
pub color: Color,
4747
pub alpha_mode: AlphaMode2d,
48+
pub uv_transform: Affine2,
4849
#[texture(1)]
4950
#[sampler(2)]
5051
pub texture: Option<Handle<Image>>,
@@ -61,6 +62,7 @@ impl Default for ColorMaterial {
6162
fn default() -> Self {
6263
ColorMaterial {
6364
color: Color::WHITE,
65+
uv_transform: Affine2::default(),
6466
texture: None,
6567
// TODO should probably default to AlphaMask once supported?
6668
alpha_mode: AlphaMode2d::Blend,
@@ -117,6 +119,7 @@ impl ColorMaterialFlags {
117119
#[derive(Clone, Default, ShaderType)]
118120
pub struct ColorMaterialUniform {
119121
pub color: Vec4,
122+
pub uv_transform: Mat3,
120123
pub flags: u32,
121124
pub alpha_cutoff: f32,
122125
}
@@ -140,6 +143,7 @@ impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
140143
};
141144
ColorMaterialUniform {
142145
color: LinearRgba::from(self.color).to_f32_array().into(),
146+
uv_transform: self.uv_transform.into(),
143147
flags: flags.bits(),
144148
alpha_cutoff,
145149
}

crates/bevy_sprite/src/mesh2d/color_material.wgsl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
struct ColorMaterial {
1111
color: vec4<f32>,
12+
uv_transform: mat3x3<f32>,
1213
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
1314
flags: u32,
1415
alpha_cutoff: f32,
@@ -34,8 +35,10 @@ fn fragment(
3435
output_color = output_color * mesh.color;
3536
#endif
3637

38+
let uv = (material.uv_transform * vec3(mesh.uv, 1.0)).xy;
39+
3740
if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) {
38-
output_color = output_color * textureSample(texture, texture_sampler, mesh.uv);
41+
output_color = output_color * textureSample(texture, texture_sampler, uv);
3942
}
4043

4144
output_color = alpha_discard(material, output_color);

examples/2d/mesh2d_alpha_mode.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ fn setup(
3434
color: WHITE.into(),
3535
alpha_mode: AlphaMode2d::Opaque,
3636
texture: Some(texture_handle.clone()),
37+
..default()
3738
})),
3839
Transform::from_xyz(-400.0, 0.0, 0.0),
3940
));
@@ -43,6 +44,7 @@ fn setup(
4344
color: BLUE.into(),
4445
alpha_mode: AlphaMode2d::Opaque,
4546
texture: Some(texture_handle.clone()),
47+
..default()
4648
})),
4749
Transform::from_xyz(-300.0, 0.0, 1.0),
4850
));
@@ -52,6 +54,7 @@ fn setup(
5254
color: GREEN.into(),
5355
alpha_mode: AlphaMode2d::Opaque,
5456
texture: Some(texture_handle.clone()),
57+
..default()
5558
})),
5659
Transform::from_xyz(-200.0, 0.0, -1.0),
5760
));
@@ -67,6 +70,7 @@ fn setup(
6770
color: WHITE.into(),
6871
alpha_mode: AlphaMode2d::Mask(0.5),
6972
texture: Some(texture_handle.clone()),
73+
..default()
7074
})),
7175
Transform::from_xyz(200.0, 0.0, 0.0),
7276
));
@@ -76,6 +80,7 @@ fn setup(
7680
color: BLUE.with_alpha(0.7).into(),
7781
alpha_mode: AlphaMode2d::Blend,
7882
texture: Some(texture_handle.clone()),
83+
..default()
7984
})),
8085
Transform::from_xyz(300.0, 0.0, 1.0),
8186
));
@@ -85,6 +90,7 @@ fn setup(
8590
color: GREEN.with_alpha(0.7).into(),
8691
alpha_mode: AlphaMode2d::Blend,
8792
texture: Some(texture_handle),
93+
..default()
8894
})),
8995
Transform::from_xyz(400.0, 0.0, -1.0),
9096
));
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! By default Bevy loads images to textures that clamps the image to the edges
2+
//! This example shows how to configure it to repeat the image instead.
3+
4+
use bevy::{
5+
audio::AudioPlugin,
6+
image::{ImageAddressMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor},
7+
math::Affine2,
8+
prelude::*,
9+
};
10+
11+
/// How much to move some rectangles away from the center
12+
const RECTANGLE_OFFSET: f32 = 250.0;
13+
/// Length of the sides of the rectangle
14+
const RECTANGLE_SIDE: f32 = 200.;
15+
/// How much to move the label away from the rectangle
16+
const LABEL_OFFSET: f32 = (RECTANGLE_SIDE / 2.) + 25.;
17+
18+
fn main() {
19+
App::new()
20+
.add_plugins(DefaultPlugins.build().disable::<AudioPlugin>())
21+
.add_systems(Startup, setup)
22+
.run();
23+
}
24+
25+
fn setup(
26+
mut commands: Commands,
27+
asset_server: Res<AssetServer>,
28+
mut meshes: ResMut<Assets<Mesh>>,
29+
mut materials: ResMut<Assets<ColorMaterial>>,
30+
) {
31+
// #11111: We use a duplicated image so that it can be load with and without
32+
// settings
33+
let image_with_default_sampler =
34+
asset_server.load("textures/fantasy_ui_borders/panel-border-010.png");
35+
let image_with_repeated_sampler = asset_server.load_with_settings(
36+
"textures/fantasy_ui_borders/panel-border-010-repeated.png",
37+
|s: &mut _| {
38+
*s = ImageLoaderSettings {
39+
sampler: ImageSampler::Descriptor(ImageSamplerDescriptor {
40+
// rewriting mode to repeat image,
41+
address_mode_u: ImageAddressMode::Repeat,
42+
address_mode_v: ImageAddressMode::Repeat,
43+
..default()
44+
}),
45+
..default()
46+
}
47+
},
48+
);
49+
50+
// central rectangle with not repeated texture
51+
commands.spawn((
52+
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
53+
MeshMaterial2d(materials.add(ColorMaterial {
54+
texture: Some(image_with_default_sampler.clone()),
55+
..default()
56+
})),
57+
Transform::from_translation(Vec3::ZERO),
58+
children![(
59+
Text2d::new("Control"),
60+
Transform::from_xyz(0., LABEL_OFFSET, 0.),
61+
)],
62+
));
63+
64+
// left rectangle with repeated texture
65+
commands.spawn((
66+
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
67+
MeshMaterial2d(materials.add(ColorMaterial {
68+
texture: Some(image_with_repeated_sampler),
69+
// uv_transform used here for proportions only, but it is full Affine2
70+
// that's why you can use rotation and shift also
71+
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
72+
..default()
73+
})),
74+
Transform::from_xyz(-RECTANGLE_OFFSET, 0.0, 0.0),
75+
children![(
76+
Text2d::new("Repeat On"),
77+
Transform::from_xyz(0., LABEL_OFFSET, 0.),
78+
)],
79+
));
80+
81+
// right rectangle with scaled texture, but with default sampler.
82+
commands.spawn((
83+
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
84+
MeshMaterial2d(materials.add(ColorMaterial {
85+
// there is no sampler set, that's why
86+
// by default you see only one small image in a row/column
87+
// and other space is filled by image edge
88+
texture: Some(image_with_default_sampler),
89+
90+
// uv_transform used here for proportions only, but it is full Affine2
91+
// that's why you can use rotation and shift also
92+
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
93+
..default()
94+
})),
95+
Transform::from_xyz(RECTANGLE_OFFSET, 0.0, 0.0),
96+
children![(
97+
Text2d::new("Repeat Off"),
98+
Transform::from_xyz(0., LABEL_OFFSET, 0.),
99+
)],
100+
));
101+
102+
// camera
103+
commands.spawn((
104+
Camera2d,
105+
Transform::default().looking_at(Vec3::ZERO, Vec3::Y),
106+
));
107+
}

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Example | Description
114114
[Mesh 2D](../examples/2d/mesh2d.rs) | Renders a 2d mesh
115115
[Mesh 2D With Vertex Colors](../examples/2d/mesh2d_vertex_color_texture.rs) | Renders a 2d mesh with vertex color attributes
116116
[Mesh2d Alpha Mode](../examples/2d/mesh2d_alpha_mode.rs) | Used to test alpha modes with mesh2d
117+
[Mesh2d Repeated Texture](../examples/2d/mesh2d_repeated_texture.rs) | Showcase of using `uv_transform` on the `ColorMaterial` of a `Mesh2d`
117118
[Move Sprite](../examples/2d/move_sprite.rs) | Changes the transform of a sprite
118119
[Pixel Grid Snapping](../examples/2d/pixel_grid_snap.rs) | Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D
119120
[Sprite](../examples/2d/sprite.rs) | Renders a sprite

examples/stress_tests/bevymark.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ fn init_materials(
613613
color: Color::WHITE,
614614
texture: textures.first().cloned(),
615615
alpha_mode,
616+
..default()
616617
}));
617618

618619
// We're seeding the PRNG here to make this example deterministic for testing purposes.
@@ -625,6 +626,7 @@ fn init_materials(
625626
color: Color::srgb_u8(color_rng.r#gen(), color_rng.r#gen(), color_rng.r#gen()),
626627
texture: textures.choose(&mut texture_rng).cloned(),
627628
alpha_mode,
629+
..default()
628630
})
629631
})
630632
.take(capacity - materials.len()),

0 commit comments

Comments
 (0)