use bevy::{ core_pipeline::Transparent3d, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, }, math::{Vec3, Vec4}, pbr2::{DrawMesh, MeshUniform, PbrShaders, SetMeshViewBindGroup, SetTransformBindGroup}, prelude::{AddAsset, App, Assets, GlobalTransform, Handle, Plugin, Transform}, reflect::TypeUuid, render2::{ camera::PerspectiveCameraBundle, color::Color, mesh::{shape, Mesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ AddRenderCommand, DrawFunctions, RenderCommand, RenderPhase, TrackedRenderPass, }, render_resource::*, renderer::RenderDevice, shader::Shader, texture::BevyDefault, view::ExtractedView, RenderApp, RenderStage, }, PipelinedDefaultPlugins, }; use crevice::std140::{AsStd140, Std140}; #[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 = extracted_asset.color.as_rgba_linear().into(); let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { contents: color.as_std140().as_bytes(), label: None, usage: BufferUsage::UNIFORM | BufferUsage::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::() .add_system_to_stage(RenderStage::Queue, queue_custom); } } fn main() { App::new() .add_plugins(PipelinedDefaultPlugins) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_plugin(LogDiagnosticsPlugin::default()) .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(), 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() }); } pub struct CustomPipeline { material_layout: BindGroupLayout, pipeline: RenderPipeline, } // TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system impl FromWorld for CustomPipeline { fn from_world(world: &mut World) -> Self { let render_device = world.get_resource::().unwrap(); let shader = Shader::from_wgsl(include_str!("../../assets/shaders/custom.wgsl")); let shader_module = render_device.create_shader_module(&shader); let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStage::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, }); let pbr_pipeline = world.get_resource::().unwrap(); let pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor { label: None, push_constant_ranges: &[], bind_group_layouts: &[ &pbr_pipeline.view_layout, &material_layout, &pbr_pipeline.mesh_layout, ], }); let pipeline = render_device.create_render_pipeline(&RenderPipelineDescriptor { label: None, vertex: VertexState { buffers: &[VertexBufferLayout { array_stride: 32, step_mode: InputStepMode::Vertex, attributes: &[ // Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)) VertexAttribute { format: VertexFormat::Float32x3, offset: 12, shader_location: 0, }, // Normal VertexAttribute { format: VertexFormat::Float32x3, offset: 0, shader_location: 1, }, // Uv VertexAttribute { format: VertexFormat::Float32x2, offset: 24, shader_location: 2, }, ], }], module: &shader_module, entry_point: "vertex", }, fragment: Some(FragmentState { module: &shader_module, entry_point: "fragment", targets: &[ColorTargetState { format: TextureFormat::bevy_default(), blend: Some(BlendState { color: BlendComponent { src_factor: BlendFactor::SrcAlpha, dst_factor: BlendFactor::OneMinusSrcAlpha, operation: BlendOperation::Add, }, alpha: BlendComponent { src_factor: BlendFactor::One, dst_factor: BlendFactor::One, operation: BlendOperation::Add, }, }), write_mask: ColorWrite::ALL, }], }), depth_stencil: Some(DepthStencilState { format: TextureFormat::Depth32Float, depth_write_enabled: true, depth_compare: CompareFunction::Greater, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, read_mask: 0, write_mask: 0, }, bias: DepthBiasState { constant: 0, slope_scale: 0.0, clamp: 0.0, }, }), layout: Some(&pipeline_layout), multisample: MultisampleState::default(), primitive: PrimitiveState { topology: PrimitiveTopology::TriangleList, strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: Some(Face::Back), polygon_mode: PolygonMode::Fill, clamp_depth: false, conservative: false, }, }); CustomPipeline { pipeline, material_layout, } } } pub fn queue_custom( transparent_3d_draw_functions: Res>, materials: 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(); 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, draw_function: draw_custom, distance: view_row_2.dot(mesh_uniform.transform.col(3)), }); } } } } type DrawCustom = ( SetCustomMaterialPipeline, SetMeshViewBindGroup<0>, SetTransformBindGroup<2>, DrawMesh, ); struct SetCustomMaterialPipeline; impl RenderCommand for SetCustomMaterialPipeline { type Param = ( SRes>, SRes, SQuery>>, ); fn render<'w>( _view: Entity, item: &Transparent3d, (materials, custom_pipeline, query): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) { let material_handle = query.get(item.entity).unwrap(); let material = materials.into_inner().get(material_handle).unwrap(); pass.set_render_pipeline(&custom_pipeline.into_inner().pipeline); pass.set_bind_group(1, &material.bind_group, &[]); } }