use crate::{ AlphaMode, DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Asset, AssetServer, Handle}; use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; use bevy_ecs::{ entity::Entity, prelude::World, system::{ lifetimeless::{Read, SQuery, SRes}, Query, Res, ResMut, SystemParamItem, }, world::FromWorld, }; use bevy_render::{ mesh::{Mesh, MeshVertexBufferLayout}, render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}, render_component::ExtractComponentPlugin, render_phase::{ AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ BindGroup, BindGroupLayout, PipelineCache, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderStage, }; use bevy_utils::tracing::error; use std::hash::Hash; use std::marker::PhantomData; /// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle) /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh`] entities with custom shader logic. For materials that can specialize their [`RenderPipelineDescriptor`] /// based on specific material values, see [`SpecializedMaterial`]. [`Material`] automatically implements [`SpecializedMaterial`] /// and can be used anywhere that type is used (such as [`MaterialPlugin`]). pub trait Material: Asset + RenderAsset + Sized { /// Returns this material's [`BindGroup`]. This should match the layout returned by [`Material::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; /// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`Material::bind_group`]. fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout; /// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used. /// Defaults to [`None`]. #[allow(unused_variables)] fn vertex_shader(asset_server: &AssetServer) -> Option> { None } /// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used. /// Defaults to [`None`]. #[allow(unused_variables)] fn fragment_shader(asset_server: &AssetServer) -> Option> { None } /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. #[allow(unused_variables)] fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { AlphaMode::Opaque } /// The dynamic uniform indices to set for the given `material`'s [`BindGroup`]. /// Defaults to an empty array / no dynamic uniform indices. #[allow(unused_variables)] #[inline] fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { &[] } /// Customizes the default [`RenderPipelineDescriptor`]. #[allow(unused_variables)] #[inline] fn specialize( pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayout, ) -> Result<(), SpecializedMeshPipelineError> { Ok(()) } } impl SpecializedMaterial for M { type Key = (); #[inline] fn key(_material: &::PreparedAsset) -> Self::Key {} #[inline] fn specialize( pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, _key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result<(), SpecializedMeshPipelineError> { ::specialize(pipeline, descriptor, layout) } #[inline] fn bind_group(material: &::PreparedAsset) -> &BindGroup { ::bind_group(material) } #[inline] fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout { ::bind_group_layout(render_device) } #[inline] fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { ::alpha_mode(material) } #[inline] fn vertex_shader(asset_server: &AssetServer) -> Option> { ::vertex_shader(asset_server) } #[inline] fn fragment_shader(asset_server: &AssetServer) -> Option> { ::fragment_shader(asset_server) } #[allow(unused_variables)] #[inline] fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { ::dynamic_uniform_indices(material) } } /// Materials are used alongside [`MaterialPlugin`] and [`MaterialMeshBundle`](crate::MaterialMeshBundle) /// to spawn entities that are rendered with a specific [`SpecializedMaterial`] type. They serve as an easy to use high level /// way to render [`Mesh`] entities with custom shader logic. [`SpecializedMaterials`](SpecializedMaterial) use their [`SpecializedMaterial::Key`] /// to customize their [`RenderPipelineDescriptor`] based on specific material values. The slightly simpler [`Material`] trait /// should be used for materials that do not need specialization. [`Material`] types automatically implement [`SpecializedMaterial`]. pub trait SpecializedMaterial: Asset + RenderAsset + Sized { /// The key used to specialize this material's [`RenderPipelineDescriptor`]. type Key: PartialEq + Eq + Hash + Clone + Send + Sync; /// Extract the [`SpecializedMaterial::Key`] for the "prepared" version of this material. This key will be /// passed in to the [`SpecializedMaterial::specialize`] function when compiling the [`RenderPipeline`](bevy_render::render_resource::RenderPipeline) /// for a given entity's material. fn key(material: &::PreparedAsset) -> Self::Key; /// Specializes the given `descriptor` according to the given `key`. fn specialize( pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result<(), SpecializedMeshPipelineError>; /// Returns this material's [`BindGroup`]. This should match the layout returned by [`SpecializedMaterial::bind_group_layout`]. fn bind_group(material: &::PreparedAsset) -> &BindGroup; /// Returns this material's [`BindGroupLayout`]. This should match the [`BindGroup`] returned by [`SpecializedMaterial::bind_group`]. fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout; /// Returns this material's vertex shader. If [`None`] is returned, the default mesh vertex shader will be used. /// Defaults to [`None`]. #[allow(unused_variables)] fn vertex_shader(asset_server: &AssetServer) -> Option> { None } /// Returns this material's fragment shader. If [`None`] is returned, the default mesh fragment shader will be used. /// Defaults to [`None`]. #[allow(unused_variables)] fn fragment_shader(asset_server: &AssetServer) -> Option> { None } /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. #[allow(unused_variables)] fn alpha_mode(material: &::PreparedAsset) -> AlphaMode { AlphaMode::Opaque } /// The dynamic uniform indices to set for the given `material`'s [`BindGroup`]. /// Defaults to an empty array / no dynamic uniform indices. #[allow(unused_variables)] #[inline] fn dynamic_uniform_indices(material: &::PreparedAsset) -> &[u32] { &[] } } /// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`SpecializedMaterial`] /// asset type (which includes [`Material`] types). pub struct MaterialPlugin(PhantomData); impl Default for MaterialPlugin { fn default() -> Self { Self(Default::default()) } } impl Plugin for MaterialPlugin { fn build(&self, app: &mut App) { app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()) .add_plugin(RenderAssetPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>() .init_resource::>>() .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); } } } #[derive(Eq, PartialEq, Clone, Hash)] pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, pub material_key: T, } pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, pub material_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, marker: PhantomData, } impl SpecializedMeshPipeline for MaterialPipeline { type Key = MaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?; if let Some(vertex_shader) = &self.vertex_shader { descriptor.vertex.shader = vertex_shader.clone(); } if let Some(fragment_shader) = &self.fragment_shader { descriptor.fragment.as_mut().unwrap().shader = fragment_shader.clone(); } // MeshPipeline::specialize's current implementation guarantees that the returned // specialized descriptor has a populated layout let descriptor_layout = descriptor.layout.as_mut().unwrap(); descriptor_layout.insert(1, self.material_layout.clone()); M::specialize(self, &mut descriptor, key.material_key, layout)?; Ok(descriptor) } } impl FromWorld for MaterialPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); let render_device = world.resource::(); let material_layout = M::bind_group_layout(render_device); MaterialPipeline { mesh_pipeline: world.resource::().clone(), material_layout, vertex_shader: M::vertex_shader(asset_server), fragment_shader: M::fragment_shader(asset_server), marker: PhantomData, } } } type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMaterialBindGroup, SetMeshBindGroup<2>, DrawMesh, ); pub struct SetMaterialBindGroup(PhantomData); impl EntityRenderCommand for SetMaterialBindGroup { 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( I, M::bind_group(material), M::dynamic_uniform_indices(material), ); RenderCommandResult::Success } } #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( opaque_draw_functions: Res>, alpha_mask_draw_functions: Res>, transparent_draw_functions: Res>, material_pipeline: Res>, mut pipelines: ResMut>>, mut pipeline_cache: ResMut, msaa: Res, render_meshes: Res>, render_materials: Res>, material_meshes: Query<(&Handle, &Handle, &MeshUniform)>, mut views: Query<( &ExtractedView, &VisibleEntities, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, )>, ) { for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in views.iter_mut() { let draw_opaque_pbr = opaque_draw_functions .read() .get_id::>() .unwrap(); let draw_alpha_mask_pbr = alpha_mask_draw_functions .read() .get_id::>() .unwrap(); let draw_transparent_pbr = transparent_draw_functions .read() .get_id::>() .unwrap(); let inverse_view_matrix = view.transform.compute_matrix().inverse(); let inverse_view_row_2 = inverse_view_matrix.row(2); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for visible_entity in &visible_entities.entities { if let Ok((material_handle, mesh_handle, mesh_uniform)) = material_meshes.get(*visible_entity) { if let Some(material) = render_materials.get(material_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) { let mut mesh_key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | msaa_key; let alpha_mode = M::alpha_mode(material); if let AlphaMode::Blend = alpha_mode { mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; } let material_key = M::key(material); let pipeline_id = pipelines.specialize( &mut pipeline_cache, &material_pipeline, MaterialPipelineKey { mesh_key, material_key, }, &mesh.layout, ); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); continue; } }; // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix // gives the z component of translation of the mesh in view space let mesh_z = inverse_view_row_2.dot(mesh_uniform.transform.col(3)); match alpha_mode { AlphaMode::Opaque => { opaque_phase.add(Opaque3d { entity: *visible_entity, draw_function: draw_opaque_pbr, pipeline: pipeline_id, // NOTE: Front-to-back ordering for opaque with ascending sort means near should have the // lowest sort key and getting further away should increase. As we have // -z in front of the camera, values in view space decrease away from the // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering distance: -mesh_z, }); } AlphaMode::Mask(_) => { alpha_mask_phase.add(AlphaMask3d { entity: *visible_entity, draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, // NOTE: Front-to-back ordering for alpha mask with ascending sort means near should have the // lowest sort key and getting further away should increase. As we have // -z in front of the camera, values in view space decrease away from the // camera. Flipping the sign of mesh_z results in the correct front-to-back ordering distance: -mesh_z, }); } AlphaMode::Blend => { transparent_phase.add(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the // lowest sort key and getting closer should increase. As we have // -z in front of the camera, the largest distance is -far with values increasing toward the // camera. As such we can just use mesh_z as the distance distance: mesh_z, }); } } } } } } } }