Skip to main content

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 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)))]
61struct 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)]
73struct ExampleBindlessExtensionUniform {
74    /// The GPU representation of the color we're going to multiply the base
75    /// color with.
76    modulate_color: Vec4,
77}
78
79impl MaterialExtension for ExampleBindlessExtension {
80    fn fragment_shader() -> ShaderRef {
81        SHADER_ASSET_PATH.into()
82    }
83}
84
85impl<'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.
96fn 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.
108fn 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
149fn 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}