extended_material/
extended_material.rs

1//! Demonstrates using a custom extension to the `StandardMaterial` to modify the results of the builtin pbr shader.
2
3use bevy::{
4    color::palettes::basic::RED,
5    pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod},
6    prelude::*,
7    render::render_resource::*,
8    shader::ShaderRef,
9};
10
11/// This example uses a shader source file from the assets subdirectory
12const SHADER_ASSET_PATH: &str = "shaders/extended_material.wgsl";
13
14fn main() {
15    App::new()
16        .add_plugins(DefaultPlugins)
17        .add_plugins(MaterialPlugin::<
18            ExtendedMaterial<StandardMaterial, MyExtension>,
19        >::default())
20        .add_systems(Startup, setup)
21        .add_systems(Update, rotate_things)
22        .run();
23}
24
25fn setup(
26    mut commands: Commands,
27    mut meshes: ResMut<Assets<Mesh>>,
28    mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, MyExtension>>>,
29) {
30    // sphere
31    commands.spawn((
32        Mesh3d(meshes.add(Sphere::new(1.0))),
33        MeshMaterial3d(materials.add(ExtendedMaterial {
34            base: StandardMaterial {
35                base_color: RED.into(),
36                // can be used in forward or deferred mode
37                opaque_render_method: OpaqueRendererMethod::Auto,
38                // in deferred mode, only the PbrInput can be modified (uvs, color and other material properties),
39                // in forward mode, the output can also be modified after lighting is applied.
40                // see the fragment shader `extended_material.wgsl` for more info.
41                // Note: to run in deferred mode, you must also add a `DeferredPrepass` component to the camera and either
42                // change the above to `OpaqueRendererMethod::Deferred` or add the `DefaultOpaqueRendererMethod` resource.
43                ..Default::default()
44            },
45            extension: MyExtension::new(1),
46        })),
47        Transform::from_xyz(0.0, 0.5, 0.0),
48    ));
49
50    // light
51    commands.spawn((
52        DirectionalLight::default(),
53        Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
54        Rotate,
55    ));
56
57    // camera
58    commands.spawn((
59        Camera3d::default(),
60        Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
61    ));
62}
63
64#[derive(Component)]
65struct Rotate;
66
67fn rotate_things(mut q: Query<&mut Transform, With<Rotate>>, time: Res<Time>) {
68    for mut t in &mut q {
69        t.rotate_y(time.delta_secs());
70    }
71}
72
73#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default)]
74struct MyExtension {
75    // We need to ensure that the bindings of the base material and the extension do not conflict,
76    // so we start from binding slot 100, leaving slots 0-99 for the base material.
77    #[uniform(100)]
78    quantize_steps: u32,
79    // Web examples WebGL2 support: structs must be 16 byte aligned.
80    #[cfg(feature = "webgl2")]
81    #[uniform(100)]
82    _webgl2_padding_8b: u32,
83    #[cfg(feature = "webgl2")]
84    #[uniform(100)]
85    _webgl2_padding_12b: u32,
86    #[cfg(feature = "webgl2")]
87    #[uniform(100)]
88    _webgl2_padding_16b: u32,
89}
90impl MyExtension {
91    fn new(quantize_steps: u32) -> Self {
92        Self {
93            quantize_steps,
94            ..default()
95        }
96    }
97}
98
99impl MaterialExtension for MyExtension {
100    fn fragment_shader() -> ShaderRef {
101        SHADER_ASSET_PATH.into()
102    }
103
104    fn deferred_fragment_shader() -> ShaderRef {
105        SHADER_ASSET_PATH.into()
106    }
107}