Add uv_transform to ColorMaterial (#17879)

# 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`
This commit is contained in:
Lucas Franca 2025-02-24 18:17:26 -03:00 committed by GitHub
parent fa85a14de1
commit 7d7c43dd62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 136 additions and 2 deletions

View File

@ -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"

View File

@ -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};
@ -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>>,
@ -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,
@ -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,
}
@ -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,
}

View File

@ -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,
@ -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);

View File

@ -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),
));
@ -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),
));
@ -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),
));
@ -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),
));
@ -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),
));
@ -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),
));

View File

@ -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),
));
}

View File

@ -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

View File

@ -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.
@ -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()),