 84991d34f3
			
		
	
	
		84991d34f3
		
	
	
	
	
		
			
			# Objective - Make the reusable PBR shading functionality a little more reusable - Add constructor functions for `StandardMaterial` and `PbrInput` structs to populate them with default values - Document unclear `PbrInput` members - Demonstrate how to reuse the bevy PBR shading functionality - The final important piece from #3969 as the initial shot at making the PBR shader code reusable in custom materials ## Solution - Add back and rework the 'old' `array_texture` example from pre-0.6. - Create a custom shader material - Use a single array texture binding and sampler for the material bind group - Use a shader that calls `pbr()` from the `bevy_pbr::pbr_functions` import - Spawn a row of cubes using the custom material - In the shader, select the array texture layer to sample by using the world position x coordinate modulo the number of array texture layers <img width="1392" alt="Screenshot 2022-06-23 at 12 28 05" src="https://user-images.githubusercontent.com/302146/175278593-2296f519-f577-4ece-81c0-d842283784a1.png"> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
		
			
				
	
	
		
			191 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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<f32>` shader
 | |
| /// uniform variable.
 | |
| fn main() {
 | |
|     App::new()
 | |
|         .add_plugins(DefaultPlugins)
 | |
|         .add_plugin(MaterialPlugin::<ArrayTextureMaterial>::default())
 | |
|         .add_startup_system(setup)
 | |
|         .add_system(create_array_texture)
 | |
|         .run();
 | |
| }
 | |
| 
 | |
| struct LoadingTexture {
 | |
|     is_loaded: bool,
 | |
|     handle: Handle<Image>,
 | |
| }
 | |
| 
 | |
| fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | |
|     // 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<AssetServer>,
 | |
|     mut loading_texture: ResMut<LoadingTexture>,
 | |
|     mut images: ResMut<Assets<Image>>,
 | |
|     mut meshes: ResMut<Assets<Mesh>>,
 | |
|     mut materials: ResMut<Assets<ArrayTextureMaterial>>,
 | |
| ) {
 | |
|     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<Image>,
 | |
| }
 | |
| 
 | |
| #[derive(Clone)]
 | |
| pub struct GpuArrayTextureMaterial {
 | |
|     bind_group: BindGroup,
 | |
| }
 | |
| 
 | |
| impl RenderAsset for ArrayTextureMaterial {
 | |
|     type ExtractedAsset = ArrayTextureMaterial;
 | |
|     type PreparedAsset = GpuArrayTextureMaterial;
 | |
|     type Param = (
 | |
|         SRes<RenderDevice>,
 | |
|         SRes<MaterialPipeline<Self>>,
 | |
|         SRes<RenderAssets<Image>>,
 | |
|     );
 | |
|     fn extract_asset(&self) -> Self::ExtractedAsset {
 | |
|         self.clone()
 | |
|     }
 | |
| 
 | |
|     fn prepare_asset(
 | |
|         extracted_asset: Self::ExtractedAsset,
 | |
|         (render_device, material_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
 | |
|     ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
 | |
|         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<Handle<Shader>> {
 | |
|         Some(asset_server.load("shaders/array_texture.wgsl"))
 | |
|     }
 | |
| 
 | |
|     fn bind_group(render_asset: &<Self as RenderAsset>::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,
 | |
|         })
 | |
|     }
 | |
| }
 |