use bevy::{ asset::LoadState, ecs::system::{lifetimeless::SRes, SystemParamItem}, pbr::MaterialPipeline, prelude::*, reflect::TypeUuid, render::{ render_asset::{PrepareAssetError, RenderAsset, RenderAssets}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, SamplerBindingType, ShaderStages, TextureSampleType, TextureViewDimension, }, renderer::RenderDevice, }, }; /// This example illustrates how to create a texture for use with a `texture_2d_array` shader /// uniform variable. fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::::default()) .add_startup_system(setup) .add_system(create_array_texture) .run(); } struct LoadingTexture { is_loaded: bool, handle: Handle, } fn setup(mut commands: Commands, asset_server: Res) { // Start loading the texture. commands.insert_resource(LoadingTexture { is_loaded: false, handle: asset_server.load("textures/array_texture.png"), }); // light commands.spawn_bundle(PointLightBundle { point_light: PointLight { intensity: 3000.0, ..Default::default() }, transform: Transform::from_xyz(-3.0, 2.0, -1.0), ..Default::default() }); commands.spawn_bundle(PointLightBundle { point_light: PointLight { intensity: 3000.0, ..Default::default() }, transform: Transform::from_xyz(3.0, 2.0, 1.0), ..Default::default() }); // camera commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::new(1.5, 0.0, 0.0), Vec3::Y), ..Default::default() }); } fn create_array_texture( mut commands: Commands, asset_server: Res, mut loading_texture: ResMut, mut images: ResMut>, mut meshes: ResMut>, mut materials: ResMut>, ) { if loading_texture.is_loaded || asset_server.get_load_state(loading_texture.handle.clone()) != LoadState::Loaded { return; } loading_texture.is_loaded = true; let image = images.get_mut(&loading_texture.handle).unwrap(); // Create a new array texture asset from the loaded texture. let array_layers = 4; image.reinterpret_stacked_2d_as_array(array_layers); // Spawn some cubes using the array texture let mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); let material_handle = materials.add(ArrayTextureMaterial { array_texture: loading_texture.handle.clone(), }); for x in -5..=5 { commands.spawn_bundle(MaterialMeshBundle { mesh: mesh_handle.clone(), material: material_handle.clone(), transform: Transform::from_xyz(x as f32 + 0.5, 0.0, 0.0), ..Default::default() }); } } #[derive(Debug, Clone, TypeUuid)] #[uuid = "9c5a0ddf-1eaf-41b4-9832-ed736fd26af3"] struct ArrayTextureMaterial { array_texture: Handle, } #[derive(Clone)] pub struct GpuArrayTextureMaterial { bind_group: BindGroup, } impl RenderAsset for ArrayTextureMaterial { type ExtractedAsset = ArrayTextureMaterial; type PreparedAsset = GpuArrayTextureMaterial; type Param = ( SRes, SRes>, SRes>, ); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( extracted_asset: Self::ExtractedAsset, (render_device, material_pipeline, gpu_images): &mut SystemParamItem, ) -> Result> { let (array_texture_texture_view, array_texture_sampler) = if let Some(result) = material_pipeline .mesh_pipeline .get_image_texture(gpu_images, &Some(extracted_asset.array_texture.clone())) { result } else { return Err(PrepareAssetError::RetryNextUpdate(extracted_asset)); }; let bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: BindingResource::TextureView(array_texture_texture_view), }, BindGroupEntry { binding: 1, resource: BindingResource::Sampler(array_texture_sampler), }, ], label: Some("array_texture_material_bind_group"), layout: &material_pipeline.material_layout, }); Ok(GpuArrayTextureMaterial { bind_group }) } } impl Material for ArrayTextureMaterial { fn fragment_shader(asset_server: &AssetServer) -> Option> { Some(asset_server.load("shaders/array_texture.wgsl")) } fn bind_group(render_asset: &::PreparedAsset) -> &BindGroup { &render_asset.bind_group } fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // Array Texture BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Float { filterable: true }, view_dimension: TextureViewDimension::D2Array, }, count: None, }, // Array Texture Sampler BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, ], label: None, }) } }