shader_material_wesl/
shader_material_wesl.rs

1//! A shader that uses the WESL shading language.
2
3use bevy::{
4    mesh::MeshVertexBufferLayoutRef,
5    pbr::{MaterialPipeline, MaterialPipelineKey},
6    prelude::*,
7    reflect::TypePath,
8    render::render_resource::{
9        AsBindGroup, RenderPipelineDescriptor, SpecializedMeshPipelineError,
10    },
11    shader::{ShaderDefVal, ShaderRef},
12};
13
14/// This example uses shader source files from the assets subdirectory
15const FRAGMENT_SHADER_ASSET_PATH: &str = "shaders/custom_material.wesl";
16
17fn main() {
18    App::new()
19        .add_plugins((
20            DefaultPlugins,
21            MaterialPlugin::<CustomMaterial>::default(),
22            CustomMaterialPlugin,
23        ))
24        .add_systems(Startup, setup)
25        .add_systems(Update, update)
26        .run();
27}
28
29/// A plugin that loads the custom material shader
30pub struct CustomMaterialPlugin;
31
32/// An example utility shader that is used by the custom material
33#[expect(
34    dead_code,
35    reason = "used to kept a strong handle, shader is referenced by the material"
36)]
37#[derive(Resource)]
38struct UtilityShader(Handle<Shader>);
39
40impl Plugin for CustomMaterialPlugin {
41    fn build(&self, app: &mut App) {
42        let handle = app
43            .world_mut()
44            .resource_mut::<AssetServer>()
45            .load::<Shader>("shaders/util.wesl");
46        app.insert_resource(UtilityShader(handle));
47    }
48}
49
50/// set up a simple 3D scene
51fn setup(
52    mut commands: Commands,
53    mut meshes: ResMut<Assets<Mesh>>,
54    mut materials: ResMut<Assets<CustomMaterial>>,
55) {
56    // cube
57    commands.spawn((
58        Mesh3d(meshes.add(Cuboid::default())),
59        MeshMaterial3d(materials.add(CustomMaterial {
60            time: Vec4::ZERO,
61            party_mode: false,
62        })),
63        Transform::from_xyz(0.0, 0.5, 0.0),
64    ));
65
66    // camera
67    commands.spawn((
68        Camera3d::default(),
69        Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
70    ));
71}
72
73fn update(
74    time: Res<Time>,
75    mut query: Query<(&MeshMaterial3d<CustomMaterial>, &mut Transform)>,
76    mut materials: ResMut<Assets<CustomMaterial>>,
77    keys: Res<ButtonInput<KeyCode>>,
78) {
79    for (material, mut transform) in query.iter_mut() {
80        let material = materials.get_mut(material).unwrap();
81        material.time.x = time.elapsed_secs();
82        if keys.just_pressed(KeyCode::Space) {
83            material.party_mode = !material.party_mode;
84        }
85
86        if material.party_mode {
87            transform.rotate(Quat::from_rotation_y(0.005));
88        }
89    }
90}
91
92// This is the struct that will be passed to your shader
93#[derive(Asset, TypePath, AsBindGroup, Clone)]
94#[bind_group_data(CustomMaterialKey)]
95struct CustomMaterial {
96    // Needed for 16 bit alignment in WebGL2
97    #[uniform(0)]
98    time: Vec4,
99    party_mode: bool,
100}
101
102#[repr(C)]
103#[derive(Eq, PartialEq, Hash, Copy, Clone)]
104struct CustomMaterialKey {
105    party_mode: bool,
106}
107
108impl From<&CustomMaterial> for CustomMaterialKey {
109    fn from(material: &CustomMaterial) -> Self {
110        Self {
111            party_mode: material.party_mode,
112        }
113    }
114}
115
116impl Material for CustomMaterial {
117    fn fragment_shader() -> ShaderRef {
118        FRAGMENT_SHADER_ASSET_PATH.into()
119    }
120
121    fn specialize(
122        _pipeline: &MaterialPipeline,
123        descriptor: &mut RenderPipelineDescriptor,
124        _layout: &MeshVertexBufferLayoutRef,
125        key: MaterialPipelineKey<Self>,
126    ) -> Result<(), SpecializedMeshPipelineError> {
127        let fragment = descriptor.fragment.as_mut().unwrap();
128        fragment.shader_defs.push(ShaderDefVal::Bool(
129            "PARTY_MODE".to_string(),
130            key.bind_group_data.party_mode,
131        ));
132        Ok(())
133    }
134}