#[cfg(feature = "meshlet")] use crate::meshlet::{ prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes, InstanceManager, }; use crate::*; use bevy_asset::{Asset, AssetId, AssetServer}; use bevy_core_pipeline::{ core_3d::{ AlphaMask3d, Camera3d, Opaque3d, Opaque3dBinKey, ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, }, prepass::{ DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey, }, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_reflect::Reflect; use bevy_render::{ camera::TemporalJitter, extract_instances::{ExtractInstancesPlugin, ExtractedInstances}, extract_resource::ExtractResource, mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::*, render_resource::*, renderer::RenderDevice, view::{ExtractedView, Msaa, RenderVisibilityRanges, VisibleEntities, WithMesh}, }; use bevy_utils::tracing::error; use std::marker::PhantomData; use std::sync::atomic::{AtomicU32, Ordering}; use std::{hash::Hash, num::NonZero}; use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; /// Materials are used alongside [`MaterialPlugin`] and [`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`](bevy_render::mesh::Mesh) entities with custom shader logic. /// /// Materials must implement [`AsBindGroup`] to define how data will be transferred to the GPU and bound in shaders. /// [`AsBindGroup`] can be derived, which makes generating bindings straightforward. See the [`AsBindGroup`] docs for details. /// /// # Example /// /// Here is a simple Material implementation. The [`AsBindGroup`] derive has many features. To see what else is available, /// check out the [`AsBindGroup`] documentation. /// ``` /// # use bevy_pbr::{Material, MaterialMeshBundle}; /// # use bevy_ecs::prelude::*; /// # use bevy_reflect::TypePath; /// # use bevy_render::{render_resource::{AsBindGroup, ShaderRef}, texture::Image}; /// # use bevy_color::LinearRgba; /// # use bevy_color::palettes::basic::RED; /// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// /// #[derive(AsBindGroup, Debug, Clone, Asset, TypePath)] /// pub struct CustomMaterial { /// // Uniform bindings must implement `ShaderType`, which will be used to convert the value to /// // its shader-compatible equivalent. Most core math types already implement `ShaderType`. /// #[uniform(0)] /// color: LinearRgba, /// // Images can be bound as textures in shaders. If the Image's sampler is also needed, just /// // add the sampler attribute with a different binding index. /// #[texture(1)] /// #[sampler(2)] /// color_texture: Handle, /// } /// /// // All functions on `Material` have default impls. You only need to implement the /// // functions that are relevant for your material. /// impl Material for CustomMaterial { /// fn fragment_shader() -> ShaderRef { /// "shaders/custom_material.wgsl".into() /// } /// } /// /// // Spawn an entity using `CustomMaterial`. /// fn setup(mut commands: Commands, mut materials: ResMut>, asset_server: Res) { /// commands.spawn(MaterialMeshBundle { /// material: materials.add(CustomMaterial { /// color: RED.into(), /// color_texture: asset_server.load("some_image.png"), /// }), /// ..Default::default() /// }); /// } /// ``` /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl /// @group(2) @binding(0) var color: vec4; /// @group(2) @binding(1) var color_texture: texture_2d; /// @group(2) @binding(2) var color_sampler: sampler; /// ``` pub trait Material: Asset + AsBindGroup + Clone + Sized { /// Returns this material's vertex shader. If [`ShaderRef::Default`] is returned, the default mesh vertex shader /// will be used. fn vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's fragment shader. If [`ShaderRef::Default`] is returned, the default mesh fragment shader /// will be used. #[allow(unused_variables)] fn fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's [`AlphaMode`]. Defaults to [`AlphaMode::Opaque`]. #[inline] fn alpha_mode(&self) -> AlphaMode { AlphaMode::Opaque } /// Returns if this material should be rendered by the deferred or forward renderer. /// for `AlphaMode::Opaque` or `AlphaMode::Mask` materials. /// If `OpaqueRendererMethod::Auto`, it will default to what is selected in the `DefaultOpaqueRendererMethod` resource. #[inline] fn opaque_render_method(&self) -> OpaqueRendererMethod { OpaqueRendererMethod::Forward } #[inline] /// Add a bias to the view depth of the mesh which can be used to force a specific render order. /// for meshes with similar depth, to avoid z-fighting. /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. fn depth_bias(&self) -> f32 { 0.0 } #[inline] /// Returns whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture). /// /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires /// rendering to take place in a separate [`Transmissive3d`] pass. fn reads_view_transmission_texture(&self) -> bool { false } /// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader /// will be used. /// /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps /// required for shadow mapping. fn prepass_vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's prepass fragment shader. If [`ShaderRef::Default`] is returned, the default prepass fragment shader /// will be used. /// /// This is used for the various [prepasses](bevy_core_pipeline::prepass) as well as for generating the depth maps /// required for shadow mapping. #[allow(unused_variables)] fn prepass_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's deferred vertex shader. If [`ShaderRef::Default`] is returned, the default deferred vertex shader /// will be used. fn deferred_vertex_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's deferred fragment shader. If [`ShaderRef::Default`] is returned, the default deferred fragment shader /// will be used. #[allow(unused_variables)] fn deferred_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's [`crate::meshlet::MeshletMesh`] fragment shader. If [`ShaderRef::Default`] is returned, /// the default meshlet mesh fragment shader will be used. /// /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s. #[allow(unused_variables)] #[cfg(feature = "meshlet")] fn meshlet_mesh_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's [`crate::meshlet::MeshletMesh`] prepass fragment shader. If [`ShaderRef::Default`] is returned, /// the default meshlet mesh prepass fragment shader will be used. /// /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s. #[allow(unused_variables)] #[cfg(feature = "meshlet")] fn meshlet_mesh_prepass_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Returns this material's [`crate::meshlet::MeshletMesh`] deferred fragment shader. If [`ShaderRef::Default`] is returned, /// the default meshlet mesh deferred fragment shader will be used. /// /// This is part of an experimental feature, and is unnecessary to implement unless you are using `MeshletMesh`'s. #[allow(unused_variables)] #[cfg(feature = "meshlet")] fn meshlet_mesh_deferred_fragment_shader() -> ShaderRef { ShaderRef::Default } /// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's /// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input. #[allow(unused_variables)] #[inline] fn specialize( pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, layout: &MeshVertexBufferLayoutRef, key: MaterialPipelineKey, ) -> Result<(), SpecializedMeshPipelineError> { Ok(()) } } /// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material`] /// asset type. pub struct MaterialPlugin { /// Controls if the prepass is enabled for the Material. /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs. /// /// When it is enabled, it will automatically add the [`PrepassPlugin`] /// required to make the prepass work on this Material. pub prepass_enabled: bool, /// Controls if shadows are enabled for the Material. pub shadows_enabled: bool, pub _marker: PhantomData, } impl Default for MaterialPlugin { fn default() -> Self { Self { prepass_enabled: true, shadows_enabled: true, _marker: Default::default(), } } } impl Plugin for MaterialPlugin where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { app.init_asset::().add_plugins(( ExtractInstancesPlugin::>::extract_visible(), RenderAssetPlugin::>::default(), )); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>>() .add_systems( Render, queue_material_meshes:: .in_set(RenderSet::QueueMeshes) .after(prepare_assets::>), ); if self.shadows_enabled { render_app.add_systems( Render, queue_shadows:: .in_set(RenderSet::QueueMeshes) .after(prepare_assets::>), ); } #[cfg(feature = "meshlet")] render_app.add_systems( Render, queue_material_meshlet_meshes:: .in_set(RenderSet::QueueMeshes) .run_if(resource_exists::), ); #[cfg(feature = "meshlet")] render_app.add_systems( Render, prepare_material_meshlet_meshes_main_opaque_pass:: .in_set(RenderSet::QueueMeshes) .after(prepare_assets::>) .before(queue_material_meshlet_meshes::) .run_if(resource_exists::), ); } if self.shadows_enabled || self.prepass_enabled { // PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin app.add_plugins(PrepassPipelinePlugin::::default()); } if self.prepass_enabled { app.add_plugins(PrepassPlugin::::default()); } } fn finish(&self, app: &mut App) { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::>(); } } } /// A key uniquely identifying a specialized [`MaterialPipeline`]. pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, pub bind_group_data: M::Data, } impl Eq for MaterialPipelineKey where M::Data: PartialEq {} impl PartialEq for MaterialPipelineKey where M::Data: PartialEq, { fn eq(&self, other: &Self) -> bool { self.mesh_key == other.mesh_key && self.bind_group_data == other.bind_group_data } } impl Clone for MaterialPipelineKey where M::Data: Clone, { fn clone(&self) -> Self { Self { mesh_key: self.mesh_key, bind_group_data: self.bind_group_data.clone(), } } } impl Hash for MaterialPipelineKey where M::Data: Hash, { fn hash(&self, state: &mut H) { self.mesh_key.hash(state); self.bind_group_data.hash(state); } } /// Render pipeline data for a given [`Material`]. #[derive(Resource)] pub struct MaterialPipeline { pub mesh_pipeline: MeshPipeline, pub material_layout: BindGroupLayout, pub vertex_shader: Option>, pub fragment_shader: Option>, pub marker: PhantomData, } impl Clone for MaterialPipeline { fn clone(&self) -> Self { Self { mesh_pipeline: self.mesh_pipeline.clone(), material_layout: self.material_layout.clone(), vertex_shader: self.vertex_shader.clone(), fragment_shader: self.fragment_shader.clone(), marker: PhantomData, } } } impl SpecializedMeshPipeline for MaterialPipeline where M::Data: PartialEq + Eq + Hash + Clone, { type Key = MaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> 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(); } descriptor.layout.insert(2, self.material_layout.clone()); M::specialize(self, &mut descriptor, layout, key)?; Ok(descriptor) } } impl FromWorld for MaterialPipeline { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); let render_device = world.resource::(); MaterialPipeline { mesh_pipeline: world.resource::().clone(), material_layout: M::bind_group_layout(render_device), vertex_shader: match M::vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, fragment_shader: match M::fragment_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, marker: PhantomData, } } } type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, SetMaterialBindGroup, DrawMesh, ); /// Sets the bind group for a given [`Material`] at the configured `I` index. pub struct SetMaterialBindGroup(PhantomData); impl RenderCommand

for SetMaterialBindGroup { type Param = ( SRes>>, SRes>, ); type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let materials = materials.into_inner(); let material_instances = material_instances.into_inner(); let Some(material_asset_id) = material_instances.get(&item.entity()) else { return RenderCommandResult::Skip; }; let Some(material) = materials.get(*material_asset_id) else { return RenderCommandResult::Skip; }; pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } } pub type RenderMaterialInstances = ExtractedInstances>; pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode, msaa: &Msaa) -> MeshPipelineKey { match alpha_mode { // Premultiplied and Add share the same pipeline key // They're made distinct in the PBR shader, via `premultiply_alpha()` AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA, AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA, AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY, AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD, AlphaMode::AlphaToCoverage => match *msaa { Msaa::Off => MeshPipelineKey::MAY_DISCARD, _ => MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE, }, _ => MeshPipelineKey::NONE, } } pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey { match tonemapping { Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, Tonemapping::SomewhatBoringDisplayTransform => { MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM } Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, } } pub const fn screen_space_specular_transmission_pipeline_key( screen_space_transmissive_blur_quality: ScreenSpaceTransmissionQuality, ) -> MeshPipelineKey { match screen_space_transmissive_blur_quality { ScreenSpaceTransmissionQuality::Low => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_LOW } ScreenSpaceTransmissionQuality::Medium => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_MEDIUM } ScreenSpaceTransmissionQuality::High => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_HIGH } ScreenSpaceTransmissionQuality::Ultra => { MeshPipelineKey::SCREEN_SPACE_SPECULAR_TRANSMISSION_ULTRA } } } /// For each view, iterates over all the meshes visible from that view and adds /// them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as appropriate. #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( ( opaque_draw_functions, alpha_mask_draw_functions, transmissive_draw_functions, transparent_draw_functions, ): ( Res>, Res>, Res>, Res>, ), material_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, render_meshes: Res>, render_materials: Res>>, render_mesh_instances: Res, render_material_instances: Res>, render_lightmaps: Res, render_visibility_ranges: Res, mut opaque_render_phases: ResMut>, mut alpha_mask_render_phases: ResMut>, mut transmissive_render_phases: ResMut>, mut transparent_render_phases: ResMut>, views: Query<( Entity, &ExtractedView, &VisibleEntities, &Msaa, Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, Has, ( Has, Has, Has, Has, ), Option<&Camera3d>, Has, Option<&Projection>, ( Has>, Has>, ), )>, ) where M::Data: PartialEq + Eq + Hash + Clone, { for ( view_entity, view, visible_entities, msaa, tonemapping, dither, shadow_filter_method, ssao, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), camera_3d, temporal_jitter, projection, (has_environment_maps, has_irradiance_volumes), ) in &views { let ( Some(opaque_phase), Some(alpha_mask_phase), Some(transmissive_phase), Some(transparent_phase), ) = ( opaque_render_phases.get_mut(&view_entity), alpha_mask_render_phases.get_mut(&view_entity), transmissive_render_phases.get_mut(&view_entity), transparent_render_phases.get_mut(&view_entity), ) else { continue; }; let draw_opaque_pbr = opaque_draw_functions.read().id::>(); let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transmissive_pbr = transmissive_draw_functions.read().id::>(); let draw_transparent_pbr = transparent_draw_functions.read().id::>(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; } if depth_prepass { view_key |= MeshPipelineKey::DEPTH_PREPASS; } if motion_vector_prepass { view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } if deferred_prepass { view_key |= MeshPipelineKey::DEFERRED_PREPASS; } if temporal_jitter { view_key |= MeshPipelineKey::TEMPORAL_JITTER; } if has_environment_maps { view_key |= MeshPipelineKey::ENVIRONMENT_MAP; } if has_irradiance_volumes { view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; } if let Some(projection) = projection { view_key |= match projection { Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, }; } match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { ShadowFilteringMethod::Hardware2x2 => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; } ShadowFilteringMethod::Gaussian => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; } ShadowFilteringMethod::Temporal => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; } } if !view.hdr { if let Some(tonemapping) = tonemapping { view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; view_key |= tonemapping_pipeline_key(*tonemapping); } if let Some(DebandDither::Enabled) = dither { view_key |= MeshPipelineKey::DEBAND_DITHER; } } if ssao { view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; } if let Some(camera_3d) = camera_3d { view_key |= screen_space_specular_transmission_pipeline_key( camera_3d.screen_space_specular_transmission_quality, ); } let rangefinder = view.rangefinder3d(); for visible_entity in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity) else { continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let Some(material) = render_materials.get(*material_asset_id) else { continue; }; let mut mesh_pipeline_key_bits = material.properties.mesh_pipeline_key_bits; mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key( material.properties.alpha_mode, msaa, )); let mut mesh_key = view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits()) | mesh_pipeline_key_bits; let lightmap_image = render_lightmaps .render_lightmaps .get(visible_entity) .map(|lightmap| lightmap.image); if lightmap_image.is_some() { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) { mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } if motion_vector_prepass { // If the previous frame have skins or morph targets, note that. if mesh_instance .flags .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN) { mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN; } if mesh_instance .flags .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH) { mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH; } } let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline, MaterialPipelineKey { mesh_key, bind_group_data: material.key.clone(), }, &mesh.layout, ); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); continue; } }; mesh_instance .material_bind_group_id .set(material.get_bind_group_id()); match mesh_key .intersection(MeshPipelineKey::BLEND_RESERVED_BITS | MeshPipelineKey::MAY_DISCARD) { MeshPipelineKey::BLEND_OPAQUE | MeshPipelineKey::BLEND_ALPHA_TO_COVERAGE => { if material.properties.reads_view_transmission_texture { let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transmissive_phase.add(Transmissive3d { entity: *visible_entity, draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, extra_index: PhaseItemExtraIndex::NONE, }); } else if material.properties.render_method == OpaqueRendererMethod::Forward { let bin_key = Opaque3dBinKey { draw_function: draw_opaque_pbr, pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material.get_bind_group_id().0, lightmap_image, }; opaque_phase.add( bin_key, *visible_entity, BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } } // Alpha mask MeshPipelineKey::MAY_DISCARD => { if material.properties.reads_view_transmission_texture { let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transmissive_phase.add(Transmissive3d { entity: *visible_entity, draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, extra_index: PhaseItemExtraIndex::NONE, }); } else if material.properties.render_method == OpaqueRendererMethod::Forward { let bin_key = OpaqueNoLightmap3dBinKey { draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material.get_bind_group_id().0, }; alpha_mask_phase.add( bin_key, *visible_entity, BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } } _ => { let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transparent_phase.add(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, pipeline: pipeline_id, distance, batch_range: 0..1, extra_index: PhaseItemExtraIndex::NONE, }); } } } } } /// Default render method used for opaque materials. #[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)] pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod); impl DefaultOpaqueRendererMethod { pub fn forward() -> Self { DefaultOpaqueRendererMethod(OpaqueRendererMethod::Forward) } pub fn deferred() -> Self { DefaultOpaqueRendererMethod(OpaqueRendererMethod::Deferred) } pub fn set_to_forward(&mut self) { self.0 = OpaqueRendererMethod::Forward; } pub fn set_to_deferred(&mut self) { self.0 = OpaqueRendererMethod::Deferred; } } /// Render method used for opaque materials. /// /// The forward rendering main pass draws each mesh entity and shades it according to its /// corresponding material and the lights that affect it. Some render features like Screen Space /// Ambient Occlusion require running depth and normal prepasses, that are 'deferred'-like /// prepasses over all mesh entities to populate depth and normal textures. This means that when /// using render features that require running prepasses, multiple passes over all visible geometry /// are required. This can be slow if there is a lot of geometry that cannot be batched into few /// draws. /// /// Deferred rendering runs a prepass to gather not only geometric information like depth and /// normals, but also all the material properties like base color, emissive color, reflectance, /// metalness, etc, and writes them into a deferred 'g-buffer' texture. The deferred main pass is /// then a fullscreen pass that reads data from these textures and executes shading. This allows /// for one pass over geometry, but is at the cost of not being able to use MSAA, and has heavier /// bandwidth usage which can be unsuitable for low end mobile or other bandwidth-constrained devices. /// /// If a material indicates `OpaqueRendererMethod::Auto`, `DefaultOpaqueRendererMethod` will be used. #[derive(Default, Clone, Copy, Debug, PartialEq, Reflect)] pub enum OpaqueRendererMethod { #[default] Forward, Deferred, Auto, } /// Common [`Material`] properties, calculated for a specific material instance. pub struct MaterialProperties { /// Is this material should be rendered by the deferred renderer when. /// [`AlphaMode::Opaque`] or [`AlphaMode::Mask`] pub render_method: OpaqueRendererMethod, /// The [`AlphaMode`] of this material. pub alpha_mode: AlphaMode, /// The bits in the [`MeshPipelineKey`] for this material. /// /// These are precalculated so that we can just "or" them together in /// [`queue_material_meshes`]. pub mesh_pipeline_key_bits: MeshPipelineKey, /// Add a bias to the view depth of the mesh which can be used to force a specific render order /// for meshes with equal depth, to avoid z-fighting. /// The bias is in depth-texture units so large values may be needed to overcome small depth differences. pub depth_bias: f32, /// Whether the material would like to read from [`ViewTransmissionTexture`](bevy_core_pipeline::core_3d::ViewTransmissionTexture). /// /// This allows taking color output from the [`Opaque3d`] pass as an input, (for screen-space transmission) but requires /// rendering to take place in a separate [`Transmissive3d`] pass. pub reads_view_transmission_texture: bool, } /// Data prepared for a [`Material`] instance. pub struct PreparedMaterial { pub bindings: Vec<(u32, OwnedBindingResource)>, pub bind_group: BindGroup, pub key: T::Data, pub properties: MaterialProperties, } impl RenderAsset for PreparedMaterial { type SourceAsset = M; type Param = ( SRes, SRes>, SRes, M::Param, ); fn prepare_asset( material: Self::SourceAsset, (render_device, pipeline, default_opaque_render_method, ref mut material_param): &mut SystemParamItem, ) -> Result> { match material.as_bind_group(&pipeline.material_layout, render_device, material_param) { Ok(prepared) => { let method = match material.opaque_render_method() { OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, OpaqueRendererMethod::Auto => default_opaque_render_method.0, }; let mut mesh_pipeline_key_bits = MeshPipelineKey::empty(); mesh_pipeline_key_bits.set( MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE, material.reads_view_transmission_texture(), ); Ok(PreparedMaterial { bindings: prepared.bindings, bind_group: prepared.bind_group, key: prepared.data, properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), reads_view_transmission_texture: mesh_pipeline_key_bits .contains(MeshPipelineKey::READS_VIEW_TRANSMISSION_TEXTURE), render_method: method, mesh_pipeline_key_bits, }, }) } Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } } #[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] pub struct MaterialBindGroupId(pub Option); impl MaterialBindGroupId { pub fn new(id: BindGroupId) -> Self { Self(Some(id)) } } impl From for MaterialBindGroupId { fn from(value: BindGroup) -> Self { Self::new(value.id()) } } /// An atomic version of [`MaterialBindGroupId`] that can be read from and written to /// safely from multiple threads. #[derive(Default)] pub struct AtomicMaterialBindGroupId(AtomicU32); impl AtomicMaterialBindGroupId { /// Stores a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering /// relative to other operations. /// /// See also: [`AtomicU32::store`]. pub fn set(&self, id: MaterialBindGroupId) { let id = if let Some(id) = id.0 { NonZero::::from(id).get() } else { 0 }; self.0.store(id, Ordering::Relaxed); } /// Loads a value atomically. Uses [`Ordering::Relaxed`] so there is zero guarantee of ordering /// relative to other operations. /// /// See also: [`AtomicU32::load`]. pub fn get(&self) -> MaterialBindGroupId { MaterialBindGroupId( NonZero::::new(self.0.load(Ordering::Relaxed)).map(BindGroupId::from), ) } } impl PreparedMaterial { pub fn get_bind_group_id(&self) -> MaterialBindGroupId { MaterialBindGroupId(Some(self.bind_group.id())) } }