use bevy::{ core_pipeline::Transparent3d, ecs::system::{lifetimeless::*, SystemParamItem}, pbr::{ DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup, }, prelude::*, reflect::TypeUuid, render::{ camera::PerspectiveCameraBundle, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::RenderDevice, view::{ExtractedView, Msaa}, RenderApp, RenderStage, }, }; use crevice::std140::{AsStd140, Std140}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(CustomMaterialPlugin) .add_startup_system(setup) .run(); } /// set up a simple 3D scene fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { // cube commands.spawn().insert_bundle(( meshes.add(Mesh::from(shape::Cube { size: 1.0 })), Transform::from_xyz(0.0, 0.5, 0.0), GlobalTransform::default(), Visibility::default(), ComputedVisibility::default(), materials.add(CustomMaterial { color: Color::GREEN, }), )); // camera commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..Default::default() }); } #[derive(Debug, Clone, TypeUuid)] #[uuid = "4ee9c363-1124-4113-890e-199d81b00281"] pub struct CustomMaterial { color: Color, } #[derive(Clone)] pub struct GpuCustomMaterial { _buffer: Buffer, bind_group: BindGroup, } impl RenderAsset for CustomMaterial { type ExtractedAsset = CustomMaterial; type PreparedAsset = GpuCustomMaterial; type Param = (SRes, SRes); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() } fn prepare_asset( extracted_asset: Self::ExtractedAsset, (render_device, custom_pipeline): &mut SystemParamItem, ) -> Result> { let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32()); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { contents: color.as_std140().as_bytes(), label: None, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, }); let bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { binding: 0, resource: buffer.as_entire_binding(), }], label: None, layout: &custom_pipeline.material_layout, }); Ok(GpuCustomMaterial { _buffer: buffer, bind_group, }) } } pub struct CustomMaterialPlugin; impl Plugin for CustomMaterialPlugin { fn build(&self, app: &mut App) { app.add_asset::() .add_plugin(ExtractComponentPlugin::>::default()) .add_plugin(RenderAssetPlugin::::default()); app.sub_app(RenderApp) .add_render_command::() .init_resource::() .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue_custom); } } pub struct CustomPipeline { mesh_pipeline: MeshPipeline, material_layout: BindGroupLayout, shader: Handle, } impl SpecializedPipeline for CustomPipeline { type Key = MeshPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut descriptor = self.mesh_pipeline.specialize(key); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), self.material_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), ]); descriptor } } impl FromWorld for CustomPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.get_resource::().unwrap(); // Watch for changes, allowing for hot shader reloading // Try changing custom_material.wgsl while the app is running! asset_server.watch_for_changes().unwrap(); let render_device = world.get_resource::().unwrap(); let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64), }, count: None, }], label: None, }); CustomPipeline { mesh_pipeline: world.get_resource::().unwrap().clone(), shader: asset_server.load("shaders/custom_material.wgsl"), material_layout, } } } #[allow(clippy::too_many_arguments)] pub fn queue_custom( transparent_3d_draw_functions: Res>, materials: Res>, custom_pipeline: Res, mut pipeline_cache: ResMut, mut specialized_pipelines: ResMut>, msaa: Res, material_meshes: Query<(Entity, &Handle, &MeshUniform), 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); for (view, mut transparent_phase) in views.iter_mut() { let view_matrix = view.transform.compute_matrix(); let view_row_2 = view_matrix.row(2); for (entity, material_handle, mesh_uniform) in material_meshes.iter() { if materials.contains_key(material_handle) { transparent_phase.add(Transparent3d { entity, pipeline: specialized_pipelines.specialize( &mut pipeline_cache, &custom_pipeline, key, ), draw_function: draw_custom, distance: view_row_2.dot(mesh_uniform.transform.col(3)), }); } } } } type DrawCustom = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetCustomMaterialBindGroup, SetMeshBindGroup<2>, DrawMesh, ); struct SetCustomMaterialBindGroup; impl EntityRenderCommand for SetCustomMaterialBindGroup { type Param = ( SRes>, SQuery>>, ); fn render<'w>( _view: Entity, item: Entity, (materials, query): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let material_handle = query.get(item).unwrap(); let material = materials.into_inner().get(material_handle).unwrap(); pass.set_bind_group(1, &material.bind_group, &[]); RenderCommandResult::Success } }