docs/bevy/examples/shader/extended_material_bindless.rs 5.5 K · 154 lines · raw · history

1 //! Demonstrates bindless `ExtendedMaterial`.
2
3 use std::f32::consts::FRAC_PI_2;
4
5 use bevy::{
6 color::palettes::{css::RED, tailwind::GRAY_600},
7 mesh::{SphereKind, SphereMeshBuilder},
8 pbr::{ExtendedMaterial, MaterialExtension, MeshMaterial3d},
9 prelude::*,
10 render::render_resource::{AsBindGroup, ShaderType},
11 shader::ShaderRef,
12 utils::default,
13 };
14
15 /// The path to the example material shader.
16 static SHADER_ASSET_PATH: &str = "shaders/extended_material_bindless.wesl";
17
18 /// The example bindless material extension.
19 ///
20 /// As usual for material extensions, we need to avoid conflicting with both the
21 /// binding numbers and bindless indices of the [`StandardMaterial`], so we
22 /// start both values at 100 and 50 respectively.
23 ///
24 /// The `#[data(50, ExampleBindlessExtensionUniform, binding_array(101))]`
25 /// attribute specifies that the plain old data
26 /// [`ExampleBindlessExtensionUniform`] will be placed into an array with
27 /// binding 101 and that the index referencing it will be stored in slot 50 of the `ExampleBindlessExtendedMaterialIndices` structure. (See below or lookup the shader for the
28 /// definition of that structure.) That corresponds to the following shader
29 /// declaration:
30 ///
31 /// ```wgsl
32 /// @group(#{MATERIAL_BIND_GROUP}) @binding(101)
33 /// var<storage> example_extended_material: array<ExampleBindlessExtendedMaterial>;
34 /// ```
35 ///
36 /// The `#[bindless(index_table(range(50..53), binding(100)))]` attribute
37 /// specifies that this material extension should be bindless. The `range`
38 /// subattribute specifies that this material extension should have its own
39 /// index table covering bindings 50, 51, and 52. The `binding` subattribute
40 /// specifies that the extended material index table should be bound to binding
41 /// 100. This corresponds to the following shader declarations:
42 ///
43 /// ```wgsl
44 /// struct ExampleBindlessExtendedMaterialIndices {
45 /// material: u32, // 50
46 /// modulate_texture: u32, // 51
47 /// modulate_texture_sampler: u32, // 52
48 /// }
49 ///
50 /// @group(#{MATERIAL_BIND_GROUP}) @binding(100)
51 /// var<storage> example_extended_material_indices: array<ExampleBindlessExtendedMaterialIndices>;
52 /// ```
53 ///
54 /// We need to use the `index_table` subattribute because the
55 /// [`StandardMaterial`] bindless index table is bound to binding 0 by default.
56 /// Thus we need to specify a different binding so that our extended bindless
57 /// index table doesn't conflict.
58 #[derive(Asset, Clone, Reflect, AsBindGroup)]
59 #[data(50, ExampleBindlessExtensionUniform, binding_array(101))]
60 #[bindless(index_table(range(50..53), binding(100)))]
61 struct ExampleBindlessExtension {
62 /// The color we're going to multiply the base color with.
63 modulate_color: Color,
64 /// The image we're going to multiply the base color with.
65 #[texture(51)]
66 #[sampler(52)]
67 modulate_texture: Option<Handle<Image>>,
68 }
69
70 /// The GPU-side data structure specifying plain old data for the material
71 /// extension.
72 #[derive(Clone, Default, ShaderType)]
73 struct ExampleBindlessExtensionUniform {
74 /// The GPU representation of the color we're going to multiply the base
75 /// color with.
76 modulate_color: Vec4,
77 }
78
79 impl MaterialExtension for ExampleBindlessExtension {
80 fn fragment_shader() -> ShaderRef {
81 SHADER_ASSET_PATH.into()
82 }
83 }
84
85 impl<'a> From<&'a ExampleBindlessExtension> for ExampleBindlessExtensionUniform {
86 fn from(material_extension: &'a ExampleBindlessExtension) -> Self {
87 // Convert the CPU `ExampleBindlessExtension` structure to its GPU
88 // format.
89 ExampleBindlessExtensionUniform {
90 modulate_color: LinearRgba::from(material_extension.modulate_color).to_vec4(),
91 }
92 }
93 }
94
95 /// The entry point.
96 fn main() {
97 App::new()
98 .add_plugins(DefaultPlugins)
99 .add_plugins(MaterialPlugin::<
100 ExtendedMaterial<StandardMaterial, ExampleBindlessExtension>,
101 >::default())
102 .add_systems(Startup, setup)
103 .add_systems(Update, rotate_sphere)
104 .run();
105 }
106
107 /// Creates the scene.
108 fn setup(
109 mut commands: Commands,
110 asset_server: Res<AssetServer>,
111 mut meshes: ResMut<Assets<Mesh>>,
112 mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ExampleBindlessExtension>>>,
113 ) {
114 // Create a gray sphere, modulated with a red-tinted checkerboard pattern.
115 commands.spawn((
116 Mesh3d(meshes.add(SphereMeshBuilder::new(
117 1.0,
118 SphereKind::Uv {
119 sectors: 20,
120 stacks: 20,
121 },
122 ))),
123 MeshMaterial3d(materials.add(ExtendedMaterial {
124 base: StandardMaterial {
125 base_color: GRAY_600.into(),
126 ..default()
127 },
128 extension: ExampleBindlessExtension {
129 modulate_color: RED.into(),
130 modulate_texture: Some(asset_server.load("textures/uv_checker_bw.png")),
131 },
132 })),
133 Transform::from_xyz(0.0, 0.5, 0.0),
134 ));
135
136 // Create a light.
137 commands.spawn((
138 DirectionalLight::default(),
139 Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
140 ));
141
142 // Create a camera.
143 commands.spawn((
144 Camera3d::default(),
145 Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
146 ));
147 }
148
149 fn rotate_sphere(mut meshes: Query<&mut Transform, With<Mesh3d>>, time: Res<Time>) {
150 for mut transform in &mut meshes {
151 transform.rotation =
152 Quat::from_euler(EulerRot::YXZ, -time.elapsed_secs(), FRAC_PI_2 * 3.0, 0.0);
153 }
154 }