PR #17898 disabled bindless support for `ExtendedMaterial`. This commit adds it back. It also adds a new example, `extended_material_bindless`, showing how to use it.
		
			
				
	
	
		
			157 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! Demonstrates bindless `ExtendedMaterial`.
 | 
						|
 | 
						|
use std::f32::consts::FRAC_PI_2;
 | 
						|
 | 
						|
use bevy::{
 | 
						|
    color::palettes::{css::RED, tailwind::GRAY_600},
 | 
						|
    pbr::{ExtendedMaterial, MaterialExtension, MeshMaterial3d},
 | 
						|
    prelude::*,
 | 
						|
    render::{
 | 
						|
        mesh::{SphereKind, SphereMeshBuilder},
 | 
						|
        render_resource::{AsBindGroup, ShaderRef, ShaderType},
 | 
						|
    },
 | 
						|
    utils::default,
 | 
						|
};
 | 
						|
 | 
						|
/// The path to the example material shader.
 | 
						|
static SHADER_ASSET_PATH: &str = "shaders/extended_material_bindless.wgsl";
 | 
						|
 | 
						|
/// The example bindless material extension.
 | 
						|
///
 | 
						|
/// As usual for material extensions, we need to avoid conflicting with both the
 | 
						|
/// binding numbers and bindless indices of the [`StandardMaterial`], so we
 | 
						|
/// start both values at 100 and 50 respectively.
 | 
						|
///
 | 
						|
/// The `#[data(50, ExampleBindlessExtensionUniform, binding_array(101))]`
 | 
						|
/// attribute specifies that the plain old data
 | 
						|
/// [`ExampleBindlessExtensionUniform`] will be placed into an array with
 | 
						|
/// binding 100 and will occupy index 50 in the
 | 
						|
/// `ExampleBindlessExtendedMaterialIndices` structure. (See the shader for the
 | 
						|
/// definition of that structure.) That corresponds to the following shader
 | 
						|
/// declaration:
 | 
						|
///
 | 
						|
/// ```wgsl
 | 
						|
/// @group(2) @binding(100) var<storage> example_extended_material_indices:
 | 
						|
///     array<ExampleBindlessExtendedMaterialIndices>;
 | 
						|
/// ```
 | 
						|
///
 | 
						|
/// The `#[bindless(index_table(range(50..53), binding(100)))]` attribute
 | 
						|
/// specifies that this material extension should be bindless. The `range`
 | 
						|
/// subattribute specifies that this material extension should have its own
 | 
						|
/// index table covering bindings 50, 51, and 52. The `binding` subattribute
 | 
						|
/// specifies that the extended material index table should be bound to binding
 | 
						|
/// 100. This corresponds to the following shader declarations:
 | 
						|
///
 | 
						|
/// ```wgsl
 | 
						|
/// struct ExampleBindlessExtendedMaterialIndices {
 | 
						|
///     material: u32,                      // 50
 | 
						|
///     modulate_texture: u32,              // 51
 | 
						|
///     modulate_texture_sampler: u32,      // 52
 | 
						|
/// }
 | 
						|
///
 | 
						|
/// @group(2) @binding(100) var<storage> example_extended_material_indices:
 | 
						|
///     array<ExampleBindlessExtendedMaterialIndices>;
 | 
						|
/// ```
 | 
						|
///
 | 
						|
/// We need to use the `index_table` subattribute because the
 | 
						|
/// [`StandardMaterial`] bindless index table is bound to binding 0 by default.
 | 
						|
/// Thus we need to specify a different binding so that our extended bindless
 | 
						|
/// index table doesn't conflict.
 | 
						|
#[derive(Asset, Clone, Reflect, AsBindGroup)]
 | 
						|
#[data(50, ExampleBindlessExtensionUniform, binding_array(101))]
 | 
						|
#[bindless(index_table(range(50..53), binding(100)))]
 | 
						|
struct ExampleBindlessExtension {
 | 
						|
    /// The color we're going to multiply the base color with.
 | 
						|
    modulate_color: Color,
 | 
						|
    /// The image we're going to multiply the base color with.
 | 
						|
    #[texture(51)]
 | 
						|
    #[sampler(52)]
 | 
						|
    modulate_texture: Option<Handle<Image>>,
 | 
						|
}
 | 
						|
 | 
						|
/// The GPU-side data structure specifying plain old data for the material
 | 
						|
/// extension.
 | 
						|
#[derive(Clone, Default, ShaderType)]
 | 
						|
struct ExampleBindlessExtensionUniform {
 | 
						|
    /// The GPU representation of the color we're going to multiply the base
 | 
						|
    /// color with.
 | 
						|
    modulate_color: Vec4,
 | 
						|
}
 | 
						|
 | 
						|
impl MaterialExtension for ExampleBindlessExtension {
 | 
						|
    fn fragment_shader() -> ShaderRef {
 | 
						|
        SHADER_ASSET_PATH.into()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl<'a> From<&'a ExampleBindlessExtension> for ExampleBindlessExtensionUniform {
 | 
						|
    fn from(material_extension: &'a ExampleBindlessExtension) -> Self {
 | 
						|
        // Convert the CPU `ExampleBindlessExtension` structure to its GPU
 | 
						|
        // format.
 | 
						|
        ExampleBindlessExtensionUniform {
 | 
						|
            modulate_color: LinearRgba::from(material_extension.modulate_color).to_vec4(),
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// The entry point.
 | 
						|
fn main() {
 | 
						|
    App::new()
 | 
						|
        .add_plugins(DefaultPlugins)
 | 
						|
        .add_plugins(MaterialPlugin::<
 | 
						|
            ExtendedMaterial<StandardMaterial, ExampleBindlessExtension>,
 | 
						|
        >::default())
 | 
						|
        .add_systems(Startup, setup)
 | 
						|
        .add_systems(Update, rotate_sphere)
 | 
						|
        .run();
 | 
						|
}
 | 
						|
 | 
						|
/// Creates the scene.
 | 
						|
fn setup(
 | 
						|
    mut commands: Commands,
 | 
						|
    asset_server: Res<AssetServer>,
 | 
						|
    mut meshes: ResMut<Assets<Mesh>>,
 | 
						|
    mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ExampleBindlessExtension>>>,
 | 
						|
) {
 | 
						|
    // Create a gray sphere, modulated with a red-tinted Bevy logo.
 | 
						|
    commands.spawn((
 | 
						|
        Mesh3d(meshes.add(SphereMeshBuilder::new(
 | 
						|
            1.0,
 | 
						|
            SphereKind::Uv {
 | 
						|
                sectors: 20,
 | 
						|
                stacks: 20,
 | 
						|
            },
 | 
						|
        ))),
 | 
						|
        MeshMaterial3d(materials.add(ExtendedMaterial {
 | 
						|
            base: StandardMaterial {
 | 
						|
                base_color: GRAY_600.into(),
 | 
						|
                ..default()
 | 
						|
            },
 | 
						|
            extension: ExampleBindlessExtension {
 | 
						|
                modulate_color: RED.into(),
 | 
						|
                modulate_texture: Some(asset_server.load("textures/uv_checker_bw.png")),
 | 
						|
            },
 | 
						|
        })),
 | 
						|
        Transform::from_xyz(0.0, 0.5, 0.0),
 | 
						|
    ));
 | 
						|
 | 
						|
    // Create a light.
 | 
						|
    commands.spawn((
 | 
						|
        DirectionalLight::default(),
 | 
						|
        Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
 | 
						|
    ));
 | 
						|
 | 
						|
    // Create a camera.
 | 
						|
    commands.spawn((
 | 
						|
        Camera3d::default(),
 | 
						|
        Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
 | 
						|
    ));
 | 
						|
}
 | 
						|
 | 
						|
fn rotate_sphere(mut meshes: Query<&mut Transform, With<Mesh3d>>, time: Res<Time>) {
 | 
						|
    for mut transform in &mut meshes {
 | 
						|
        transform.rotation =
 | 
						|
            Quat::from_euler(EulerRot::YXZ, -time.elapsed_secs(), FRAC_PI_2 * 3.0, 0.0);
 | 
						|
    }
 | 
						|
}
 |