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