use bevy_app::{Plugin, PreUpdate}; use bevy_asset::{load_internal_asset, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::CORE_3D_DEPTH_FORMAT, deferred::{ AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT, }, prelude::Camera3d, prepass::{ AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, }, }; use bevy_ecs::{ prelude::*, system::{ lifetimeless::{Read, SRes}, SystemParamItem, }, }; use bevy_math::{Affine3A, Mat4}; use bevy_render::{ batching::batch_and_prepare_render_phase, globals::{GlobalsBuffer, GlobalsUniform}, mesh::MeshVertexBufferLayout, prelude::{Camera, Mesh}, render_asset::RenderAssets, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState, PushConstantRange, RenderPipelineDescriptor, Shader, ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, StencilFaceState, StencilState, TextureAspect, TextureFormat, TextureSampleType, TextureView, TextureViewDescriptor, TextureViewDimension, VertexState, }, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, FallbackImageMsaa}, view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::prelude::GlobalTransform; use bevy_utils::default; use bevy_utils::tracing::error; use crate::{ prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, }; use std::{hash::Hash, marker::PhantomData}; pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); pub const PREPASS_BINDINGS_SHADER_HANDLE: Handle = Handle::weak_from_u128(5533152893177403494); pub const PREPASS_UTILS_SHADER_HANDLE: Handle = Handle::weak_from_u128(4603948296044544); pub const PREPASS_IO_SHADER_HANDLE: Handle = Handle::weak_from_u128(81212356509530944); /// Sets up everything required to use the prepass pipeline. /// /// This does not add the actual prepasses, see [`PrepassPlugin`] for that. pub struct PrepassPipelinePlugin(PhantomData); impl Default for PrepassPipelinePlugin { fn default() -> Self { Self(Default::default()) } } impl Plugin for PrepassPipelinePlugin where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut bevy_app::App) { load_internal_asset!( app, PREPASS_SHADER_HANDLE, "prepass.wgsl", Shader::from_wgsl ); load_internal_asset!( app, PREPASS_BINDINGS_SHADER_HANDLE, "prepass_bindings.wgsl", Shader::from_wgsl ); load_internal_asset!( app, PREPASS_UTILS_SHADER_HANDLE, "prepass_utils.wgsl", Shader::from_wgsl ); load_internal_asset!( app, PREPASS_IO_SHADER_HANDLE, "prepass_io.wgsl", Shader::from_wgsl ); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app .add_systems( Render, prepare_prepass_view_bind_group::.in_set(RenderSet::PrepareBindGroups), ) .init_resource::() .init_resource::>>() .init_resource::(); } fn finish(&self, app: &mut bevy_app::App) { let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app.init_resource::>(); } } /// Sets up the prepasses for a [`Material`]. /// /// This depends on the [`PrepassPipelinePlugin`]. pub struct PrepassPlugin(PhantomData); impl Default for PrepassPlugin { fn default() -> Self { Self(Default::default()) } } impl Plugin for PrepassPlugin where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut bevy_app::App) { let no_prepass_plugin_loaded = app.world.get_resource::().is_none(); if no_prepass_plugin_loaded { app.insert_resource(AnyPrepassPluginLoaded) // At the start of each frame, last frame's GlobalTransforms become this frame's PreviousGlobalTransforms // and last frame's view projection matrices become this frame's PreviousViewProjections .add_systems( PreUpdate, ( update_mesh_previous_global_transforms, update_previous_view_projections, ), ); } let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; if no_prepass_plugin_loaded { render_app .add_systems(ExtractSchedule, extract_camera_previous_view_projection) .add_systems( Render, ( prepare_previous_view_projection_uniforms, batch_and_prepare_render_phase::, batch_and_prepare_render_phase::, ) .in_set(RenderSet::PrepareResources), ); } render_app .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_render_command::>() .add_systems( Render, queue_prepass_material_meshes:: .in_set(RenderSet::QueueMeshes) .after(prepare_materials::), ); } } #[derive(Resource)] struct AnyPrepassPluginLoaded; #[derive(Component, ShaderType, Clone)] pub struct PreviousViewProjection { pub view_proj: Mat4, } pub fn update_previous_view_projections( mut commands: Commands, query: Query<(Entity, &Camera, &GlobalTransform), (With, With)>, ) { for (entity, camera, camera_transform) in &query { commands.entity(entity).insert(PreviousViewProjection { view_proj: camera.projection_matrix() * camera_transform.compute_matrix().inverse(), }); } } #[derive(Component)] pub struct PreviousGlobalTransform(pub Affine3A); pub fn update_mesh_previous_global_transforms( mut commands: Commands, views: Query<&Camera, (With, With)>, meshes: Query<(Entity, &GlobalTransform), With>>, ) { let should_run = views.iter().any(|camera| camera.is_active); if should_run { for (entity, transform) in &meshes { commands .entity(entity) .insert(PreviousGlobalTransform(transform.affine())); } } } #[derive(Resource)] pub struct PrepassPipeline { pub view_layout_motion_vectors: BindGroupLayout, pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, pub material_layout: BindGroupLayout, pub prepass_material_vertex_shader: Option>, pub prepass_material_fragment_shader: Option>, pub deferred_material_vertex_shader: Option>, pub deferred_material_fragment_shader: Option>, pub material_pipeline: MaterialPipeline, _marker: PhantomData, } impl FromWorld for PrepassPipeline { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); let asset_server = world.resource::(); let view_layout_motion_vectors = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(ViewUniform::min_size()), }, count: None, }, // Globals BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(GlobalsUniform::min_size()), }, count: None, }, // PreviousViewProjection BindGroupLayoutEntry { binding: 2, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(PreviousViewProjection::min_size()), }, count: None, }, ], label: Some("prepass_view_layout_motion_vectors"), }); let view_layout_no_motion_vectors = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(ViewUniform::min_size()), }, count: None, }, // Globals BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(GlobalsUniform::min_size()), }, count: None, }, ], label: Some("prepass_view_layout_no_motion_vectors"), }); let mesh_pipeline = world.resource::(); PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), prepass_material_vertex_shader: match M::prepass_vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, prepass_material_fragment_shader: match M::prepass_fragment_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, deferred_material_vertex_shader: match M::deferred_vertex_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, deferred_material_fragment_shader: match M::deferred_fragment_shader() { ShaderRef::Default => None, ShaderRef::Handle(handle) => Some(handle), ShaderRef::Path(path) => Some(asset_server.load(path)), }, material_layout: M::bind_group_layout(render_device), material_pipeline: world.resource::>().clone(), _marker: PhantomData, } } } impl SpecializedMeshPipeline for PrepassPipeline where M::Data: PartialEq + Eq + Hash + Clone, { type Key = MaterialPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut bind_group_layouts = vec![if key .mesh_key .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { self.view_layout_motion_vectors.clone() } else { self.view_layout_no_motion_vectors.clone() }]; let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); // Let the shader code know that it's running in a prepass pipeline. // (PBR code will use this to detect that it's running in deferred mode, // since that's the only time it gets called from a prepass pipeline.) shader_defs.push("PREPASS_PIPELINE".into()); // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.insert(1, self.material_layout.clone()); #[cfg(all(feature = "webgl", target_arch = "wasm32"))] shader_defs.push("WEBGL2".into()); shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); if key.mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) { shader_defs.push("DEPTH_PREPASS".into()); } if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) { shader_defs.push("MAY_DISCARD".into()); } let blend_key = key .mesh_key .intersection(MeshPipelineKey::BLEND_RESERVED_BITS); if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); } if blend_key == MeshPipelineKey::BLEND_ALPHA { shader_defs.push("BLEND_ALPHA".into()); } if layout.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) { shader_defs.push("DEPTH_CLAMP_ORTHO".into()); // PERF: This line forces the "prepass fragment shader" to always run in // common scenarios like "directional light calculation". Doing so resolves // a pretty nasty depth clamping bug, but it also feels a bit excessive. // We should try to find a way to resolve this without forcing the fragment // shader to run. // https://github.com/bevyengine/bevy/pull/8877 shader_defs.push("PREPASS_FRAGMENT".into()); } if layout.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1)); } if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { shader_defs.push("NORMAL_PREPASS".into()); } if key .mesh_key .intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2)); shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into()); if layout.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } } if key .mesh_key .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into()); } if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("DEFERRED_PREPASS".into()); if layout.contains(Mesh::ATTRIBUTE_COLOR) { shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(6)); } } if key .mesh_key .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } if key.mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS, ) { shader_defs.push("PREPASS_FRAGMENT".into()); } let bind_group = setup_morph_and_skinning_defs( &self.mesh_layouts, layout, 4, &key.mesh_key, &mut shader_defs, &mut vertex_attributes, ); bind_group_layouts.insert(2, bind_group); let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 let mut targets = vec![ key.mesh_key .contains(MeshPipelineKey::NORMAL_PREPASS) .then_some(ColorTargetState { format: NORMAL_PREPASS_FORMAT, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. blend: None, write_mask: ColorWrites::ALL, }), key.mesh_key .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) .then_some(ColorTargetState { format: MOTION_VECTOR_PREPASS_FORMAT, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. blend: None, write_mask: ColorWrites::ALL, }), key.mesh_key .contains(MeshPipelineKey::DEFERRED_PREPASS) .then_some(ColorTargetState { format: DEFERRED_PREPASS_FORMAT, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. blend: None, write_mask: ColorWrites::ALL, }), key.mesh_key .contains(MeshPipelineKey::DEFERRED_PREPASS) .then_some(ColorTargetState { format: DEFERRED_LIGHTING_PASS_ID_FORMAT, blend: None, write_mask: ColorWrites::ALL, }), ]; if targets.iter().all(Option::is_none) { // if no targets are required then clear the list, so that no fragment shader is required // (though one may still be used for discarding depth buffer writes) targets.clear(); } // The fragment shader is only used when the normal prepass or motion vectors prepass // is enabled or the material uses alpha cutoff values and doesn't rely on the standard // prepass shader or we are clamping the orthographic depth. let fragment_required = !targets.is_empty() || key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) && self.prepass_material_fragment_shader.is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material let frag_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { match self.deferred_material_fragment_shader.clone() { Some(frag_shader_handle) => frag_shader_handle, _ => PREPASS_SHADER_HANDLE, } } else { match self.prepass_material_fragment_shader.clone() { Some(frag_shader_handle) => frag_shader_handle, _ => PREPASS_SHADER_HANDLE, } }; FragmentState { shader: frag_shader_handle, entry_point: "fragment".into(), shader_defs: shader_defs.clone(), targets, } }); // Use the vertex shader from the material if present let vert_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { if let Some(handle) = &self.deferred_material_vertex_shader { handle.clone() } else { PREPASS_SHADER_HANDLE } } else if let Some(handle) = &self.prepass_material_vertex_shader { handle.clone() } else { PREPASS_SHADER_HANDLE }; let mut push_constant_ranges = Vec::with_capacity(1); if cfg!(all(feature = "webgl", target_arch = "wasm32")) { push_constant_ranges.push(PushConstantRange { stages: ShaderStages::VERTEX, range: 0..4, }); } let mut descriptor = RenderPipelineDescriptor { vertex: VertexState { shader: vert_shader_handle, entry_point: "vertex".into(), shader_defs, buffers: vec![vertex_buffer_layout], }, fragment, layout: bind_group_layouts, primitive: PrimitiveState { topology: key.mesh_key.primitive_topology(), strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: None, unclipped_depth: false, polygon_mode: PolygonMode::Fill, conservative: false, }, depth_stencil: Some(DepthStencilState { format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, depth_compare: CompareFunction::GreaterEqual, 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, }, }), multisample: MultisampleState { count: key.mesh_key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, push_constant_ranges, label: Some("prepass_pipeline".into()), }; // This is a bit risky because it's possible to change something that would // break the prepass but be fine in the main pass. // Since this api is pretty low-level it doesn't matter that much, but it is a potential issue. M::specialize(&self.material_pipeline, &mut descriptor, layout, key)?; Ok(descriptor) } } pub fn get_bind_group_layout_entries( bindings: [u32; 4], multisampled: bool, ) -> [BindGroupLayoutEntry; 4] { [ // Depth texture BindGroupLayoutEntry { binding: bindings[0], visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled, sample_type: TextureSampleType::Depth, view_dimension: TextureViewDimension::D2, }, count: None, }, // Normal texture BindGroupLayoutEntry { binding: bindings[1], visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled, sample_type: TextureSampleType::Float { filterable: false }, view_dimension: TextureViewDimension::D2, }, count: None, }, // Motion Vectors texture BindGroupLayoutEntry { binding: bindings[2], visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled, sample_type: TextureSampleType::Float { filterable: false }, view_dimension: TextureViewDimension::D2, }, count: None, }, // Deferred texture BindGroupLayoutEntry { binding: bindings[3], visibility: ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, sample_type: TextureSampleType::Uint, view_dimension: TextureViewDimension::D2, }, count: None, }, ] } // Needed so the texture views can live long enough. pub struct PrepassBindingsSet([TextureView; 4]); impl PrepassBindingsSet { pub fn get_entries(&self, bindings: [u32; 4]) -> [BindGroupEntry; 4] { [ BindGroupEntry { binding: bindings[0], resource: BindingResource::TextureView(&self.0[0]), }, BindGroupEntry { binding: bindings[1], resource: BindingResource::TextureView(&self.0[1]), }, BindGroupEntry { binding: bindings[2], resource: BindingResource::TextureView(&self.0[2]), }, BindGroupEntry { binding: bindings[3], resource: BindingResource::TextureView(&self.0[3]), }, ] } } pub fn get_bindings( prepass_textures: Option<&ViewPrepassTextures>, fallback_images: &mut FallbackImageMsaa, msaa: &Msaa, ) -> PrepassBindingsSet { let depth_desc = TextureViewDescriptor { label: Some("prepass_depth"), aspect: TextureAspect::DepthOnly, ..default() }; let depth_view = match prepass_textures.and_then(|x| x.depth.as_ref()) { Some(texture) => texture.texture.create_view(&depth_desc), None => fallback_images .image_for_samplecount(msaa.samples(), CORE_3D_DEPTH_FORMAT) .texture .create_view(&depth_desc), }; let normal_motion_vectors_fallback = &fallback_images .image_for_samplecount(msaa.samples(), TextureFormat::bevy_default()) .texture_view; let normal_view = match prepass_textures.and_then(|x| x.normal.as_ref()) { Some(texture) => &texture.default_view, None => normal_motion_vectors_fallback, } .clone(); let motion_vectors_view = match prepass_textures.and_then(|x| x.motion_vectors.as_ref()) { Some(texture) => &texture.default_view, None => normal_motion_vectors_fallback, } .clone(); let deferred_fallback = &fallback_images .image_for_samplecount(1, TextureFormat::Rgba32Uint) .texture_view; let deferred_view = match prepass_textures.and_then(|x| x.deferred.as_ref()) { Some(texture) => &texture.default_view, None => deferred_fallback, } .clone(); PrepassBindingsSet([depth_view, normal_view, motion_vectors_view, deferred_view]) } // Extract the render phases for the prepass pub fn extract_camera_previous_view_projection( mut commands: Commands, cameras_3d: Extract), With>>, ) { for (entity, camera, maybe_previous_view_proj) in cameras_3d.iter() { if camera.is_active { let mut entity = commands.get_or_spawn(entity); if let Some(previous_view) = maybe_previous_view_proj { entity.insert(previous_view.clone()); } } } } #[derive(Resource, Default)] pub struct PreviousViewProjectionUniforms { pub uniforms: DynamicUniformBuffer, } #[derive(Component)] pub struct PreviousViewProjectionUniformOffset { pub offset: u32, } pub fn prepare_previous_view_projection_uniforms( mut commands: Commands, render_device: Res, render_queue: Res, mut view_uniforms: ResMut, views: Query< (Entity, &ExtractedView, Option<&PreviousViewProjection>), With, >, ) { let views_iter = views.iter(); let view_count = views_iter.len(); let Some(mut writer) = view_uniforms .uniforms .get_writer(view_count, &render_device, &render_queue) else { return; }; for (entity, camera, maybe_previous_view_proj) in views_iter { let view_projection = match maybe_previous_view_proj { Some(previous_view) => previous_view.clone(), None => PreviousViewProjection { view_proj: camera.projection * camera.transform.compute_matrix().inverse(), }, }; commands .entity(entity) .insert(PreviousViewProjectionUniformOffset { offset: writer.write(&view_projection), }); } } #[derive(Default, Resource)] pub struct PrepassViewBindGroup { motion_vectors: Option, no_motion_vectors: Option, } pub fn prepare_prepass_view_bind_group( render_device: Res, prepass_pipeline: Res>, view_uniforms: Res, globals_buffer: Res, previous_view_proj_uniforms: Res, mut prepass_view_bind_group: ResMut, ) { if let (Some(view_binding), Some(globals_binding)) = ( view_uniforms.uniforms.binding(), globals_buffer.buffer.binding(), ) { prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: view_binding.clone(), }, BindGroupEntry { binding: 1, resource: globals_binding.clone(), }, ], label: Some("prepass_view_no_motion_vectors_bind_group"), layout: &prepass_pipeline.view_layout_no_motion_vectors, })); if let Some(previous_view_proj_binding) = previous_view_proj_uniforms.uniforms.binding() { prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: view_binding, }, BindGroupEntry { binding: 1, resource: globals_binding, }, BindGroupEntry { binding: 2, resource: previous_view_proj_binding, }, ], label: Some("prepass_view_motion_vectors_bind_group"), layout: &prepass_pipeline.view_layout_motion_vectors, })); } } } #[allow(clippy::too_many_arguments)] pub fn queue_prepass_material_meshes( opaque_draw_functions: Res>, alpha_mask_draw_functions: Res>, opaque_deferred_draw_functions: Res>, alpha_mask_deferred_draw_functions: Res>, prepass_pipeline: Res>, mut pipelines: ResMut>>, pipeline_cache: Res, msaa: Res, render_meshes: Res>, render_mesh_instances: Res, render_materials: Res>, render_material_instances: Res>, mut views: Query<( &ExtractedView, &VisibleEntities, &mut RenderPhase, &mut RenderPhase, Option<&mut RenderPhase>, Option<&mut RenderPhase>, Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, Option<&DeferredPrepass>, )>, ) where M::Data: PartialEq + Eq + Hash + Clone, { let opaque_draw_prepass = opaque_draw_functions .read() .get_id::>() .unwrap(); let alpha_mask_draw_prepass = alpha_mask_draw_functions .read() .get_id::>() .unwrap(); let opaque_draw_deferred = opaque_deferred_draw_functions .read() .get_id::>() .unwrap(); let alpha_mask_draw_deferred = alpha_mask_deferred_draw_functions .read() .get_id::>() .unwrap(); for ( view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut opaque_deferred_phase, mut alpha_mask_deferred_phase, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass, ) in &mut views { let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); if depth_prepass.is_some() { view_key |= MeshPipelineKey::DEPTH_PREPASS; } if normal_prepass.is_some() { view_key |= MeshPipelineKey::NORMAL_PREPASS; } if motion_vector_prepass.is_some() { view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } let mut opaque_phase_deferred = opaque_deferred_phase.as_mut(); let mut alpha_mask_phase_deferred = alpha_mask_deferred_phase.as_mut(); let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; let Some(mesh_instance) = render_mesh_instances.get(visible_entity) else { continue; }; let Some(material) = render_materials.get(material_asset_id) else { continue; }; let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else { continue; }; let mut mesh_key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; if mesh.morph_targets.is_some() { mesh_key |= MeshPipelineKey::MORPH_TARGETS; } let alpha_mode = material.properties.alpha_mode; match alpha_mode { AlphaMode::Opaque => {} AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::MAY_DISCARD, AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => continue, } let forward = match material.properties.render_method { OpaqueRendererMethod::Forward => true, OpaqueRendererMethod::Deferred => false, OpaqueRendererMethod::Auto => unreachable!(), }; let deferred = deferred_prepass.is_some() && !forward; if deferred { mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; } let pipeline_id = pipelines.specialize( &pipeline_cache, &prepass_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; } }; let distance = rangefinder .distance_translation(&mesh_instance.transforms.transform.translation) + material.properties.depth_bias; match alpha_mode { AlphaMode::Opaque => { if deferred { opaque_phase_deferred .as_mut() .unwrap() .add(Opaque3dDeferred { entity: *visible_entity, draw_function: opaque_draw_deferred, pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } else { opaque_phase.add(Opaque3dPrepass { entity: *visible_entity, draw_function: opaque_draw_prepass, pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } } AlphaMode::Mask(_) => { if deferred { alpha_mask_phase_deferred .as_mut() .unwrap() .add(AlphaMask3dDeferred { entity: *visible_entity, draw_function: alpha_mask_draw_deferred, pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } else { alpha_mask_phase.add(AlphaMask3dPrepass { entity: *visible_entity, draw_function: alpha_mask_draw_prepass, pipeline_id, distance, batch_range: 0..1, dynamic_offset: None, }); } } AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => {} } } } } pub struct SetPrepassViewBindGroup; impl RenderCommand

for SetPrepassViewBindGroup { type Param = SRes; type ViewWorldQuery = ( Read, Option>, ); type ItemWorldQuery = (); #[inline] fn render<'w>( _item: &P, (view_uniform_offset, previous_view_projection_uniform_offset): ( &'_ ViewUniformOffset, Option<&'_ PreviousViewProjectionUniformOffset>, ), _entity: (), prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let prepass_view_bind_group = prepass_view_bind_group.into_inner(); if let Some(previous_view_projection_uniform_offset) = previous_view_projection_uniform_offset { pass.set_bind_group( I, prepass_view_bind_group.motion_vectors.as_ref().unwrap(), &[ view_uniform_offset.offset, previous_view_projection_uniform_offset.offset, ], ); } else { pass.set_bind_group( I, prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), &[view_uniform_offset.offset], ); } RenderCommandResult::Success } } pub type DrawPrepass = ( SetItemPipeline, SetPrepassViewBindGroup<0>, SetMaterialBindGroup, SetMeshBindGroup<2>, DrawMesh, ); #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] struct PrepassLightsViewFlush;