bevy_sprite_render/mesh2d/
color_material.rs

1use crate::{AlphaMode2d, Material2d, Material2dPlugin};
2use bevy_app::{App, Plugin};
3use bevy_asset::{embedded_asset, embedded_path, Asset, AssetApp, AssetPath, Assets, Handle};
4use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba};
5use bevy_image::Image;
6use bevy_math::{Affine2, Mat3, Vec4};
7use bevy_reflect::prelude::*;
8use bevy_render::{render_asset::RenderAssets, render_resource::*, texture::GpuImage};
9use bevy_shader::ShaderRef;
10
11#[derive(Default)]
12pub struct ColorMaterialPlugin;
13
14impl Plugin for ColorMaterialPlugin {
15    fn build(&self, app: &mut App) {
16        embedded_asset!(app, "color_material.wgsl");
17
18        app.add_plugins(Material2dPlugin::<ColorMaterial>::default())
19            .register_asset_reflect::<ColorMaterial>();
20
21        // Initialize the default material handle.
22        app.world_mut()
23            .resource_mut::<Assets<ColorMaterial>>()
24            .insert(
25                &Handle::<ColorMaterial>::default(),
26                ColorMaterial {
27                    color: Color::srgb(1.0, 0.0, 1.0),
28                    ..Default::default()
29                },
30            )
31            .unwrap();
32    }
33}
34
35/// A [2d material](Material2d) that renders [2d meshes](crate::Mesh2d) with a texture tinted by a uniform color
36#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
37#[reflect(Default, Debug, Clone)]
38#[uniform(0, ColorMaterialUniform)]
39pub struct ColorMaterial {
40    pub color: Color,
41    pub alpha_mode: AlphaMode2d,
42    pub uv_transform: Affine2,
43    #[texture(1)]
44    #[sampler(2)]
45    pub texture: Option<Handle<Image>>,
46}
47
48impl ColorMaterial {
49    /// Creates a new material from a given color
50    pub fn from_color(color: impl Into<Color>) -> Self {
51        Self::from(color.into())
52    }
53}
54
55impl Default for ColorMaterial {
56    fn default() -> Self {
57        ColorMaterial {
58            color: Color::WHITE,
59            uv_transform: Affine2::default(),
60            texture: None,
61            // TODO should probably default to AlphaMask once supported?
62            alpha_mode: AlphaMode2d::Blend,
63        }
64    }
65}
66
67impl From<Color> for ColorMaterial {
68    fn from(color: Color) -> Self {
69        ColorMaterial {
70            color,
71            alpha_mode: if color.alpha() < 1.0 {
72                AlphaMode2d::Blend
73            } else {
74                AlphaMode2d::Opaque
75            },
76            ..Default::default()
77        }
78    }
79}
80
81impl From<Handle<Image>> for ColorMaterial {
82    fn from(texture: Handle<Image>) -> Self {
83        ColorMaterial {
84            texture: Some(texture),
85            ..Default::default()
86        }
87    }
88}
89
90// NOTE: These must match the bit flags in bevy_sprite_render/src/mesh2d/color_material.wgsl!
91bitflags::bitflags! {
92    #[repr(transparent)]
93    pub struct ColorMaterialFlags: u32 {
94        const TEXTURE                    = 1 << 0;
95        /// Bitmask reserving bits for the [`AlphaMode2d`]
96        /// Values are just sequential values bitshifted into
97        /// the bitmask, and can range from 0 to 3.
98        const ALPHA_MODE_RESERVED_BITS   = Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS;
99        const ALPHA_MODE_OPAQUE          = 0 << Self::ALPHA_MODE_SHIFT_BITS;
100        const ALPHA_MODE_MASK            = 1 << Self::ALPHA_MODE_SHIFT_BITS;
101        const ALPHA_MODE_BLEND           = 2 << Self::ALPHA_MODE_SHIFT_BITS;
102        const NONE                       = 0;
103        const UNINITIALIZED              = 0xFFFF;
104    }
105}
106
107impl ColorMaterialFlags {
108    const ALPHA_MODE_MASK_BITS: u32 = 0b11;
109    const ALPHA_MODE_SHIFT_BITS: u32 = 32 - Self::ALPHA_MODE_MASK_BITS.count_ones();
110}
111
112/// The GPU representation of the uniform data of a [`ColorMaterial`].
113#[derive(Clone, Default, ShaderType)]
114pub struct ColorMaterialUniform {
115    pub color: Vec4,
116    pub uv_transform: Mat3,
117    pub flags: u32,
118    pub alpha_cutoff: f32,
119}
120
121impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
122    fn as_bind_group_shader_type(&self, _images: &RenderAssets<GpuImage>) -> ColorMaterialUniform {
123        let mut flags = ColorMaterialFlags::NONE;
124        if self.texture.is_some() {
125            flags |= ColorMaterialFlags::TEXTURE;
126        }
127
128        // Defaults to 0.5 like in 3d
129        let mut alpha_cutoff = 0.5;
130        match self.alpha_mode {
131            AlphaMode2d::Opaque => flags |= ColorMaterialFlags::ALPHA_MODE_OPAQUE,
132            AlphaMode2d::Mask(c) => {
133                alpha_cutoff = c;
134                flags |= ColorMaterialFlags::ALPHA_MODE_MASK;
135            }
136            AlphaMode2d::Blend => flags |= ColorMaterialFlags::ALPHA_MODE_BLEND,
137        };
138        ColorMaterialUniform {
139            color: LinearRgba::from(self.color).to_f32_array().into(),
140            uv_transform: self.uv_transform.into(),
141            flags: flags.bits(),
142            alpha_cutoff,
143        }
144    }
145}
146
147impl Material2d for ColorMaterial {
148    fn fragment_shader() -> ShaderRef {
149        ShaderRef::Path(
150            AssetPath::from_path_buf(embedded_path!("color_material.wgsl")).with_source("embedded"),
151        )
152    }
153
154    fn alpha_mode(&self) -> AlphaMode2d {
155        self.alpha_mode
156    }
157}