Skip to content

Commit

Permalink
Add uv_transform to ColorMaterial (#17879)
Browse files Browse the repository at this point in the history
# Objective

Implements and closes #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`
  • Loading branch information
hukasu authored Feb 24, 2025
1 parent fa85a14 commit 7d7c43d
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 2 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,17 @@ description = "Used to test alpha modes with mesh2d"
category = "2D Rendering"
wasm = true

[[example]]
name = "mesh2d_repeated_texture"
path = "examples/2d/mesh2d_repeated_texture.rs"
doc-scrape-examples = true

[package.metadata.example.mesh2d_repeated_texture]
name = "Mesh2d Repeated Texture"
description = "Showcase of using `uv_transform` on the `ColorMaterial` of a `Mesh2d`"
category = "2D Rendering"
wasm = true

[[example]]
name = "pixel_grid_snap"
path = "examples/2d/pixel_grid_snap.rs"
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_sprite/src/mesh2d/color_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, weak_handle, Asset, AssetApp, Assets, Handle};
use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba};
use bevy_image::Image;
use bevy_math::Vec4;
use bevy_math::{Affine2, Mat3, Vec4};
use bevy_reflect::prelude::*;
use bevy_render::{render_asset::RenderAssets, render_resource::*, texture::GpuImage};

Expand Down Expand Up @@ -45,6 +45,7 @@ impl Plugin for ColorMaterialPlugin {
pub struct ColorMaterial {
pub color: Color,
pub alpha_mode: AlphaMode2d,
pub uv_transform: Affine2,
#[texture(1)]
#[sampler(2)]
pub texture: Option<Handle<Image>>,
Expand All @@ -61,6 +62,7 @@ impl Default for ColorMaterial {
fn default() -> Self {
ColorMaterial {
color: Color::WHITE,
uv_transform: Affine2::default(),
texture: None,
// TODO should probably default to AlphaMask once supported?
alpha_mode: AlphaMode2d::Blend,
Expand Down Expand Up @@ -117,6 +119,7 @@ impl ColorMaterialFlags {
#[derive(Clone, Default, ShaderType)]
pub struct ColorMaterialUniform {
pub color: Vec4,
pub uv_transform: Mat3,
pub flags: u32,
pub alpha_cutoff: f32,
}
Expand All @@ -140,6 +143,7 @@ impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
};
ColorMaterialUniform {
color: LinearRgba::from(self.color).to_f32_array().into(),
uv_transform: self.uv_transform.into(),
flags: flags.bits(),
alpha_cutoff,
}
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_sprite/src/mesh2d/color_material.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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

let uv = (material.uv_transform * vec3(mesh.uv, 1.0)).xy;

if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) {
output_color = output_color * textureSample(texture, texture_sampler, mesh.uv);
output_color = output_color * textureSample(texture, texture_sampler, uv);
}

output_color = alpha_discard(material, output_color);
Expand Down
6 changes: 6 additions & 0 deletions examples/2d/mesh2d_alpha_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fn setup(
color: WHITE.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-400.0, 0.0, 0.0),
));
Expand All @@ -43,6 +44,7 @@ fn setup(
color: BLUE.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-300.0, 0.0, 1.0),
));
Expand All @@ -52,6 +54,7 @@ fn setup(
color: GREEN.into(),
alpha_mode: AlphaMode2d::Opaque,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(-200.0, 0.0, -1.0),
));
Expand All @@ -67,6 +70,7 @@ fn setup(
color: WHITE.into(),
alpha_mode: AlphaMode2d::Mask(0.5),
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(200.0, 0.0, 0.0),
));
Expand All @@ -76,6 +80,7 @@ fn setup(
color: BLUE.with_alpha(0.7).into(),
alpha_mode: AlphaMode2d::Blend,
texture: Some(texture_handle.clone()),
..default()
})),
Transform::from_xyz(300.0, 0.0, 1.0),
));
Expand All @@ -85,6 +90,7 @@ fn setup(
color: GREEN.with_alpha(0.7).into(),
alpha_mode: AlphaMode2d::Blend,
texture: Some(texture_handle),
..default()
})),
Transform::from_xyz(400.0, 0.0, -1.0),
));
Expand Down
107 changes: 107 additions & 0 deletions examples/2d/mesh2d_repeated_texture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! By default Bevy loads images to textures that clamps the image to the edges
//! This example shows how to configure it to repeat the image instead.
use bevy::{
audio::AudioPlugin,
image::{ImageAddressMode, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor},
math::Affine2,
prelude::*,
};

/// How much to move some rectangles away from the center
const RECTANGLE_OFFSET: f32 = 250.0;
/// Length of the sides of the rectangle
const RECTANGLE_SIDE: f32 = 200.;
/// How much to move the label away from the rectangle
const LABEL_OFFSET: f32 = (RECTANGLE_SIDE / 2.) + 25.;

fn main() {
App::new()
.add_plugins(DefaultPlugins.build().disable::<AudioPlugin>())
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// #11111: We use a duplicated image so that it can be load with and without
// settings
let image_with_default_sampler =
asset_server.load("textures/fantasy_ui_borders/panel-border-010.png");
let image_with_repeated_sampler = asset_server.load_with_settings(
"textures/fantasy_ui_borders/panel-border-010-repeated.png",
|s: &mut _| {
*s = ImageLoaderSettings {
sampler: ImageSampler::Descriptor(ImageSamplerDescriptor {
// rewriting mode to repeat image,
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
..default()
}),
..default()
}
},
);

// central rectangle with not repeated texture
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(image_with_default_sampler.clone()),
..default()
})),
Transform::from_translation(Vec3::ZERO),
children![(
Text2d::new("Control"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));

// left rectangle with repeated texture
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(image_with_repeated_sampler),
// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(-RECTANGLE_OFFSET, 0.0, 0.0),
children![(
Text2d::new("Repeat On"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));

// right rectangle with scaled texture, but with default sampler.
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(RECTANGLE_SIDE, RECTANGLE_SIDE))),
MeshMaterial2d(materials.add(ColorMaterial {
// there is no sampler set, that's why
// by default you see only one small image in a row/column
// and other space is filled by image edge
texture: Some(image_with_default_sampler),

// uv_transform used here for proportions only, but it is full Affine2
// that's why you can use rotation and shift also
uv_transform: Affine2::from_scale(Vec2::new(2., 3.)),
..default()
})),
Transform::from_xyz(RECTANGLE_OFFSET, 0.0, 0.0),
children![(
Text2d::new("Repeat Off"),
Transform::from_xyz(0., LABEL_OFFSET, 0.),
)],
));

// camera
commands.spawn((
Camera2d,
Transform::default().looking_at(Vec3::ZERO, Vec3::Y),
));
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Example | Description
[Mesh 2D](../examples/2d/mesh2d.rs) | Renders a 2d mesh
[Mesh 2D With Vertex Colors](../examples/2d/mesh2d_vertex_color_texture.rs) | Renders a 2d mesh with vertex color attributes
[Mesh2d Alpha Mode](../examples/2d/mesh2d_alpha_mode.rs) | Used to test alpha modes with mesh2d
[Mesh2d Repeated Texture](../examples/2d/mesh2d_repeated_texture.rs) | Showcase of using `uv_transform` on the `ColorMaterial` of a `Mesh2d`
[Move Sprite](../examples/2d/move_sprite.rs) | Changes the transform of a sprite
[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
[Sprite](../examples/2d/sprite.rs) | Renders a sprite
Expand Down
2 changes: 2 additions & 0 deletions examples/stress_tests/bevymark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ fn init_materials(
color: Color::WHITE,
texture: textures.first().cloned(),
alpha_mode,
..default()
}));

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

0 comments on commit 7d7c43d

Please sign in to comment.