use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; use crate::material_bind_groups::{MaterialBindGroupAllocator, MaterialBindingId}; #[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, Opaque3dBatchSetKey, Opaque3dBinKey, ScreenSpaceTransmissionQuality, Transmissive3d, Transparent3d, }, oit::OrderIndependentTransparencySettings, prepass::{ DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, OpaqueNoLightmap3dBinKey, }, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ prelude::*, system::{ lifetimeless::{SRes, SResMut}, SystemParamItem, }, }; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::{ camera::TemporalJitter, extract_resource::ExtractResource, mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, render_phase::*, render_resource::*, renderer::RenderDevice, view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility}, Extract, }; use bevy_render::{mesh::allocator::MeshAllocator, sync_world::MainEntityHashMap}; use bevy_render::{texture::FallbackImage, view::RenderVisibleEntities}; use bevy_utils::{hashbrown::hash_map::Entry, tracing::error}; use core::{hash::Hash, marker::PhantomData}; /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh3d`] 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, MeshMaterial3d}; /// # use bevy_ecs::prelude::*; /// # use bevy_image::Image; /// # use bevy_reflect::TypePath; /// # use bevy_render::{mesh::{Mesh, Mesh3d}, render_resource::{AsBindGroup, ShaderRef}}; /// # use bevy_color::LinearRgba; /// # use bevy_color::palettes::basic::RED; /// # use bevy_asset::{Handle, AssetServer, Assets, Asset}; /// # use bevy_math::primitives::Capsule3d; /// # /// #[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 with a mesh using `CustomMaterial`. /// fn setup( /// mut commands: Commands, /// mut meshes: ResMut>, /// mut materials: ResMut>, /// asset_server: Res /// ) { /// commands.spawn(( /// Mesh3d(meshes.add(Capsule3d::default())), /// MeshMaterial3d(materials.add(CustomMaterial { /// color: RED.into(), /// color_texture: asset_server.load("some_image.png"), /// })), /// )); /// } /// ``` /// /// 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. /// /// See [`crate::meshlet::MeshletMesh`] for limitations. #[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. /// /// See [`crate::meshlet::MeshletMesh`] for limitations. #[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. /// /// See [`crate::meshlet::MeshletMesh`] for limitations. #[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::() .register_type::>() .add_plugins(RenderAssetPlugin::>::default()); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .init_resource::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .init_resource::>>() .add_systems( ExtractSchedule, extract_mesh_materials:: .before(extract_meshes_for_cpu_building) .before(extract_meshes_for_gpu_building), ) .add_systems( Render, queue_material_meshes:: .in_set(RenderSet::QueueMeshes) .after(prepare_assets::>), ) .add_systems( Render, prepare_material_bind_groups:: .in_set(RenderSet::PrepareBindGroups) .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::>() .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>, /// Whether this material *actually* uses bindless resources, taking the /// platform support (or lack thereof) of bindless resources into account. pub bindless: bool, 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(), bindless: self.bindless, 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)?; // If bindless mode is on, add a `BINDLESS` define. if self.bindless { descriptor.vertex.shader_defs.push("BINDLESS".into()); if let Some(ref mut fragment) = descriptor.fragment { fragment.shader_defs.push("BINDLESS".into()); } } 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)), }, bindless: material_bind_groups::material_uses_bindless_resources::(render_device), 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>, SRes>, ); type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, (materials, material_instances, material_bind_group_allocator): SystemParamItem< 'w, '_, Self::Param, >, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let materials = materials.into_inner(); let material_instances = material_instances.into_inner(); let material_bind_group_allocator = material_bind_group_allocator.into_inner(); let Some(material_asset_id) = material_instances.get(&item.main_entity()) else { return RenderCommandResult::Skip; }; let Some(material) = materials.get(*material_asset_id) else { return RenderCommandResult::Skip; }; let Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group) else { return RenderCommandResult::Skip; }; let Some(bind_group) = material_bind_group.get_bind_group() else { return RenderCommandResult::Skip; }; pass.set_bind_group(I, bind_group, &[]); RenderCommandResult::Success } } /// Stores all extracted instances of a [`Material`] in the render world. #[derive(Resource, Deref, DerefMut)] pub struct RenderMaterialInstances(pub MainEntityHashMap>); impl Default for RenderMaterialInstances { fn default() -> Self { Self(Default::default()) } } 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 } } } fn extract_mesh_materials( mut material_instances: ResMut>, mut material_ids: ResMut, mut material_bind_group_allocator: ResMut>, query: Extract)>>, ) { material_instances.clear(); for (entity, view_visibility, material) in &query { if view_visibility.get() { material_instances.insert(entity.into(), material.id()); // Allocate a slot for this material in the bind group. let material_id = material.id().untyped(); material_ids .mesh_to_material .insert(entity.into(), material_id); if let Entry::Vacant(entry) = material_ids.material_to_binding.entry(material_id) { entry.insert(material_bind_group_allocator.allocate()); } } } } /// 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, (mesh_allocator, material_bind_group_allocator): ( Res, 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, &RenderVisibleEntities, &Msaa, Option<&Tonemapping>, Option<&DebandDither>, Option<&ShadowFilteringMethod>, Has, ( Has, Has, Has, Has, ), Option<&Camera3d>, Has, Option<&Projection>, ( Has>, 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), has_oit, ) 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 has_oit { view_key |= MeshPipelineKey::OIT_ENABLED; } 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 (render_entity, 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 Some(material_bind_group) = material_bind_group_allocator.get(material.binding.group) 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_bind_group .get_extra_data(material.binding.slot) .clone(), }, &mesh.layout, ); let pipeline_id = match pipeline_id { Ok(id) => id, Err(err) => { error!("{}", err); continue; } }; 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: (*render_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 (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id); let bin_key = Opaque3dBinKey { batch_set_key: Opaque3dBatchSetKey { draw_function: draw_opaque_pbr, pipeline: pipeline_id, material_bind_group_index: Some(material.binding.group.0), vertex_slab: vertex_slab.unwrap_or_default(), index_slab, lightmap_image, }, asset_id: mesh_instance.mesh_asset_id.into(), }; opaque_phase.add( bin_key, (*render_entity, *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: (*render_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_index: Some(material.binding.group.0), }; alpha_mask_phase.add( bin_key, (*render_entity, *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: (*render_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)] #[reflect(Resource, Default, Debug)] 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 binding: MaterialBindingId, pub properties: MaterialProperties, pub phantom: PhantomData, } impl RenderAsset for PreparedMaterial { type SourceAsset = M; type Param = ( SRes, SRes>, SRes, SRes, SResMut>, M::Param, ); fn prepare_asset( material: Self::SourceAsset, material_id: AssetId, ( render_device, pipeline, default_opaque_render_method, mesh_material_ids, ref mut bind_group_allocator, ref mut material_param, ): &mut SystemParamItem, ) -> Result> { // Fetch the material binding ID, so that we can write it in to the // `PreparedMaterial`. let Some(material_binding_id) = mesh_material_ids .material_to_binding .get(&material_id.untyped()) else { return Err(PrepareAssetError::RetryNextUpdate(material)); }; 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(), ); match material.unprepared_bind_group( &pipeline.material_layout, render_device, material_param, ) { Ok(unprepared) => { bind_group_allocator.init(render_device, *material_binding_id, unprepared); Ok(PreparedMaterial { binding: *material_binding_id, 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, }, phantom: PhantomData, }) } Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } Err(AsBindGroupError::CreateBindGroupDirectly) => { // This material has opted out of automatic bind group creation // and is requesting a fully-custom bind group. Invoke // `as_bind_group` as requested, and store the resulting bind // group in the slot. match material.as_bind_group( &pipeline.material_layout, render_device, material_param, ) { Ok(prepared_bind_group) => { // Store the resulting bind group directly in the slot. bind_group_allocator.init_custom( *material_binding_id, prepared_bind_group.bind_group, prepared_bind_group.data, ); Ok(PreparedMaterial { binding: *material_binding_id, 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, }, phantom: PhantomData, }) } Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } fn unload_asset( asset_id: AssetId, (_, _, _, mesh_material_ids, ref mut bind_group_allocator, _): &mut SystemParamItem< Self::Param, >, ) { // Mark this material's slot in the binding array as free. let Some(material_binding_id) = mesh_material_ids .material_to_binding .get(&asset_id.untyped()) else { return; }; bind_group_allocator.free(*material_binding_id); } } #[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()) } } /// A system that creates and/or recreates any bind groups that contain /// materials that were modified this frame. pub fn prepare_material_bind_groups( mut allocator: ResMut>, render_device: Res, fallback_image: Res, fallback_resources: Res, ) where M: Material, { allocator.prepare_bind_groups(&render_device, &fallback_image, &fallback_resources); }