//! A shader that uses dynamic data like the time since startup. //! //! This example uses a specialized pipeline. use bevy::{ core_pipeline::core_3d::Transparent3d, ecs::system::{ lifetimeless::{Read, SRes}, SystemParamItem, }, pbr::{ DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup, }, prelude::*, render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::{RenderDevice, RenderQueue}, view::{ComputedVisibility, ExtractedView, Msaa, Visibility}, RenderApp, RenderStage, }, }; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(CustomMaterialPlugin) .add_startup_system(setup) .run(); } fn setup(mut commands: Commands, mut meshes: ResMut>) { // cube commands.spawn_bundle(( meshes.add(Mesh::from(shape::Cube { size: 1.0 })), Transform::from_xyz(0.0, 0.5, 0.0), GlobalTransform::default(), CustomMaterial, Visibility::default(), ComputedVisibility::default(), )); // camera commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); } #[derive(Component)] struct CustomMaterial; pub struct CustomMaterialPlugin; impl Plugin for CustomMaterialPlugin { fn build(&self, app: &mut App) { let render_device = app.world.resource::(); let buffer = render_device.create_buffer(&BufferDescriptor { label: Some("time uniform buffer"), size: std::mem::size_of::() as u64, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, mapped_at_creation: false, }); app.add_plugin(ExtractComponentPlugin::::default()) .add_plugin(ExtractResourcePlugin::::default()); app.sub_app_mut(RenderApp) .add_render_command::() .insert_resource(TimeMeta { buffer, bind_group: None, }) .init_resource::() .init_resource::>() .add_system_to_stage(RenderStage::Prepare, prepare_time) .add_system_to_stage(RenderStage::Queue, queue_custom) .add_system_to_stage(RenderStage::Queue, queue_time_bind_group); } } impl ExtractComponent for CustomMaterial { type Query = Read; type Filter = (); fn extract_component(_: bevy::ecs::query::QueryItem) -> Self { CustomMaterial } } // add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline` #[allow(clippy::too_many_arguments)] fn queue_custom( transparent_3d_draw_functions: Res>, custom_pipeline: Res, msaa: Res, mut pipelines: ResMut>, mut pipeline_cache: ResMut, render_meshes: Res>, material_meshes: Query<(Entity, &MeshUniform, &Handle), With>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { let draw_custom = transparent_3d_draw_functions .read() .get_id::() .unwrap(); let key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); for (view, mut transparent_phase) in &mut views { let rangefinder = view.rangefinder3d(); for (entity, mesh_uniform, mesh_handle) in &material_meshes { if let Some(mesh) = render_meshes.get(mesh_handle) { let pipeline = pipelines .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout) .unwrap(); transparent_phase.add(Transparent3d { entity, pipeline, draw_function: draw_custom, distance: rangefinder.distance(&mesh_uniform.transform), }); } } } } #[derive(Resource, Default)] struct ExtractedTime { seconds_since_startup: f32, } impl ExtractResource for ExtractedTime { type Source = Time; fn extract_resource(time: &Self::Source) -> Self { ExtractedTime { seconds_since_startup: time.seconds_since_startup() as f32, } } } #[derive(Resource)] struct TimeMeta { buffer: Buffer, bind_group: Option, } // write the extracted time into the corresponding uniform buffer fn prepare_time( time: Res, time_meta: ResMut, render_queue: Res, ) { render_queue.write_buffer( &time_meta.buffer, 0, bevy::core::cast_slice(&[time.seconds_since_startup]), ); } // create a bind group for the time uniform buffer fn queue_time_bind_group( render_device: Res, mut time_meta: ResMut, pipeline: Res, ) { let bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: None, layout: &pipeline.time_bind_group_layout, entries: &[BindGroupEntry { binding: 0, resource: time_meta.buffer.as_entire_binding(), }], }); time_meta.bind_group = Some(bind_group); } #[derive(Resource)] pub struct CustomPipeline { shader: Handle, mesh_pipeline: MeshPipeline, time_bind_group_layout: BindGroupLayout, } impl FromWorld for CustomPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); let shader = asset_server.load("shaders/animate_shader.wgsl"); let render_device = world.resource::(); let time_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some("time bind group"), entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: BufferSize::new(std::mem::size_of::() as u64), }, count: None, }], }); let mesh_pipeline = world.resource::(); CustomPipeline { shader, mesh_pipeline: mesh_pipeline.clone(), time_bind_group_layout, } } } impl SpecializedMeshPipeline for CustomPipeline { type Key = MeshPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), self.time_bind_group_layout.clone(), ]); Ok(descriptor) } } type DrawCustom = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, SetTimeBindGroup<2>, DrawMesh, ); struct SetTimeBindGroup; impl EntityRenderCommand for SetTimeBindGroup { type Param = SRes; fn render<'w>( _view: Entity, _item: Entity, time_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let time_bind_group = time_meta.into_inner().bind_group.as_ref().unwrap(); pass.set_bind_group(I, time_bind_group, &[]); RenderCommandResult::Success } }