diff --git a/Cargo.toml b/Cargo.toml index a56514cc4b0e7..51cb064ffe6cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -611,6 +611,16 @@ description = "Illustrates bloom configuration using HDR and emissive materials" category = "3D Rendering" wasm = true +[[example]] +name = "deferred_rendering" +path = "examples/3d/deferred_rendering.rs" + +[package.metadata.example.deferred_rendering] +name = "Deferred Rendering" +description = "Renders meshes with both forward and deferred pipelines" +category = "3D Rendering" +wasm = true + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" diff --git a/assets/shaders/array_texture.wgsl b/assets/shaders/array_texture.wgsl index 828bb0689a150..7945baf3f8a98 100644 --- a/assets/shaders/array_texture.wgsl +++ b/assets/shaders/array_texture.wgsl @@ -1,6 +1,6 @@ #import bevy_pbr::mesh_vertex_output MeshVertexOutput #import bevy_pbr::mesh_view_bindings view -#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT +#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT, PbrInput, pbr_input_new #import bevy_core_pipeline::tonemapping tone_mapping #import bevy_pbr::pbr_functions as fns @@ -16,7 +16,7 @@ fn fragment( // Prepare a 'processed' StandardMaterial by sampling all textures to resolve // the material members - var pbr_input: fns::PbrInput = fns::pbr_input_new(); + var pbr_input: PbrInput = pbr_input_new(); pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, mesh.uv, layer); #ifdef VERTEX_COLORS diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 34d8c299c94c9..a64ed8f4c8bef 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -1,7 +1,7 @@ use crate::{ clear_color::{ClearColor, ClearColorConfig}, core_3d::{Camera3d, Opaque3d}, - prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass}, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; use bevy_ecs::{prelude::*, query::QueryItem}; @@ -34,6 +34,7 @@ impl ViewNode for MainOpaquePass3dNode { Option<&'static DepthPrepass>, Option<&'static NormalPrepass>, Option<&'static MotionVectorPrepass>, + Option<&'static DeferredPrepass>, Option<&'static SkyboxPipelineId>, Option<&'static SkyboxBindGroup>, &'static ViewUniformOffset, @@ -53,12 +54,24 @@ impl ViewNode for MainOpaquePass3dNode { depth_prepass, normal_prepass, motion_vector_prepass, + deferred_prepass, skybox_pipeline, skybox_bind_group, view_uniform_offset, ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { + let load = if deferred_prepass.is_none() { + match camera_3d.clear_color { + ClearColorConfig::Default => LoadOp::Clear(world.resource::().0.into()), + ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + ClearColorConfig::None => LoadOp::Load, + } + } else { + // If the deferred lighting pass has run, don't clear again in this pass. + LoadOp::Load + }; + // Run the opaque pass, sorted front-to-back // NOTE: Scoped to drop the mutable borrow of render_context #[cfg(feature = "trace")] @@ -69,16 +82,9 @@ impl ViewNode for MainOpaquePass3dNode { label: Some("main_opaque_pass_3d"), // NOTE: The opaque pass loads the color // buffer as well as writing to it. - color_attachments: &[Some(target.get_color_attachment(Operations { - load: match camera_3d.clear_color { - ClearColorConfig::Default => { - LoadOp::Clear(world.resource::().0.into()) - } - ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), - ClearColorConfig::None => LoadOp::Load, - }, - store: true, - }))], + color_attachments: &[Some( + target.get_color_attachment(Operations { load, store: true }), + )], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &depth.view, // NOTE: The opaque main pass loads the depth buffer and possibly overwrites it @@ -86,6 +92,7 @@ impl ViewNode for MainOpaquePass3dNode { load: if depth_prepass.is_some() || normal_prepass.is_some() || motion_vector_prepass.is_some() + || deferred_prepass.is_some() { // if any prepass runs, it will generate a depth buffer so we should use it, // even if only the normal_prepass is used. diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index e30b20b8d5e49..b0bee381cc275 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -10,6 +10,9 @@ pub mod graph { pub mod node { pub const MSAA_WRITEBACK: &str = "msaa_writeback"; pub const PREPASS: &str = "prepass"; + pub const DEFERRED_PREPASS: &str = "deferred_prepass"; + pub const COPY_DEFERRED_LIGHTING_ID: &str = "copy_deferred_lighting_id"; + pub const END_PREPASSES: &str = "end_prepasses"; pub const START_MAIN_PASS: &str = "start_main_pass"; pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass"; pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass"; @@ -24,13 +27,16 @@ pub mod graph { } pub const CORE_3D: &str = graph::NAME; +// PERF: vulkan docs recommend using 24 bit depth for better performance +pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; + use std::{cmp::Reverse, ops::Range}; pub use camera_3d::*; pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; -use bevy_app::{App, Plugin}; +use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::prelude::*; use bevy_render::{ camera::{Camera, ExtractedCamera}, @@ -50,12 +56,17 @@ use bevy_render::{ view::ViewDepthTexture, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::{nonmax::NonMaxU32, FloatOrd, HashMap}; +use bevy_utils::{nonmax::NonMaxU32, tracing::warn, FloatOrd, HashMap}; use crate::{ + deferred::{ + copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode, + AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, + DEFERRED_PREPASS_FORMAT, + }, prepass::{ - node::PrepassNode, AlphaMask3dPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, - Opaque3dPrepass, ViewPrepassTextures, DEPTH_PREPASS_FORMAT, MOTION_VECTOR_PREPASS_FORMAT, + node::PrepassNode, AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, + NormalPrepass, Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, }, skybox::SkyboxPlugin, @@ -69,7 +80,8 @@ impl Plugin for Core3dPlugin { fn build(&self, app: &mut App) { app.register_type::() .register_type::() - .add_plugins((SkyboxPlugin, ExtractComponentPlugin::::default())); + .add_plugins((SkyboxPlugin, ExtractComponentPlugin::::default())) + .add_systems(PostUpdate, check_msaa); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, @@ -82,6 +94,8 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() + .init_resource::>() + .init_resource::>() .add_systems(ExtractSchedule, extract_core_3d_camera_phases) .add_systems(ExtractSchedule, extract_camera_prepass_phase) .add_systems( @@ -92,6 +106,8 @@ impl Plugin for Core3dPlugin { sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources), prepare_prepass_textures.in_set(RenderSet::PrepareResources), ), @@ -101,6 +117,15 @@ impl Plugin for Core3dPlugin { render_app .add_render_sub_graph(CORE_3D) .add_render_graph_node::>(CORE_3D, PREPASS) + .add_render_graph_node::>( + CORE_3D, + DEFERRED_PREPASS, + ) + .add_render_graph_node::>( + CORE_3D, + COPY_DEFERRED_LIGHTING_ID, + ) + .add_render_graph_node::(CORE_3D, END_PREPASSES) .add_render_graph_node::(CORE_3D, START_MAIN_PASS) .add_render_graph_node::>( CORE_3D, @@ -118,6 +143,9 @@ impl Plugin for Core3dPlugin { CORE_3D, &[ PREPASS, + DEFERRED_PREPASS, + COPY_DEFERRED_LIGHTING_ID, + END_PREPASSES, START_MAIN_PASS, MAIN_OPAQUE_PASS, MAIN_TRANSPARENT_PASS, @@ -341,12 +369,14 @@ pub fn extract_camera_prepass_phase( Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, + Option<&DeferredPrepass>, ), With, >, >, ) { - for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass) in cameras_3d.iter() + for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in + cameras_3d.iter() { if camera.is_active { let mut entity = commands.get_or_spawn(entity); @@ -361,6 +391,13 @@ pub fn extract_camera_prepass_phase( )); } + if deferred_prepass.is_some() { + entity.insert(( + RenderPhase::::default(), + RenderPhase::::default(), + )); + } + if depth_prepass.is_some() { entity.insert(DepthPrepass); } @@ -370,6 +407,9 @@ pub fn extract_camera_prepass_phase( if motion_vector_prepass.is_some() { entity.insert(MotionVectorPrepass); } + if deferred_prepass.is_some() { + entity.insert(DeferredPrepass); + } } } } @@ -428,8 +468,7 @@ pub fn prepare_core_3d_depth_textures( mip_level_count: 1, sample_count: msaa.samples(), dimension: TextureDimension::D2, - // PERF: vulkan docs recommend using 24 bit depth for better performance - format: TextureFormat::Depth32Float, + format: CORE_3D_DEPTH_FORMAT, usage, view_formats: &[], }; @@ -445,6 +484,22 @@ pub fn prepare_core_3d_depth_textures( } } +// Disable MSAA and warn if using deferred rendering +pub fn check_msaa( + mut msaa: ResMut, + deferred_views: Query, With)>, +) { + if !deferred_views.is_empty() { + match *msaa { + Msaa::Off => (), + _ => { + warn!("MSAA is incompatible with deferred rendering and has been disabled."); + *msaa = Msaa::Off; + } + }; + } +} + // Prepares the textures used by the prepass pub fn prepare_prepass_textures( mut commands: Commands, @@ -458,6 +513,7 @@ pub fn prepare_prepass_textures( Option<&DepthPrepass>, Option<&NormalPrepass>, Option<&MotionVectorPrepass>, + Option<&DeferredPrepass>, ), ( With>, @@ -467,8 +523,12 @@ pub fn prepare_prepass_textures( ) { let mut depth_textures = HashMap::default(); let mut normal_textures = HashMap::default(); + let mut deferred_textures = HashMap::default(); + let mut deferred_lighting_id_textures = HashMap::default(); let mut motion_vectors_textures = HashMap::default(); - for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass) in &views_3d { + for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in + &views_3d + { let Some(physical_target_size) = camera.physical_target_size else { continue; }; @@ -489,7 +549,7 @@ pub fn prepare_prepass_textures( mip_level_count: 1, sample_count: msaa.samples(), dimension: TextureDimension::D2, - format: DEPTH_PREPASS_FORMAT, + format: CORE_3D_DEPTH_FORMAT, usage: TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, @@ -544,10 +604,56 @@ pub fn prepare_prepass_textures( .clone() }); + let cached_deferred_texture = deferred_prepass.is_some().then(|| { + deferred_textures + .entry(camera.target.clone()) + .or_insert_with(|| { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("prepass_deferred_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: DEFERRED_PREPASS_FORMAT, + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }) + .clone() + }); + + let deferred_lighting_pass_id_texture = deferred_prepass.is_some().then(|| { + deferred_lighting_id_textures + .entry(camera.target.clone()) + .or_insert_with(|| { + texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("deferred_lighting_pass_id_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: DEFERRED_LIGHTING_PASS_ID_FORMAT, + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + ) + }) + .clone() + }); + commands.entity(entity).insert(ViewPrepassTextures { depth: cached_depth_texture, normal: cached_normals_texture, motion_vectors: cached_motion_vectors_texture, + deferred: cached_deferred_texture, + deferred_lighting_pass_id: deferred_lighting_pass_id_texture, size, }); } diff --git a/crates/bevy_core_pipeline/src/deferred/copy_deferred_lighting_id.wgsl b/crates/bevy_core_pipeline/src/deferred/copy_deferred_lighting_id.wgsl new file mode 100644 index 0000000000000..2fc7b1d86748d --- /dev/null +++ b/crates/bevy_core_pipeline/src/deferred/copy_deferred_lighting_id.wgsl @@ -0,0 +1,19 @@ +#import bevy_pbr::utils +#import bevy_core_pipeline::fullscreen_vertex_shader FullscreenVertexOutput + +@group(0) @binding(0) +var material_id_texture: texture_2d; + +struct FragmentOutput { + @builtin(frag_depth) frag_depth: f32, + +} + +@fragment +fn fragment(in: FullscreenVertexOutput) -> FragmentOutput { + var out: FragmentOutput; + // Depth is stored as unorm, so we are dividing the u8 by 255.0 here. + out.frag_depth = f32(textureLoad(material_id_texture, vec2(in.position.xy), 0).x) / 255.0; + return out; +} + diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs new file mode 100644 index 0000000000000..cb68e472035cb --- /dev/null +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -0,0 +1,224 @@ +use crate::{ + fullscreen_vertex_shader::fullscreen_shader_vertex_state, + prepass::{DeferredPrepass, ViewPrepassTextures}, +}; +use bevy_app::prelude::*; +use bevy_asset::{load_internal_asset, Handle}; +use bevy_ecs::prelude::*; +use bevy_math::UVec2; +use bevy_render::{ + camera::ExtractedCamera, + render_resource::*, + renderer::RenderDevice, + texture::{CachedTexture, TextureCache}, + view::ViewTarget, + Render, RenderApp, RenderSet, +}; + +use bevy_ecs::query::QueryItem; +use bevy_render::{ + render_graph::{NodeRunError, RenderGraphContext, ViewNode}, + render_resource::{ + BindGroupDescriptor, BindGroupEntry, BindingResource, Operations, PipelineCache, + RenderPassDescriptor, + }, + renderer::RenderContext, +}; + +use super::DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT; + +pub const COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE: Handle = + Handle::weak_from_u128(5230948520734987); +pub struct CopyDeferredLightingIdPlugin; + +impl Plugin for CopyDeferredLightingIdPlugin { + fn build(&self, app: &mut App) { + load_internal_asset!( + app, + COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE, + "copy_deferred_lighting_id.wgsl", + Shader::from_wgsl + ); + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + render_app.add_systems( + Render, + (prepare_deferred_lighting_id_textures.in_set(RenderSet::PrepareResources),), + ); + } + + fn finish(&self, app: &mut App) { + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.init_resource::(); + } +} + +#[derive(Default)] +pub struct CopyDeferredLightingIdNode; +impl CopyDeferredLightingIdNode { + pub const NAME: &str = "copy_deferred_lighting_id"; +} + +impl ViewNode for CopyDeferredLightingIdNode { + type ViewQuery = ( + &'static ViewTarget, + &'static ViewPrepassTextures, + &'static DeferredLightingIdDepthTexture, + ); + + fn run( + &self, + _graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + (_view_target, view_prepass_textures, deferred_lighting_id_depth_texture): QueryItem< + Self::ViewQuery, + >, + world: &World, + ) -> Result<(), NodeRunError> { + let copy_deferred_lighting_id_pipeline = world.resource::(); + + let pipeline_cache = world.resource::(); + + let Some(pipeline) = + pipeline_cache.get_render_pipeline(copy_deferred_lighting_id_pipeline.pipeline_id) + else { + return Ok(()); + }; + let Some(deferred_lighting_pass_id_texture) = + &view_prepass_textures.deferred_lighting_pass_id + else { + return Ok(()); + }; + + let bind_group = render_context + .render_device() + .create_bind_group(&BindGroupDescriptor { + label: Some("copy_deferred_lighting_id_bind_group"), + layout: ©_deferred_lighting_id_pipeline.layout, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView( + &deferred_lighting_pass_id_texture.default_view, + ), + }], + }); + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("copy_deferred_lighting_id_pass"), + color_attachments: &[], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &deferred_lighting_id_depth_texture.texture.default_view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: None, + }), + }); + + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); + + Ok(()) + } +} + +#[derive(Resource)] +struct CopyDeferredLightingIdPipeline { + layout: BindGroupLayout, + pipeline_id: CachedRenderPipelineId, +} + +impl FromWorld for CopyDeferredLightingIdPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("copy_deferred_lighting_id_bind_group_layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Uint, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }); + + let pipeline_id = + world + .resource_mut::() + .queue_render_pipeline(RenderPipelineDescriptor { + label: Some("copy_deferred_lighting_id_pipeline".into()), + layout: vec![layout.clone()], + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: COPY_DEFERRED_LIGHTING_ID_SHADER_HANDLE, + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![], + }), + primitive: PrimitiveState::default(), + depth_stencil: Some(DepthStencilState { + format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: CompareFunction::Always, + stencil: StencilState::default(), + bias: DepthBiasState::default(), + }), + multisample: MultisampleState::default(), + push_constant_ranges: vec![], + }); + + Self { + layout, + pipeline_id, + } + } +} + +#[derive(Component)] +pub struct DeferredLightingIdDepthTexture { + pub texture: CachedTexture, +} + +fn prepare_deferred_lighting_id_textures( + mut commands: Commands, + mut texture_cache: ResMut, + render_device: Res, + views: Query<(Entity, &ExtractedCamera), With>, +) { + for (entity, camera) in &views { + if let Some(UVec2 { + x: width, + y: height, + }) = camera.physical_target_size + { + let texture_descriptor = TextureDescriptor { + label: Some("deferred_lighting_id_depth_texture_a"), + size: Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC, + view_formats: &[], + }; + let texture = texture_cache.get(&render_device, texture_descriptor); + commands + .entity(entity) + .insert(DeferredLightingIdDepthTexture { texture }); + } + } +} diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs new file mode 100644 index 0000000000000..a79e62b71998a --- /dev/null +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -0,0 +1,149 @@ +pub mod copy_lighting_id; +pub mod node; + +use std::{cmp::Reverse, ops::Range}; + +use bevy_ecs::prelude::*; +use bevy_render::{ + render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, + render_resource::{CachedRenderPipelineId, TextureFormat}, +}; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; + +pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint; +pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint; +pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; + +/// Opaque phase of the 3D Deferred pass. +/// +/// Sorted front-to-back by the z-distance in front of the camera. +/// +/// Used to render all 3D meshes with materials that have no transparency. +pub struct Opaque3dDeferred { + pub distance: f32, + pub entity: Entity, + pub pipeline_id: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, +} + +impl PhaseItem for Opaque3dDeferred { + // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. + type SortKey = Reverse; + + #[inline] + fn entity(&self) -> Entity { + self.entity + } + + #[inline] + fn sort_key(&self) -> Self::SortKey { + Reverse(FloatOrd(self.distance)) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } + + #[inline] + fn sort(items: &mut [Self]) { + // Key negated to match reversed SortKey ordering + radsort::sort_by_key(items, |item| -item.distance); + } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } +} + +impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline_id + } +} + +/// Alpha mask phase of the 3D Deferred pass. +/// +/// Sorted front-to-back by the z-distance in front of the camera. +/// +/// Used to render all meshes with a material with an alpha mask. +pub struct AlphaMask3dDeferred { + pub distance: f32, + pub entity: Entity, + pub pipeline_id: CachedRenderPipelineId, + pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, +} + +impl PhaseItem for AlphaMask3dDeferred { + // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. + type SortKey = Reverse; + + #[inline] + fn entity(&self) -> Entity { + self.entity + } + + #[inline] + fn sort_key(&self) -> Self::SortKey { + Reverse(FloatOrd(self.distance)) + } + + #[inline] + fn draw_function(&self) -> DrawFunctionId { + self.draw_function + } + + #[inline] + fn sort(items: &mut [Self]) { + // Key negated to match reversed SortKey ordering + radsort::sort_by_key(items, |item| -item.distance); + } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } +} + +impl CachedRenderPipelinePhaseItem for AlphaMask3dDeferred { + #[inline] + fn cached_pipeline(&self) -> CachedRenderPipelineId { + self.pipeline_id + } +} diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs new file mode 100644 index 0000000000000..f598e8393ea70 --- /dev/null +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -0,0 +1,205 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::query::QueryItem; +use bevy_render::render_graph::ViewNode; + +use bevy_render::{ + camera::ExtractedCamera, + prelude::Color, + render_graph::{NodeRunError, RenderGraphContext}, + render_phase::RenderPhase, + render_resource::{ + LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, + RenderPassDescriptor, + }, + renderer::RenderContext, + view::ViewDepthTexture, +}; +#[cfg(feature = "trace")] +use bevy_utils::tracing::info_span; + +use crate::core_3d::{Camera3d, Camera3dDepthLoadOp}; +use crate::prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass, ViewPrepassTextures}; + +use super::{AlphaMask3dDeferred, Opaque3dDeferred}; + +/// Render node used by the prepass. +/// +/// By default, inserted before the main pass in the render graph. +#[derive(Default)] +pub struct DeferredGBufferPrepassNode; + +impl ViewNode for DeferredGBufferPrepassNode { + type ViewQuery = ( + &'static ExtractedCamera, + &'static RenderPhase, + &'static RenderPhase, + &'static ViewDepthTexture, + &'static ViewPrepassTextures, + &'static Camera3d, + Option<&'static DepthPrepass>, + Option<&'static NormalPrepass>, + Option<&'static MotionVectorPrepass>, + ); + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + ( + camera, + opaque_deferred_phase, + alpha_mask_deferred_phase, + view_depth_texture, + view_prepass_textures, + camera_3d, + depth_prepass, + normal_prepass, + motion_vector_prepass, + ): QueryItem, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.view_entity(); + + let mut color_attachments = vec![]; + color_attachments.push( + view_prepass_textures + .normal + .as_ref() + .map(|view_normals_texture| RenderPassColorAttachment { + view: &view_normals_texture.default_view, + resolve_target: None, + ops: Operations { + load: if normal_prepass.is_some() { + // Load if the normal_prepass has already run. + // The prepass will have already cleared this for the current frame. + LoadOp::Load + } else { + LoadOp::Clear(Color::BLACK.into()) + }, + store: true, + }, + }), + ); + color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map( + |view_motion_vectors_texture| RenderPassColorAttachment { + view: &view_motion_vectors_texture.default_view, + resolve_target: None, + ops: Operations { + load: if motion_vector_prepass.is_some() { + // Load if the motion_vector_prepass has already run. + // The prepass will have already cleared this for the current frame. + LoadOp::Load + } else { + LoadOp::Clear(Color::BLACK.into()) + }, + store: true, + }, + }, + )); + + // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: + // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. + // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT. + // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9 + // For webgl2 we fallback to manually clearing + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + if let Some(deferred_texture) = &view_prepass_textures.deferred { + render_context.command_encoder().clear_texture( + &deferred_texture.texture, + &bevy_render::render_resource::ImageSubresourceRange::default(), + ); + } + + color_attachments.push( + view_prepass_textures + .deferred + .as_ref() + .map(|deferred_texture| RenderPassColorAttachment { + view: &deferred_texture.default_view, + resolve_target: None, + ops: Operations { + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + load: LoadOp::Load, + #[cfg(not(all(feature = "webgl", target_arch = "wasm32")))] + load: LoadOp::Clear(Default::default()), + store: true, + }, + }), + ); + + color_attachments.push( + view_prepass_textures + .deferred_lighting_pass_id + .as_ref() + .map(|deferred_lighting_pass_id| RenderPassColorAttachment { + view: &deferred_lighting_pass_id.default_view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Default::default()), + store: true, + }, + }), + ); + + if color_attachments.iter().all(Option::is_none) { + // All attachments are none: clear the attachment list so that no fragment shader is required. + color_attachments.clear(); + } + + { + // Set up the pass descriptor with the depth attachment and optional color attachments. + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("deferred"), + color_attachments: &color_attachments, + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &view_depth_texture.view, + depth_ops: Some(Operations { + load: if depth_prepass.is_some() + || normal_prepass.is_some() + || motion_vector_prepass.is_some() + { + // If any prepass runs, it will generate a depth buffer so we should use it. + Camera3dDepthLoadOp::Load + } else { + // NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections. + camera_3d.depth_load_op.clone() + } + .into(), + store: true, + }), + stencil_ops: None, + }), + }); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + // Always run deferred pass to ensure the deferred gbuffer and deferred_lighting_pass_id are cleared. + { + // Run the prepass, sorted front-to-back. + #[cfg(feature = "trace")] + let _opaque_prepass_span = info_span!("opaque_deferred").entered(); + opaque_deferred_phase.render(&mut render_pass, world, view_entity); + } + + if !alpha_mask_deferred_phase.items.is_empty() { + // Run the deferred, sorted front-to-back. + #[cfg(feature = "trace")] + let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered(); + alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity); + } + } + + if let Some(prepass_depth_texture) = &view_prepass_textures.depth { + // Copy depth buffer to texture. + render_context.command_encoder().copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.as_image_copy(), + view_prepass_textures.size, + ); + } + + Ok(()) + } +} diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 51e5ae36278e9..3c64535d4fe68 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -6,6 +6,7 @@ pub mod clear_color; pub mod contrast_adaptive_sharpening; pub mod core_2d; pub mod core_3d; +pub mod deferred; pub mod fullscreen_vertex_shader; pub mod fxaa; pub mod msaa_writeback; @@ -40,6 +41,7 @@ use crate::{ contrast_adaptive_sharpening::CASPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, + deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, fxaa::FxaaPlugin, msaa_writeback::MsaaWritebackPlugin, @@ -72,6 +74,7 @@ impl Plugin for CorePipelinePlugin { ExtractResourcePlugin::::default(), Core2dPlugin, Core3dPlugin, + CopyDeferredLightingIdPlugin, BlitPlugin, MsaaWritebackPlugin, TonemappingPlugin, diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index f408a168e7c7c..ba4de1952c82e 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -38,7 +38,6 @@ use bevy_render::{ }; use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; -pub const DEPTH_PREPASS_FORMAT: TextureFormat = TextureFormat::Depth32Float; pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float; @@ -55,6 +54,10 @@ pub struct NormalPrepass; #[derive(Component, Default, Reflect)] pub struct MotionVectorPrepass; +/// If added to a [`crate::prelude::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes. +#[derive(Component, Default, Reflect)] +pub struct DeferredPrepass; + /// Textures that are written to by the prepass. /// /// This component will only be present if any of the relevant prepass components are also present. @@ -69,6 +72,12 @@ pub struct ViewPrepassTextures { /// The motion vectors texture generated by the prepass. /// Exists only if [`MotionVectorPrepass`] is added to the `ViewTarget` pub motion_vectors: Option, + /// The deferred gbuffer generated by the deferred pass. + /// Exists only if [`DeferredPrepass`] is added to the `ViewTarget` + pub deferred: Option, + /// A texture that specifies the deferred lighting pass id for a material. + /// Exists only if [`DeferredPrepass`] is added to the `ViewTarget` + pub deferred_lighting_pass_id: Option, /// The size of the textures. pub size: Extent3d, } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index bdf997601372e..5b64ad7bf4284 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -16,7 +16,7 @@ use bevy_render::{ #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use super::{AlphaMask3dPrepass, Opaque3dPrepass, ViewPrepassTextures}; +use super::{AlphaMask3dPrepass, DeferredPrepass, Opaque3dPrepass, ViewPrepassTextures}; /// Render node used by the prepass. /// @@ -31,6 +31,7 @@ impl ViewNode for PrepassNode { &'static RenderPhase, &'static ViewDepthTexture, &'static ViewPrepassTextures, + Option<&'static DeferredPrepass>, ); fn run( @@ -43,13 +44,13 @@ impl ViewNode for PrepassNode { alpha_mask_prepass_phase, view_depth_texture, view_prepass_textures, + deferred_prepass, ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.view_entity(); - let mut color_attachments = vec![]; - color_attachments.push( + let mut color_attachments = vec![ view_prepass_textures .normal .as_ref() @@ -61,20 +62,25 @@ impl ViewNode for PrepassNode { store: true, }, }), - ); - color_attachments.push(view_prepass_textures.motion_vectors.as_ref().map( - |view_motion_vectors_texture| RenderPassColorAttachment { - view: &view_motion_vectors_texture.default_view, - resolve_target: None, - ops: Operations { - // Red and Green channels are X and Y components of the motion vectors - // Blue channel doesn't matter, but set to 0.0 for possible faster clear - // https://gpuopen.com/performance/#clears - load: LoadOp::Clear(Color::rgb_linear(0.0, 0.0, 0.0).into()), - store: true, - }, - }, - )); + view_prepass_textures + .motion_vectors + .as_ref() + .map(|view_motion_vectors_texture| RenderPassColorAttachment { + view: &view_motion_vectors_texture.default_view, + resolve_target: None, + ops: Operations { + // Red and Green channels are X and Y components of the motion vectors + // Blue channel doesn't matter, but set to 0.0 for possible faster clear + // https://gpuopen.com/performance/#clears + load: LoadOp::Clear(Color::BLACK.into()), + store: true, + }, + }), + // Use None in place of Deferred attachments + None, + None, + ]; + if color_attachments.iter().all(Option::is_none) { // all attachments are none: clear the attachment list so that no fragment shader is required color_attachments.clear(); @@ -113,16 +119,17 @@ impl ViewNode for PrepassNode { alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity); } } - - if let Some(prepass_depth_texture) = &view_prepass_textures.depth { - // Copy depth buffer to texture - render_context.command_encoder().copy_texture_to_texture( - view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.as_image_copy(), - view_prepass_textures.size, - ); + if deferred_prepass.is_none() { + // Copy if deferred isn't going to + if let Some(prepass_depth_texture) = &view_prepass_textures.depth { + // Copy depth buffer to texture + render_context.command_encoder().copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.as_image_copy(), + view_prepass_textures.size, + ); + } } - Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index bd2035d6b1b35..efc133651930e 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -11,7 +11,7 @@ use bevy_render::{ render_asset::RenderAssets, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, + BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, CachedRenderPipelineId, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, FragmentState, MultisampleState, PipelineCache, PrimitiveState, RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages, ShaderType, @@ -24,6 +24,8 @@ use bevy_render::{ Render, RenderApp, RenderSet, }; +use crate::core_3d::CORE_3D_DEPTH_FORMAT; + const SKYBOX_SHADER_HANDLE: Handle = Handle::weak_from_u128(55594763423201); pub struct SkyboxPlugin; @@ -121,6 +123,7 @@ impl SkyboxPipeline { struct SkyboxPipelineKey { hdr: bool, samples: u32, + depth_format: TextureFormat, } impl SpecializedRenderPipeline for SkyboxPipeline { @@ -139,7 +142,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline { }, primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, + format: key.depth_format, depth_write_enabled: false, depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { @@ -169,7 +172,8 @@ impl SpecializedRenderPipeline for SkyboxPipeline { } else { TextureFormat::bevy_default() }, - blend: Some(BlendState::REPLACE), + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. + blend: None, write_mask: ColorWrites::ALL, })], }), @@ -195,6 +199,7 @@ fn prepare_skybox_pipelines( SkyboxPipelineKey { hdr: view.hdr, samples: msaa.samples(), + depth_format: CORE_3D_DEPTH_FORMAT, }, ); diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index c9a465f595549..e63125a0fbb2f 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_asset::Handle; -use bevy_core_pipeline::core_3d::Transparent3d; +use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}; use bevy_ecs::{ prelude::Entity, @@ -121,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { layout, primitive: PrimitiveState::default(), depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, + format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, depth_compare: CompareFunction::Greater, stencil: StencilState::default(), diff --git a/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl b/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl new file mode 100644 index 0000000000000..a8b046e5b4159 --- /dev/null +++ b/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl @@ -0,0 +1,95 @@ +#import bevy_pbr::prepass_utils +#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT +#import bevy_pbr::pbr_functions as pbr_functions +#import bevy_pbr::pbr_deferred_types as deferred_types +#import bevy_pbr::pbr_deferred_functions pbr_input_from_deferred_gbuffer, unpack_unorm3x4_plus_unorm_20_ +#import bevy_pbr::mesh_view_types FOG_MODE_OFF + +#import bevy_pbr::mesh_view_bindings deferred_prepass_texture, fog, view, screen_space_ambient_occlusion_texture +#import bevy_core_pipeline::tonemapping screen_space_dither, powsafe, tone_mapping + +#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION +#import bevy_pbr::gtao_utils gtao_multibounce +#endif + +struct FullscreenVertexOutput { + @builtin(position) + position: vec4, + @location(0) + uv: vec2, +}; + +struct PbrDeferredLightingDepthId { + depth_id: u32, // limited to u8 +#ifdef SIXTEEN_BYTE_ALIGNMENT + // WebGL2 structs must be 16 byte aligned. + _webgl2_padding_0: f32, + _webgl2_padding_1: f32, + _webgl2_padding_2: f32, +#endif +} +@group(1) @binding(0) +var depth_id: PbrDeferredLightingDepthId; + +@vertex +fn vertex(@builtin(vertex_index) vertex_index: u32) -> FullscreenVertexOutput { + // See the full screen vertex shader for explanation above for how this works. + let uv = vec2(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0; + // Depth is stored as unorm, so we are dividing the u8 depth_id by 255.0 here. + let clip_position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), f32(depth_id.depth_id) / 255.0, 1.0); + + return FullscreenVertexOutput(clip_position, uv); +} + +@fragment +fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { + var frag_coord = vec4(in.position.xy, 0.0, 0.0); + + let deferred_data = textureLoad(deferred_prepass_texture, vec2(frag_coord.xy), 0); + +#ifdef WEBGL2 + frag_coord.z = deferred_types::unpack_unorm3x4_plus_unorm_20_(deferred_data.b).w; +#else + frag_coord.z = bevy_pbr::prepass_utils::prepass_depth(in.position, 0u); +#endif + + var pbr_input = pbr_input_from_deferred_gbuffer(frag_coord, deferred_data); + var output_color = vec4(0.0); + +#ifdef SCREEN_SPACE_AMBIENT_OCCLUSION + let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2(in.position.xy), 0i).r; + let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb); + pbr_input.occlusion = min(pbr_input.occlusion, ssao_multibounce); +#endif // SCREEN_SPACE_AMBIENT_OCCLUSION + + // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit + if ((pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { + output_color = pbr_functions::pbr(pbr_input); + } else { + output_color = pbr_input.material.base_color; + } + + // fog + if (fog.mode != FOG_MODE_OFF && (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) { + output_color = pbr_functions::apply_fog(fog, output_color, pbr_input.world_position.xyz, view.world_position.xyz); + } + +#ifdef TONEMAP_IN_SHADER + output_color = tone_mapping(output_color, view.color_grading); +#ifdef DEBAND_DITHER + var output_rgb = output_color.rgb; + output_rgb = powsafe(output_rgb, 1.0 / 2.2); + output_rgb = output_rgb + screen_space_dither(frag_coord.xy); + // This conversion back to linear space is required because our output texture format is + // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. + output_rgb = powsafe(output_rgb, 2.2); + output_color = vec4(output_rgb, output_color.a); +#endif +#endif +#ifdef PREMULTIPLY_ALPHA + output_color = pbr_functions::premultiply_alpha(material.flags, output_color); +#endif + + return output_color; +} + diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs new file mode 100644 index 0000000000000..564c973eef460 --- /dev/null +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -0,0 +1,473 @@ +use crate::{MeshPipeline, MeshViewBindGroup, ScreenSpaceAmbientOcclusionSettings}; +use bevy_app::prelude::*; +use bevy_asset::{load_internal_asset, Handle}; +use bevy_core_pipeline::{ + clear_color::ClearColorConfig, + core_3d, + deferred::{ + copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, + }, + prelude::{Camera3d, ClearColor}, + prepass::DeferredPrepass, + tonemapping::{DebandDither, Tonemapping}, +}; +use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_render::{ + extract_component::{ + ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, + }, + render_asset::RenderAssets, + render_graph::{NodeRunError, RenderGraphContext, ViewNode, ViewNodeRunner}, + render_resource::{self, Operations, PipelineCache, RenderPassDescriptor}, + renderer::{RenderContext, RenderDevice}, + texture::Image, + view::{ViewTarget, ViewUniformOffset}, + Render, RenderSet, +}; + +use bevy_render::{ + render_graph::RenderGraphApp, render_resource::*, texture::BevyDefault, view::ExtractedView, + RenderApp, +}; + +use crate::{ + EnvironmentMapLight, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, + ViewLightsUniformOffset, +}; + +pub struct DeferredPbrLightingPlugin; + +pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle = + Handle::weak_from_u128(2708011359337029741); + +pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1; + +/// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass. +/// Will be automatically added to entities with the [`DeferredPrepass`] component that don't already have a [`PbrDeferredLightingDepthId`]. +#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)] +pub struct PbrDeferredLightingDepthId { + depth_id: u32, + + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_0: f32, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_1: f32, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_2: f32, +} + +impl PbrDeferredLightingDepthId { + pub fn new(value: u8) -> PbrDeferredLightingDepthId { + PbrDeferredLightingDepthId { + depth_id: value as u32, + + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_0: 0.0, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_1: 0.0, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_2: 0.0, + } + } + + pub fn set(&mut self, value: u8) { + self.depth_id = value as u32; + } + + pub fn get(&self) -> u8 { + self.depth_id as u8 + } +} + +impl Default for PbrDeferredLightingDepthId { + fn default() -> Self { + PbrDeferredLightingDepthId { + depth_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID as u32, + + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_0: 0.0, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_1: 0.0, + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + _webgl2_padding_2: 0.0, + } + } +} + +impl Plugin for DeferredPbrLightingPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + ExtractComponentPlugin::::default(), + UniformComponentPlugin::::default(), + )) + .add_systems(PostUpdate, insert_deferred_lighting_pass_id_component); + + load_internal_asset!( + app, + DEFERRED_LIGHTING_SHADER_HANDLE, + "deferred_lighting.wgsl", + Shader::from_wgsl + ); + + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .init_resource::>() + .add_systems( + Render, + (prepare_deferred_lighting_pipelines.in_set(RenderSet::Prepare),), + ) + .add_render_graph_node::>( + core_3d::graph::NAME, + DEFERRED_LIGHTING_PASS, + ) + .add_render_graph_edges( + core_3d::graph::NAME, + &[ + core_3d::graph::node::START_MAIN_PASS, + DEFERRED_LIGHTING_PASS, + core_3d::graph::node::MAIN_OPAQUE_PASS, + ], + ); + } + + fn finish(&self, app: &mut App) { + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.init_resource::(); + } +} + +pub const DEFERRED_LIGHTING_PASS: &str = "deferred_opaque_pbr_lighting_pass_3d"; +#[derive(Default)] +pub struct DeferredOpaquePass3dPbrLightingNode; + +impl ViewNode for DeferredOpaquePass3dPbrLightingNode { + type ViewQuery = ( + &'static ViewUniformOffset, + &'static ViewLightsUniformOffset, + &'static ViewFogUniformOffset, + &'static MeshViewBindGroup, + &'static ViewTarget, + &'static DeferredLightingIdDepthTexture, + &'static Camera3d, + &'static DeferredLightingPipeline, + ); + + fn run( + &self, + _graph_context: &mut RenderGraphContext, + render_context: &mut RenderContext, + ( + view_uniform_offset, + view_lights_offset, + view_fog_offset, + mesh_view_bind_group, + target, + deferred_lighting_id_depth_texture, + camera_3d, + deferred_lighting_pipeline, + ): QueryItem, + world: &World, + ) -> Result<(), NodeRunError> { + let pipeline_cache = world.resource::(); + let deferred_lighting_layout = world.resource::(); + + let Some(pipeline) = + pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id) + else { + return Ok(()); + }; + + let deferred_lighting_pass_id = + world.resource::>(); + let Some(deferred_lighting_pass_id_binding) = + deferred_lighting_pass_id.uniforms().binding() + else { + return Ok(()); + }; + + let bind_group_1 = render_context + .render_device() + .create_bind_group(&BindGroupDescriptor { + label: Some("deferred_lighting_layout_group_1"), + layout: &deferred_lighting_layout.bind_group_layout_1, + entries: &[BindGroupEntry { + binding: 0, + resource: deferred_lighting_pass_id_binding.clone(), + }], + }); + + let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("deferred_lighting_pass"), + color_attachments: &[Some(target.get_color_attachment(Operations { + load: match camera_3d.clear_color { + ClearColorConfig::Default => { + LoadOp::Clear(world.resource::().0.into()) + } + ClearColorConfig::Custom(color) => LoadOp::Clear(color.into()), + ClearColorConfig::None => LoadOp::Load, + }, + store: true, + }))], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &deferred_lighting_id_depth_texture.texture.default_view, + depth_ops: Some(Operations { + load: LoadOp::Load, + store: false, + }), + stencil_ops: None, + }), + }); + + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &mesh_view_bind_group.value, + &[ + view_uniform_offset.offset, + view_lights_offset.offset, + view_fog_offset.offset, + ], + ); + render_pass.set_bind_group(1, &bind_group_1, &[]); + render_pass.draw(0..3, 0..1); + + Ok(()) + } +} + +#[derive(Resource)] +pub struct DeferredLightingLayout { + bind_group_layout_0: BindGroupLayout, + bind_group_layout_1: BindGroupLayout, +} + +#[derive(Component)] +pub struct DeferredLightingPipeline { + pub pipeline_id: CachedRenderPipelineId, +} + +impl SpecializedRenderPipeline for DeferredLightingLayout { + type Key = MeshPipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut shader_defs = Vec::new(); + + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + shader_defs.push("WEBGL2".into()); + + if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { + shader_defs.push("TONEMAP_IN_SHADER".into()); + + let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); + + if method == MeshPipelineKey::TONEMAP_METHOD_NONE { + shader_defs.push("TONEMAP_METHOD_NONE".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD { + shader_defs.push("TONEMAP_METHOD_REINHARD".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE { + shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED { + shader_defs.push("TONEMAP_METHOD_ACES_FITTED ".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX { + shader_defs.push("TONEMAP_METHOD_AGX".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM { + shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC { + shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); + } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE { + shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); + } + + // Debanding is tied to tonemapping in the shader, cannot run without it. + if key.contains(MeshPipelineKey::DEBAND_DITHER) { + shader_defs.push("DEBAND_DITHER".into()); + } + } + + if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { + shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); + } + + if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { + shader_defs.push("ENVIRONMENT_MAP".into()); + } + + let shadow_filter_method = + key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS); + if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 { + shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into()); + } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13 { + shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into()); + } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 { + shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into()); + } + + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); + + RenderPipelineDescriptor { + label: Some("deferred_lighting_pipeline".into()), + layout: vec![ + self.bind_group_layout_0.clone(), + self.bind_group_layout_1.clone(), + ], + vertex: VertexState { + shader: DEFERRED_LIGHTING_SHADER_HANDLE, + shader_defs: shader_defs.clone(), + entry_point: "vertex".into(), + buffers: Vec::new(), + }, + fragment: Some(FragmentState { + shader: DEFERRED_LIGHTING_SHADER_HANDLE, + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: if key.contains(MeshPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: Some(DepthStencilState { + format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, + depth_write_enabled: false, + depth_compare: CompareFunction::Equal, + 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::default(), + push_constant_ranges: vec![], + } + } +} + +impl FromWorld for DeferredLightingLayout { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("deferred_lighting_layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: render_resource::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(PbrDeferredLightingDepthId::min_size()), + }, + count: None, + }], + }); + Self { + bind_group_layout_0: world.resource::().view_layout.clone(), + bind_group_layout_1: layout, + } + } +} + +pub fn insert_deferred_lighting_pass_id_component( + mut commands: Commands, + views: Query, Without)>, +) { + for entity in views.iter() { + commands + .entity(entity) + .insert(PbrDeferredLightingDepthId::default()); + } +} + +pub fn prepare_deferred_lighting_pipelines( + mut commands: Commands, + pipeline_cache: Res, + mut pipelines: ResMut>, + deferred_lighting_layout: Res, + views: Query< + ( + Entity, + &ExtractedView, + Option<&Tonemapping>, + Option<&DebandDither>, + Option<&EnvironmentMapLight>, + Option<&ShadowFilteringMethod>, + Option<&ScreenSpaceAmbientOcclusionSettings>, + ), + With, + >, + images: Res>, +) { + for (entity, view, tonemapping, dither, environment_map, shadow_filter_method, ssao) in &views { + let mut view_key = MeshPipelineKey::from_hdr(view.hdr); + + if !view.hdr { + if let Some(tonemapping) = tonemapping { + view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + view_key |= 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, + }; + } + if let Some(DebandDither::Enabled) = dither { + view_key |= MeshPipelineKey::DEBAND_DITHER; + } + } + + if ssao.is_some() { + view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; + } + + let environment_map_loaded = match environment_map { + Some(environment_map) => environment_map.is_loaded(&images), + None => false, + }; + if environment_map_loaded { + view_key |= MeshPipelineKey::ENVIRONMENT_MAP; + } + + match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { + ShadowFilteringMethod::Hardware2x2 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; + } + ShadowFilteringMethod::Castano13 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13; + } + ShadowFilteringMethod::Jimenez14 => { + view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14; + } + } + + let pipeline_id = + pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key); + + commands + .entity(entity) + .insert(DeferredLightingPipeline { pipeline_id }); + } +} diff --git a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl new file mode 100644 index 0000000000000..c98d5c19a7f7b --- /dev/null +++ b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl @@ -0,0 +1,129 @@ +#define_import_path bevy_pbr::pbr_deferred_functions +#import bevy_pbr::pbr_types PbrInput, standard_material_new, STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT +#import bevy_pbr::pbr_deferred_types as deferred_types +#import bevy_pbr::pbr_functions as pbr_functions +#import bevy_pbr::rgb9e5 as rgb9e5 +#import bevy_pbr::mesh_view_bindings as view_bindings +#import bevy_pbr::mesh_view_bindings view +#import bevy_pbr::utils octahedral_encode, octahedral_decode + +// --------------------------- +// from https://github.com/DGriffin91/bevy_coordinate_systems/blob/main/src/transformations.wgsl +// --------------------------- + +/// Convert a ndc space position to world space +fn position_ndc_to_world(ndc_pos: vec3) -> vec3 { + let world_pos = view.inverse_view_proj * vec4(ndc_pos, 1.0); + return world_pos.xyz / world_pos.w; +} + +/// Convert ndc space xy coordinate [-1.0 .. 1.0] to uv [0.0 .. 1.0] +fn ndc_to_uv(ndc: vec2) -> vec2 { + return ndc * vec2(0.5, -0.5) + vec2(0.5); +} + +/// Convert uv [0.0 .. 1.0] coordinate to ndc space xy [-1.0 .. 1.0] +fn uv_to_ndc(uv: vec2) -> vec2 { + return uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0); +} + +/// Returns the (0.0, 0.0) .. (1.0, 1.0) position within the viewport for the current render target. +/// [0 .. render target viewport size] eg. [(0.0, 0.0) .. (1280.0, 720.0)] to [(0.0, 0.0) .. (1.0, 1.0)] +fn frag_coord_to_uv(frag_coord: vec2) -> vec2 { + return (frag_coord - view.viewport.xy) / view.viewport.zw; +} + +/// Convert frag coord to ndc. +fn frag_coord_to_ndc(frag_coord: vec4) -> vec3 { + return vec3(uv_to_ndc(frag_coord_to_uv(frag_coord.xy)), frag_coord.z); +} + +// Creates the deferred gbuffer from a PbrInput. +fn deferred_gbuffer_from_pbr_input(in: PbrInput) -> vec4 { + // Only monochrome occlusion supported. May not be worth including at all. + // Some models have baked occlusion, GLTF only supports monochrome. + // Real time occlusion is applied in the deferred lighting pass. + // Deriving luminance via Rec. 709. coefficients + // https://en.wikipedia.org/wiki/Rec._709 + let occlusion = dot(in.occlusion, vec3(0.2126, 0.7152, 0.0722)); +#ifdef WEBGL2 // More crunched for webgl so we can also fit depth. + var props = deferred_types::pack_unorm3x4_plus_unorm_20_(vec4( + in.material.reflectance, + in.material.metallic, + occlusion, + in.frag_coord.z)); +#else + var props = deferred_types::pack_unorm4x8_(vec4( + in.material.reflectance, // could be fewer bits + in.material.metallic, // could be fewer bits + occlusion, // is this worth including? + 0.0)); // spare +#endif // WEBGL2 + let flags = deferred_types::deferred_flags_from_mesh_material_flags(in.flags, in.material.flags); + let octahedral_normal = octahedral_encode(normalize(in.N)); + var base_color_srgb = vec3(0.0); + var emissive = in.material.emissive.rgb; + if ((in.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) { + // Material is unlit, use emissive component of gbuffer for color data. + // Unlit materials are effectively emissive. + emissive = in.material.base_color.rgb; + } else { + base_color_srgb = pow(in.material.base_color.rgb, vec3(1.0 / 2.2)); + } + let deferred = vec4( + deferred_types::pack_unorm4x8_(vec4(base_color_srgb, in.material.perceptual_roughness)), + rgb9e5::vec3_to_rgb9e5_(emissive), + props, + deferred_types::pack_24bit_normal_and_flags(octahedral_normal, flags), + ); + return deferred; +} + +// Creates a PbrInput from the deferred gbuffer. +fn pbr_input_from_deferred_gbuffer(frag_coord: vec4, gbuffer: vec4) -> PbrInput { + var pbr: PbrInput; + pbr.material = standard_material_new(); + + let flags = deferred_types::unpack_flags(gbuffer.a); + let deferred_flags = deferred_types::mesh_material_flags_from_deferred_flags(flags); + pbr.flags = deferred_flags.x; + pbr.material.flags = deferred_flags.y; + + let base_rough = deferred_types::unpack_unorm4x8_(gbuffer.r); + pbr.material.perceptual_roughness = base_rough.a; + let emissive = rgb9e5::rgb9e5_to_vec3_(gbuffer.g); + if ((pbr.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) { + pbr.material.base_color = vec4(emissive, 1.0); + pbr.material.emissive = vec4(vec3(0.0), 1.0); + } else { + pbr.material.base_color = vec4(pow(base_rough.rgb, vec3(2.2)), 1.0); + pbr.material.emissive = vec4(emissive, 1.0); + } +#ifdef WEBGL2 // More crunched for webgl so we can also fit depth. + let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b); + // Bias to 0.5 since that's the value for almost all materials. + pbr.material.reflectance = saturate(props.r - 0.03333333333); +#else + let props = deferred_types::unpack_unorm4x8_(gbuffer.b); + pbr.material.reflectance = props.r; +#endif // WEBGL2 + pbr.material.metallic = props.g; + pbr.occlusion = vec3(props.b); + let octahedral_normal = deferred_types::unpack_24bit_normal(gbuffer.a); + let N = octahedral_decode(octahedral_normal); + + let world_position = vec4(position_ndc_to_world(frag_coord_to_ndc(frag_coord)), 1.0); + let is_orthographic = view.projection[3].w == 1.0; + let V = pbr_functions::calculate_view(world_position, is_orthographic); + + pbr.frag_coord = frag_coord; + pbr.world_normal = N; + pbr.world_position = world_position; + pbr.N = N; + pbr.V = V; + pbr.is_orthographic = is_orthographic; + + return pbr; +} + + diff --git a/crates/bevy_pbr/src/deferred/pbr_deferred_types.wgsl b/crates/bevy_pbr/src/deferred/pbr_deferred_types.wgsl new file mode 100644 index 0000000000000..c9aa026d70d51 --- /dev/null +++ b/crates/bevy_pbr/src/deferred/pbr_deferred_types.wgsl @@ -0,0 +1,86 @@ +#define_import_path bevy_pbr::pbr_deferred_types +#import bevy_pbr::mesh_types MESH_FLAGS_SHADOW_RECEIVER_BIT +#import bevy_pbr::pbr_types STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT, STANDARD_MATERIAL_FLAGS_UNLIT_BIT + +// Maximum of 8 bits available +const DEFERRED_FLAGS_UNLIT_BIT: u32 = 1u; +const DEFERRED_FLAGS_FOG_ENABLED_BIT: u32 = 2u; +const DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 4u; + +fn deferred_flags_from_mesh_material_flags(mesh_flags: u32, mat_flags: u32) -> u32 { + var flags = 0u; + flags |= u32((mesh_flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) * DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT; + flags |= u32((mat_flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) * DEFERRED_FLAGS_FOG_ENABLED_BIT; + flags |= u32((mat_flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) * DEFERRED_FLAGS_UNLIT_BIT; + return flags; +} + +fn mesh_material_flags_from_deferred_flags(deferred_flags: u32) -> vec2 { + var mat_flags = 0u; + var mesh_flags = 0u; + mesh_flags |= u32((deferred_flags & DEFERRED_MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u) * MESH_FLAGS_SHADOW_RECEIVER_BIT; + mat_flags |= u32((deferred_flags & DEFERRED_FLAGS_FOG_ENABLED_BIT) != 0u) * STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT; + mat_flags |= u32((deferred_flags & DEFERRED_FLAGS_UNLIT_BIT) != 0u) * STANDARD_MATERIAL_FLAGS_UNLIT_BIT; + return vec2(mesh_flags, mat_flags); +} + +const U12MAXF = 4095.0; +const U16MAXF = 65535.0; +const U20MAXF = 1048575.0; + +// Storing normals as oct24. +// Flags are stored in the remaining 8 bits. +// https://jcgt.org/published/0003/02/01/paper.pdf +// Could possibly go down to oct20 if the space is needed. + +fn pack_24bit_normal_and_flags(octahedral_normal: vec2, flags: u32) -> u32 { + let unorm1 = u32(saturate(octahedral_normal.x) * U12MAXF + 0.5); + let unorm2 = u32(saturate(octahedral_normal.y) * U12MAXF + 0.5); + return (unorm1 & 0xFFFu) | ((unorm2 & 0xFFFu) << 12u) | ((flags & 0xFFu) << 24u); +} + +fn unpack_24bit_normal(packed: u32) -> vec2 { + let unorm1 = packed & 0xFFFu; + let unorm2 = (packed >> 12u) & 0xFFFu; + return vec2(f32(unorm1) / U12MAXF, f32(unorm2) / U12MAXF); +} + +fn unpack_flags(packed: u32) -> u32 { + return (packed >> 24u) & 0xFFu; +} + +// The builtin one didn't work in webgl. +// "'unpackUnorm4x8' : no matching overloaded function found" +// https://github.com/gfx-rs/naga/issues/2006 +fn unpack_unorm4x8_(v: u32) -> vec4 { + return vec4( + f32(v & 0xFFu), + f32((v >> 8u) & 0xFFu), + f32((v >> 16u) & 0xFFu), + f32((v >> 24u) & 0xFFu) + ) / 255.0; +} + +// 'packUnorm4x8' : no matching overloaded function found +// https://github.com/gfx-rs/naga/issues/2006 +fn pack_unorm4x8_(values: vec4) -> u32 { + let v = vec4(saturate(values) * 255.0 + 0.5); + return (v.w << 24u) | (v.z << 16u) | (v.y << 8u) | v.x; +} + +// Pack 3x 4bit unorm + 1x 20bit +fn pack_unorm3x4_plus_unorm_20_(v: vec4) -> u32 { + let sm = vec3(saturate(v.xyz) * 15.0 + 0.5); + let bg = u32(saturate(v.w) * U20MAXF + 0.5); + return (bg << 12u) | (sm.z << 8u) | (sm.y << 4u) | sm.x; +} + +// Unpack 3x 4bit unorm + 1x 20bit +fn unpack_unorm3x4_plus_unorm_20_(v: u32) -> vec4 { + return vec4( + f32(v & 0xfu) / 15.0, + f32((v >> 4u) & 0xFu) / 15.0, + f32((v >> 8u) & 0xFu) / 15.0, + f32((v >> 12u) & 0xFFFFFFu) / U20MAXF, + ); +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index bac3c25cf6af8..c6eb9851ec4e1 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -4,6 +4,7 @@ pub mod wireframe; mod alpha; mod bundle; +pub mod deferred; mod environment_map; mod fog; mod light; @@ -63,6 +64,8 @@ use bevy_render::{ use bevy_transform::TransformSystem; use environment_map::EnvironmentMapPlugin; +use crate::deferred::DeferredPbrLightingPlugin; + pub const PBR_TYPES_SHADER_HANDLE: Handle = Handle::weak_from_u128(1708015359337029744); pub const PBR_BINDINGS_SHADER_HANDLE: Handle = Handle::weak_from_u128(5635987986427308186); pub const UTILS_HANDLE: Handle = Handle::weak_from_u128(1900548483293416725); @@ -76,18 +79,26 @@ pub const PBR_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(16550102 pub const PBR_AMBIENT_HANDLE: Handle = Handle::weak_from_u128(2441520459096337034); pub const PARALLAX_MAPPING_SHADER_HANDLE: Handle = Handle::weak_from_u128(17035894873630133905); +pub const PBR_PREPASS_FUNCTIONS_SHADER_HANDLE: Handle = + Handle::weak_from_u128(73204817249182637); +pub const PBR_DEFERRED_TYPES_HANDLE: Handle = Handle::weak_from_u128(3221241127431430599); +pub const PBR_DEFERRED_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(72019026415438599); +pub const RGB9E5_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(2659010996143919192); /// Sets up the entire PBR infrastructure of bevy. pub struct PbrPlugin { /// Controls if the prepass is enabled for the StandardMaterial. /// For more information about what a prepass is, see the [`bevy_core_pipeline::prepass`] docs. pub prepass_enabled: bool, + /// Controls if [`DeferredPbrLightingPlugin`] is added. + pub add_default_deferred_lighting_plugin: bool, } impl Default for PbrPlugin { fn default() -> Self { Self { prepass_enabled: true, + add_default_deferred_lighting_plugin: true, } } } @@ -125,6 +136,18 @@ impl Plugin for PbrPlugin { "render/shadows.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + PBR_DEFERRED_TYPES_HANDLE, + "deferred/pbr_deferred_types.wgsl", + Shader::from_wgsl + ); + load_internal_asset!( + app, + PBR_DEFERRED_FUNCTIONS_HANDLE, + "deferred/pbr_deferred_functions.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, SHADOW_SAMPLING_HANDLE, @@ -137,6 +160,12 @@ impl Plugin for PbrPlugin { "render/pbr_functions.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + RGB9E5_FUNCTIONS_HANDLE, + "render/rgb9e5.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, PBR_AMBIENT_HANDLE, @@ -144,6 +173,12 @@ impl Plugin for PbrPlugin { Shader::from_wgsl ); load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl); + load_internal_asset!( + app, + PBR_PREPASS_FUNCTIONS_SHADER_HANDLE, + "render/pbr_prepass_functions.wgsl", + Shader::from_wgsl + ); load_internal_asset!( app, PBR_PREPASS_SHADER_HANDLE, @@ -180,6 +215,8 @@ impl Plugin for PbrPlugin { .init_resource::() .init_resource::() .init_resource::() + .register_type::() + .init_resource::() .add_plugins(( MeshRenderPlugin, MaterialPlugin:: { @@ -190,6 +227,7 @@ impl Plugin for PbrPlugin { EnvironmentMapPlugin, ExtractResourcePlugin::::default(), FogPlugin, + ExtractResourcePlugin::::default(), ExtractComponentPlugin::::default(), )) .configure_sets( @@ -245,6 +283,10 @@ impl Plugin for PbrPlugin { ), ); + if self.add_default_deferred_lighting_plugin { + app.add_plugins(DeferredPbrLightingPlugin); + } + app.world.resource_mut::>().insert( Handle::::default(), StandardMaterial { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 50738889f5910..96e43017b877b 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -8,7 +8,7 @@ use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Hand use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, experimental::taa::TemporalAntiAliasSettings, - prepass::NormalPrepass, + prepass::{DeferredPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; @@ -16,7 +16,9 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; +use bevy_reflect::Reflect; use bevy_render::{ + extract_resource::ExtractResource, mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, @@ -116,8 +118,16 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { 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] - /// Add a bias to the view depth of the mesh which can be used to force a specific render order + 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 { @@ -137,6 +147,19 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized { 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 + } + /// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's /// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input. #[allow(unused_variables)] @@ -422,6 +445,7 @@ pub fn queue_material_meshes( Option<&ShadowFilteringMethod>, Option<&ScreenSpaceAmbientOcclusionSettings>, Option<&NormalPrepass>, + Option<&DeferredPrepass>, Option<&TemporalAntiAliasSettings>, &mut RenderPhase, &mut RenderPhase, @@ -439,6 +463,7 @@ pub fn queue_material_meshes( shadow_filter_method, ssao, normal_prepass, + deferred_prepass, taa_settings, mut opaque_phase, mut alpha_mask_phase, @@ -455,6 +480,11 @@ pub fn queue_material_meshes( if normal_prepass.is_some() { view_key |= MeshPipelineKey::NORMAL_PREPASS; } + + if deferred_prepass.is_some() { + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + if taa_settings.is_some() { view_key |= MeshPipelineKey::TAA; } @@ -502,6 +532,13 @@ pub fn queue_material_meshes( let Some(material) = render_materials.get(material_asset_id) else { continue; }; + + let forward = match material.properties.render_method { + OpaqueRendererMethod::Forward => true, + OpaqueRendererMethod::Deferred => false, + OpaqueRendererMethod::Auto => unreachable!(), + }; + let mut mesh_key = view_key; mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); @@ -511,6 +548,10 @@ pub fn queue_material_meshes( } mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); + if deferred_prepass.is_some() && !forward { + mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; + } + let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline, @@ -535,24 +576,28 @@ pub fn queue_material_meshes( + material.properties.depth_bias; match material.properties.alpha_mode { AlphaMode::Opaque => { - opaque_phase.add(Opaque3d { - entity: *visible_entity, - draw_function: draw_opaque_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - dynamic_offset: None, - }); + if forward { + opaque_phase.add(Opaque3d { + entity: *visible_entity, + draw_function: draw_opaque_pbr, + pipeline: pipeline_id, + distance, + batch_range: 0..1, + dynamic_offset: None, + }); + } } AlphaMode::Mask(_) => { - alpha_mask_phase.add(AlphaMask3d { - entity: *visible_entity, - draw_function: draw_alpha_mask_pbr, - pipeline: pipeline_id, - distance, - batch_range: 0..1, - dynamic_offset: None, - }); + if forward { + alpha_mask_phase.add(AlphaMask3d { + entity: *visible_entity, + draw_function: draw_alpha_mask_pbr, + pipeline: pipeline_id, + distance, + batch_range: 0..1, + dynamic_offset: None, + }); + } } AlphaMode::Blend | AlphaMode::Premultiplied @@ -572,8 +617,59 @@ pub fn queue_material_meshes( } } +/// 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, 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, /// Add a bias to the view depth of the mesh which can be used to force a specific render order @@ -676,6 +772,7 @@ impl Default for PrepareNextFrameMaterials { /// This system prepares all assets of the corresponding [`Material`] type /// which where extracted this frame for the GPU. +#[allow(clippy::too_many_arguments)] pub fn prepare_materials( mut prepare_next_frame: Local>, mut extracted_assets: ResMut>, @@ -684,6 +781,7 @@ pub fn prepare_materials( images: Res>, fallback_image: Res, pipeline: Res>, + default_opaque_render_method: Res, ) { let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (id, material) in queued_assets.into_iter() { @@ -693,6 +791,7 @@ pub fn prepare_materials( &images, &fallback_image, &pipeline, + default_opaque_render_method.0, ) { Ok(prepared_asset) => { render_materials.insert(id, prepared_asset); @@ -714,6 +813,7 @@ pub fn prepare_materials( &images, &fallback_image, &pipeline, + default_opaque_render_method.0, ) { Ok(prepared_asset) => { render_materials.insert(id, prepared_asset); @@ -731,6 +831,7 @@ fn prepare_material( images: &RenderAssets, fallback_image: &FallbackImage, pipeline: &MaterialPipeline, + default_opaque_render_method: OpaqueRendererMethod, ) -> Result, AsBindGroupError> { let prepared = material.as_bind_group( &pipeline.material_layout, @@ -738,6 +839,11 @@ fn prepare_material( images, fallback_image, )?; + let method = match material.opaque_render_method() { + OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, + OpaqueRendererMethod::Deferred => OpaqueRendererMethod::Deferred, + OpaqueRendererMethod::Auto => default_opaque_render_method, + }; Ok(PreparedMaterial { bindings: prepared.bindings, bind_group: prepared.bind_group, @@ -745,6 +851,7 @@ fn prepare_material( properties: MaterialProperties { alpha_mode: material.alpha_mode(), depth_bias: material.depth_bias(), + render_method: method, }, }) } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index f66fc80e3f249..a07c144374310 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -1,6 +1,7 @@ use crate::{ - AlphaMode, Material, MaterialPipeline, MaterialPipelineKey, ParallaxMappingMethod, - PBR_PREPASS_SHADER_HANDLE, PBR_SHADER_HANDLE, + deferred::DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID, AlphaMode, Material, MaterialPipeline, + MaterialPipelineKey, OpaqueRendererMethod, ParallaxMappingMethod, PBR_PREPASS_SHADER_HANDLE, + PBR_SHADER_HANDLE, }; use bevy_asset::{Asset, Handle}; use bevy_math::Vec4; @@ -316,6 +317,14 @@ pub struct StandardMaterial { /// /// Default is `16.0`. pub max_parallax_layer_count: f32, + + /// Render method used for opaque materials. (Where `alpha_mode` is [`AlphaMode::Opaque`] or [`AlphaMode::Mask`]) + pub opaque_render_method: OpaqueRendererMethod, + + /// Used for selecting the deferred lighting pass for deferred materials. + /// Default is [`DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID`] for default + /// PBR deferred lighting pass. Ignored in the case of forward materials. + pub deferred_lighting_pass_id: u8, } impl Default for StandardMaterial { @@ -349,6 +358,8 @@ impl Default for StandardMaterial { parallax_depth_scale: 0.1, max_parallax_layer_count: 16.0, parallax_mapping_method: ParallaxMappingMethod::Occlusion, + opaque_render_method: OpaqueRendererMethod::Auto, + deferred_lighting_pass_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID, } } } @@ -441,6 +452,8 @@ pub struct StandardMaterialUniform { /// Using [`ParallaxMappingMethod::Relief`], how many additional /// steps to use at most to find the depth value. pub max_relief_mapping_search_steps: u32, + /// ID for specifying which deferred lighting pass should be used for rendering this material, if any. + pub deferred_lighting_pass_id: u32, } impl AsBindGroupShaderType for StandardMaterial { @@ -514,6 +527,7 @@ impl AsBindGroupShaderType for StandardMaterial { parallax_depth_scale: self.parallax_depth_scale, max_parallax_layer_count: self.max_parallax_layer_count, max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(), + deferred_lighting_pass_id: self.deferred_lighting_pass_id as u32, } } } @@ -572,6 +586,10 @@ impl Material for StandardMaterial { PBR_PREPASS_SHADER_HANDLE.into() } + fn deferred_fragment_shader() -> ShaderRef { + PBR_SHADER_HANDLE.into() + } + fn fragment_shader() -> ShaderRef { PBR_SHADER_HANDLE.into() } @@ -585,4 +603,9 @@ impl Material for StandardMaterial { fn depth_bias(&self) -> f32 { self.depth_bias } + + #[inline] + fn opaque_render_method(&self) -> OpaqueRendererMethod { + self.opaque_render_method + } } diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 372757f935148..53e26506121a0 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -1,11 +1,15 @@ 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, DepthPrepass, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, - ViewPrepassTextures, DEPTH_PREPASS_FORMAT, MOTION_VECTOR_PREPASS_FORMAT, - NORMAL_PREPASS_FORMAT, + AlphaMask3dPrepass, DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, + Opaque3dPrepass, ViewPrepassTextures, MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, }, }; use bevy_ecs::{ @@ -28,27 +32,28 @@ use bevy_render::{ }, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingResource, BindingType, BlendState, BufferBindingType, - ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, - DynamicUniformBuffer, FragmentState, FrontFace, MultisampleState, PipelineCache, - PolygonMode, PrimitiveState, PushConstantRange, RenderPipelineDescriptor, Shader, - ShaderRef, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, - SpecializedMeshPipelines, StencilFaceState, StencilState, TextureSampleType, - TextureViewDimension, VertexState, + 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::{FallbackImagesDepth, FallbackImagesMsaa}, + 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, - RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SetMaterialBindGroup, - SetMeshBindGroup, + OpaqueRendererMethod, RenderMaterialInstances, RenderMaterials, RenderMeshInstances, + SetMaterialBindGroup, SetMeshBindGroup, }; use std::{hash::Hash, marker::PhantomData}; @@ -60,6 +65,8 @@ pub const PREPASS_BINDINGS_SHADER_HANDLE: Handle = 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. @@ -97,6 +104,13 @@ where 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; }; @@ -172,6 +186,8 @@ where render_app .add_render_command::>() .add_render_command::>() + .add_render_command::>() + .add_render_command::>() .add_systems( Render, queue_prepass_material_meshes:: @@ -225,8 +241,10 @@ pub struct PrepassPipeline { pub view_layout_no_motion_vectors: BindGroupLayout, pub mesh_layouts: MeshLayouts, pub material_layout: BindGroupLayout, - pub material_vertex_shader: Option>, - pub material_fragment_shader: Option>, + 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, } @@ -311,12 +329,22 @@ impl FromWorld for PrepassPipeline { view_layout_motion_vectors, view_layout_no_motion_vectors, mesh_layouts: mesh_pipeline.mesh_layouts.clone(), - material_vertex_shader: match M::prepass_vertex_shader() { + 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)), }, - material_fragment_shader: match M::prepass_fragment_shader() { + 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)), @@ -354,6 +382,11 @@ where // 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()); } @@ -394,9 +427,15 @@ where } if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { - vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2)); 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)); @@ -405,15 +444,32 @@ where if key .mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) { - shader_defs.push("MOTION_VECTOR_PREPASS".into()); + 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 - .intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS) + .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()); } @@ -430,25 +486,40 @@ where 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![]; - targets.push( + let mut targets = vec![ key.mesh_key .contains(MeshPipelineKey::NORMAL_PREPASS) .then_some(ColorTargetState { format: NORMAL_PREPASS_FORMAT, - blend: Some(BlendState::REPLACE), + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. + blend: None, write_mask: ColorWrites::ALL, }), - ); - targets.push( key.mesh_key .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) .then_some(ColorTargetState { format: MOTION_VECTOR_PREPASS_FORMAT, - blend: Some(BlendState::REPLACE), + // 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) @@ -461,13 +532,20 @@ where let fragment_required = !targets.is_empty() || key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) - && self.material_fragment_shader.is_some()); + && self.prepass_material_fragment_shader.is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material - let frag_shader_handle = match self.material_fragment_shader.clone() { - Some(frag_shader_handle) => frag_shader_handle, - _ => PREPASS_SHADER_HANDLE, + 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 { @@ -479,7 +557,13 @@ where }); // Use the vertex shader from the material if present - let vert_shader_handle = if let Some(handle) = &self.material_vertex_shader { + 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 @@ -512,7 +596,7 @@ where conservative: false, }, depth_stencil: Some(DepthStencilState { - format: DEPTH_PREPASS_FORMAT, + format: CORE_3D_DEPTH_FORMAT, depth_write_enabled: true, depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { @@ -546,9 +630,9 @@ where } pub fn get_bind_group_layout_entries( - bindings: [u32; 3], + bindings: [u32; 4], multisampled: bool, -) -> [BindGroupLayoutEntry; 3] { +) -> [BindGroupLayoutEntry; 4] { [ // Depth texture BindGroupLayoutEntry { @@ -583,53 +667,91 @@ pub fn get_bind_group_layout_entries( }, 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, + }, ] } -pub fn get_bindings<'a>( - prepass_textures: Option<&'a ViewPrepassTextures>, - fallback_images: &'a mut FallbackImagesMsaa, - fallback_depths: &'a mut FallbackImagesDepth, - msaa: &'a Msaa, - bindings: [u32; 3], -) -> [BindGroupEntry<'a>; 3] { +// 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.default_view, - None => { - &fallback_depths - .image_for_samplecount(msaa.samples()) - .texture_view - } + 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()) + .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(); - [ - BindGroupEntry { - binding: bindings[0], - resource: BindingResource::TextureView(depth_view), - }, - BindGroupEntry { - binding: bindings[1], - resource: BindingResource::TextureView(normal_view), - }, - BindGroupEntry { - binding: bindings[2], - resource: BindingResource::TextureView(motion_vectors_view), - }, - ] + 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 @@ -754,6 +876,8 @@ pub fn prepare_prepass_view_bind_group( 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, @@ -767,9 +891,12 @@ pub fn queue_prepass_material_meshes( &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, @@ -782,14 +909,25 @@ pub fn queue_prepass_material_meshes( .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()); @@ -803,6 +941,9 @@ pub fn queue_prepass_material_meshes( 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 { @@ -834,6 +975,18 @@ pub fn queue_prepass_material_meshes( | 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, @@ -856,24 +1009,52 @@ pub fn queue_prepass_material_meshes( + material.properties.depth_bias; match alpha_mode { AlphaMode::Opaque => { - opaque_phase.add(Opaque3dPrepass { - entity: *visible_entity, - draw_function: opaque_draw_prepass, - pipeline_id, - distance, - batch_range: 0..1, - dynamic_offset: None, - }); + 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(_) => { - alpha_mask_phase.add(AlphaMask3dPrepass { - entity: *visible_entity, - draw_function: alpha_mask_draw_prepass, - pipeline_id, - distance, - batch_range: 0..1, - dynamic_offset: None, - }); + 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 diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 396935ff411e7..fd2021e9eee1e 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -1,60 +1,15 @@ #import bevy_pbr::prepass_bindings #import bevy_pbr::mesh_functions +#import bevy_pbr::prepass_io Vertex, VertexOutput, FragmentInput, FragmentOutput #import bevy_pbr::skinning #import bevy_pbr::morph #import bevy_pbr::mesh_bindings mesh #import bevy_render::instance_index get_instance_index +#import bevy_pbr::mesh_view_bindings view, previous_view_proj -// Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can -// pass them to custom prepass shaders like pbr_prepass.wgsl. -struct Vertex { - @builtin(instance_index) instance_index: u32, - @location(0) position: vec3, - -#ifdef VERTEX_UVS - @location(1) uv: vec2, -#endif // VERTEX_UVS - -#ifdef NORMAL_PREPASS - @location(2) normal: vec3, -#ifdef VERTEX_TANGENTS - @location(3) tangent: vec4, -#endif // VERTEX_TANGENTS -#endif // NORMAL_PREPASS - -#ifdef SKINNED - @location(4) joint_indices: vec4, - @location(5) joint_weights: vec4, -#endif // SKINNED - -#ifdef MORPH_TARGETS - @builtin(vertex_index) index: u32, -#endif // MORPH_TARGETS -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - -#ifdef VERTEX_UVS - @location(0) uv: vec2, -#endif // VERTEX_UVS - -#ifdef NORMAL_PREPASS - @location(1) world_normal: vec3, -#ifdef VERTEX_TANGENTS - @location(2) world_tangent: vec4, -#endif // VERTEX_TANGENTS -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(3) world_position: vec4, - @location(4) previous_world_position: vec4, -#endif // MOTION_VECTOR_PREPASS - -#ifdef DEPTH_CLAMP_ORTHO - @location(5) clip_position_unclamped: vec4, -#endif // DEPTH_CLAMP_ORTHO -} +#ifdef DEFERRED_PREPASS +#import bevy_pbr::rgb9e5 +#endif #ifdef MORPH_TARGETS fn morph_vertex(vertex_in: Vertex) -> Vertex { @@ -105,7 +60,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.uv = vertex.uv; #endif // VERTEX_UVS -#ifdef NORMAL_PREPASS +#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS #ifdef SKINNED out.world_normal = bevy_pbr::skinning::skin_normals(model, vertex.normal); #else // SKINNED @@ -126,10 +81,17 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { get_instance_index(vertex_no_morph.instance_index) ); #endif // VERTEX_TANGENTS -#endif // NORMAL_PREPASS +#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS -#ifdef MOTION_VECTOR_PREPASS +#ifdef VERTEX_COLORS + out.color = vertex.color; +#endif + +#ifdef MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS out.world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); +#endif // MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS + +#ifdef MOTION_VECTOR_PREPASS // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 out.previous_world_position = bevy_pbr::mesh_functions::mesh_position_local_to_world( @@ -138,43 +100,16 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { ); #endif // MOTION_VECTOR_PREPASS +#ifdef VERTEX_OUTPUT_INSTANCE_INDEX + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 + out.instance_index = get_instance_index(vertex_no_morph.instance_index); +#endif + return out; } #ifdef PREPASS_FRAGMENT -struct FragmentInput { -#ifdef VERTEX_UVS - @location(0) uv: vec2, -#endif // VERTEX_UVS - -#ifdef NORMAL_PREPASS - @location(1) world_normal: vec3, -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(3) world_position: vec4, - @location(4) previous_world_position: vec4, -#endif // MOTION_VECTOR_PREPASS - -#ifdef DEPTH_CLAMP_ORTHO - @location(5) clip_position_unclamped: vec4, -#endif // DEPTH_CLAMP_ORTHO -} - -struct FragmentOutput { -#ifdef NORMAL_PREPASS - @location(0) normal: vec4, -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(1) motion_vector: vec2, -#endif // MOTION_VECTOR_PREPASS - -#ifdef DEPTH_CLAMP_ORTHO - @builtin(frag_depth) frag_depth: f32, -#endif // DEPTH_CLAMP_ORTHO -} - @fragment fn fragment(in: FragmentInput) -> FragmentOutput { var out: FragmentOutput; @@ -188,7 +123,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #endif // DEPTH_CLAMP_ORTHO #ifdef MOTION_VECTOR_PREPASS - let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position; + let clip_position_t = view.unjittered_view_proj * in.world_position; let clip_position = clip_position_t.xy / clip_position_t.w; let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position; let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w; @@ -201,6 +136,15 @@ fn fragment(in: FragmentInput) -> FragmentOutput { out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5); #endif // MOTION_VECTOR_PREPASS +#ifdef DEFERRED_PREPASS + // There isn't any material info available for this default prepass shader so we are just writing  + // emissive magenta out to the deferred gbuffer to be rendered by the first deferred lighting pass layer. + // The is here so if the default prepass fragment is used for deferred magenta will be rendered, and also + // as an example to show that a user could write to the deferred gbuffer if they were to start from this shader. + out.deferred = vec4(0u, bevy_pbr::rgb9e5::vec3_to_rgb9e5_(vec3(1.0, 0.0, 1.0)), 0u, 0u); + out.deferred_lighting_pass_id = 1u; +#endif + return out; } #endif // PREPASS_FRAGMENT diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index 965af6a19425e..5f5fef362acf6 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -1,11 +1,6 @@ #define_import_path bevy_pbr::prepass_bindings -#import bevy_render::view View -#import bevy_render::globals Globals #import bevy_pbr::mesh_types -@group(0) @binding(0) var view: View; -@group(0) @binding(1) var globals: Globals; - #ifdef MOTION_VECTOR_PREPASS @group(0) @binding(2) var previous_view_proj: mat4x4; #endif // MOTION_VECTOR_PREPASS diff --git a/crates/bevy_pbr/src/prepass/prepass_io.wgsl b/crates/bevy_pbr/src/prepass/prepass_io.wgsl new file mode 100644 index 0000000000000..ca49da07e1c0f --- /dev/null +++ b/crates/bevy_pbr/src/prepass/prepass_io.wgsl @@ -0,0 +1,114 @@ +#define_import_path bevy_pbr::prepass_io + +// Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can +// pass them to custom prepass shaders like pbr_prepass.wgsl. +struct Vertex { + @builtin(instance_index) instance_index: u32, + @location(0) position: vec3, + +#ifdef VERTEX_UVS + @location(1) uv: vec2, +#endif + +#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS + @location(2) normal: vec3, +#ifdef VERTEX_TANGENTS + @location(3) tangent: vec4, +#endif +#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS + +#ifdef SKINNED + @location(4) joint_indices: vec4, + @location(5) joint_weights: vec4, +#endif + +#ifdef VERTEX_COLORS + @location(6) color: vec4, +#endif + +#ifdef MORPH_TARGETS + @builtin(vertex_index) index: u32, +#endif // MORPH_TARGETS +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + +#ifdef VERTEX_UVS + @location(0) uv: vec2, +#endif + +#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS + @location(1) world_normal: vec3, +#ifdef VERTEX_TANGENTS + @location(2) world_tangent: vec4, +#endif +#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS + + @location(3) world_position: vec4, +#ifdef MOTION_VECTOR_PREPASS + @location(4) previous_world_position: vec4, +#endif + +#ifdef DEPTH_CLAMP_ORTHO + @location(5) clip_position_unclamped: vec4, +#endif // DEPTH_CLAMP_ORTHO +#ifdef VERTEX_OUTPUT_INSTANCE_INDEX + @location(6) instance_index: u32, +#endif + +#ifdef VERTEX_COLORS + @location(7) color: vec4, +#endif +} + +struct FragmentInput { + @builtin(position) position: vec4, +#ifdef VERTEX_UVS + @location(0) uv: vec2, +#endif // VERTEX_UVS + +#ifdef NORMAL_PREPASS_OR_DEFERRED_PREPASS + @location(1) world_normal: vec3, +#ifdef VERTEX_TANGENTS + @location(2) world_tangent: vec4, +#endif +#endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS + + @location(3) world_position: vec4, +#ifdef MOTION_VECTOR_PREPASS + @location(4) previous_world_position: vec4, +#endif // MOTION_VECTOR_PREPASS + +#ifdef DEPTH_CLAMP_ORTHO + @location(5) clip_position_unclamped: vec4, +#endif // DEPTH_CLAMP_ORTHO +#ifdef VERTEX_OUTPUT_INSTANCE_INDEX + @location(6) instance_index: u32, +#endif + +#ifdef VERTEX_COLORS + @location(7) color: vec4, +#endif +}; + +#ifdef PREPASS_FRAGMENT +struct FragmentOutput { +#ifdef NORMAL_PREPASS + @location(0) normal: vec4, +#endif + +#ifdef MOTION_VECTOR_PREPASS + @location(1) motion_vector: vec2, +#endif + +#ifdef DEFERRED_PREPASS + @location(2) deferred: vec4, + @location(3) deferred_lighting_pass_id: u32, +#endif + +#ifdef DEPTH_CLAMP_ORTHO + @builtin(frag_depth) frag_depth: f32, +#endif // DEPTH_CLAMP_ORTHO +} +#endif //PREPASS_FRAGMENT diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index feec375ddc3d3..258ea59f09065 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -6,7 +6,7 @@ use crate::{ PointLight, PointLightShadowMap, PrepassPipeline, RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SpotLight, VisiblePointLights, }; -use bevy_core_pipeline::core_3d::Transparent3d; +use bevy_core_pipeline::core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT}; use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_render::{ @@ -218,7 +218,6 @@ pub const MAX_DIRECTIONAL_LIGHTS: usize = 10; pub const MAX_CASCADES_PER_LIGHT: usize = 4; #[cfg(all(feature = "webgl", target_arch = "wasm32"))] pub const MAX_CASCADES_PER_LIGHT: usize = 1; -pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; #[derive(Resource, Clone)] pub struct ShadowSamplers { @@ -912,7 +911,7 @@ pub fn prepare_lights( mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: SHADOW_FORMAT, + format: CORE_3D_DEPTH_FORMAT, label: Some("point_light_shadow_map_texture"), usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, view_formats: &[], @@ -933,7 +932,7 @@ pub fn prepare_lights( mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: SHADOW_FORMAT, + format: CORE_3D_DEPTH_FORMAT, label: Some("directional_light_shadow_map_texture"), usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, view_formats: &[], @@ -1172,7 +1171,7 @@ pub fn prepare_lights( dimension: Some(TextureViewDimension::CubeArray), #[cfg(all(feature = "webgl", target_arch = "wasm32"))] dimension: Some(TextureViewDimension::Cube), - aspect: TextureAspect::All, + aspect: TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, @@ -1187,7 +1186,7 @@ pub fn prepare_lights( dimension: Some(TextureViewDimension::D2Array), #[cfg(all(feature = "webgl", target_arch = "wasm32"))] dimension: Some(TextureViewDimension::D2), - aspect: TextureAspect::All, + aspect: TextureAspect::DepthOnly, base_mip_level: 0, mip_level_count: None, base_array_layer: 0, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 790470c3ae495..a883b56555697 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -8,7 +8,8 @@ use crate::{ use bevy_app::{Plugin, PostUpdate}; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ - core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, + core_3d::{AlphaMask3d, Opaque3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, + deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, prepass::ViewPrepassTextures, tonemapping::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, @@ -37,8 +38,8 @@ use bevy_render::{ render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ - BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImagesDepth, - FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImageMsaa, GpuImage, Image, + ImageSampler, TextureFormatPixelInfo, }, view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, @@ -137,6 +138,8 @@ impl Plugin for MeshRenderPlugin { batch_and_prepare_render_phase::, batch_and_prepare_render_phase::, batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, ) .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: @@ -513,7 +516,7 @@ impl FromWorld for MeshPipeline { || (cfg!(all(feature = "webgl", target_arch = "wasm32")) && !multisampled) { entries.extend_from_slice(&prepass::get_bind_group_layout_entries( - [17, 18, 19], + [17, 18, 19, 20], multisampled, )); } @@ -637,14 +640,15 @@ bitflags::bitflags! { const DEBAND_DITHER = (1 << 2); const DEPTH_PREPASS = (1 << 3); const NORMAL_PREPASS = (1 << 4); - const MOTION_VECTOR_PREPASS = (1 << 5); - const MAY_DISCARD = (1 << 6); // Guards shader codepaths that may discard, allowing early depth tests in most cases + const DEFERRED_PREPASS = (1 << 5); + const MOTION_VECTOR_PREPASS = (1 << 6); + const MAY_DISCARD = (1 << 7); // Guards shader codepaths that may discard, allowing early depth tests in most cases // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test - const ENVIRONMENT_MAP = (1 << 7); - const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 8); - const DEPTH_CLAMP_ORTHO = (1 << 9); - const TAA = (1 << 10); - const MORPH_TARGETS = (1 << 11); + const ENVIRONMENT_MAP = (1 << 8); + const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 9); + const DEPTH_CLAMP_ORTHO = (1 << 10); + const TAA = (1 << 11); + const MORPH_TARGETS = (1 << 12); const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3 const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); // @@ -862,7 +866,8 @@ impl SpecializedMeshPipeline for MeshPipeline { depth_write_enabled = false; } else { label = "opaque_mesh_pipeline".into(); - blend = Some(BlendState::REPLACE); + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases + blend = None; // For the opaque and alpha mask passes, fragments that are closer will replace // the current fragment value in the output and the depth is written to the // depth buffer @@ -874,6 +879,9 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("LOAD_PREPASS_NORMALS".into()); } + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + shader_defs.push("WEBGL2".into()); + if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); @@ -978,7 +986,7 @@ impl SpecializedMeshPipeline for MeshPipeline { strip_index_format: None, }, depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, + format: CORE_3D_DEPTH_FORMAT, depth_write_enabled, depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { @@ -1090,10 +1098,9 @@ pub fn prepare_mesh_view_bind_groups( Option<&EnvironmentMapLight>, &Tonemapping, )>, - (images, mut fallback_images, mut fallback_depths, fallback_cubemap): ( + (images, mut fallback_images, fallback_cubemap): ( Res>, - FallbackImagesMsaa, - FallbackImagesDepth, + FallbackImageMsaa, Res, ), msaa: Res, @@ -1124,7 +1131,7 @@ pub fn prepare_mesh_view_bind_groups( ) in &views { let fallback_ssao = fallback_images - .image_for_samplecount(1) + .image_for_samplecount(1, TextureFormat::bevy_default()) .texture_view .clone(); @@ -1205,28 +1212,31 @@ pub fn prepare_mesh_view_bind_groups( get_lut_bindings(&images, &tonemapping_luts, tonemapping, [15, 16]); entries.extend_from_slice(&tonemapping_luts); + let label = Some("mesh_view_bind_group"); + // When using WebGL, we can't have a depth texture with multisampling if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32"))) || (cfg!(all(feature = "webgl", target_arch = "wasm32")) && msaa.samples() == 1) { - entries.extend_from_slice(&prepass::get_bindings( - prepass_textures, - &mut fallback_images, - &mut fallback_depths, - &msaa, - [17, 18, 19], - )); + let prepass_bindings = + prepass::get_bindings(prepass_textures, &mut fallback_images, &msaa); + entries.extend_from_slice(&prepass_bindings.get_entries([17, 18, 19, 20])); + commands.entity(entity).insert(MeshViewBindGroup { + value: render_device.create_bind_group(&BindGroupDescriptor { + entries: &entries, + label, + layout, + }), + }); + } else { + commands.entity(entity).insert(MeshViewBindGroup { + value: render_device.create_bind_group(&BindGroupDescriptor { + entries: &entries, + label, + layout, + }), + }); } - - let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor { - entries: &entries, - label: Some("mesh_view_bind_group"), - layout, - }); - - commands.entity(entity).insert(MeshViewBindGroup { - value: view_bind_group, - }); } } } diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 4ed5cc3da1a66..f3b51ddc50abb 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -49,4 +49,5 @@ @group(0) @binding(17) var depth_prepass_texture: texture_depth_2d; @group(0) @binding(18) var normal_prepass_texture: texture_2d; @group(0) @binding(19) var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(20) var deferred_prepass_texture: texture_2d; #endif diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 545b40c5b3a24..125d07d0e2a01 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -3,9 +3,7 @@ #import bevy_pbr::pbr_functions as pbr_functions #import bevy_pbr::pbr_bindings as pbr_bindings #import bevy_pbr::pbr_types as pbr_types -#import bevy_pbr::prepass_utils -#import bevy_pbr::mesh_vertex_output MeshVertexOutput #import bevy_pbr::mesh_bindings mesh #import bevy_pbr::mesh_view_bindings view, fog, screen_space_ambient_occlusion_texture #import bevy_pbr::mesh_view_types FOG_MODE_OFF @@ -18,11 +16,34 @@ #import bevy_pbr::gtao_utils gtao_multibounce #endif +#ifdef DEFERRED_PREPASS +#import bevy_pbr::pbr_deferred_types as pbr_deferred_types +#import bevy_pbr::pbr_deferred_functions as pbr_deferred_functions +#import bevy_pbr::pbr_prepass_functions as pbr_prepass_functions +#import bevy_pbr::prepass_io as prepass_io +#else // DEFERRED_PREPASS +#import bevy_pbr::mesh_vertex_output as mesh_vertex_output +#endif // DEFERRED_PREPASS + +#ifdef MOTION_VECTOR_PREPASS +@group(0) @binding(2) +var previous_view_proj: mat4x4; +#endif // MOTION_VECTOR_PREPASS + + @fragment +#ifdef DEFERRED_PREPASS fn fragment( - in: MeshVertexOutput, - @builtin(front_facing) is_front: bool, -) -> @location(0) vec4 { + in: prepass_io::FragmentInput, + @builtin(front_facing) is_front: bool, + ) -> prepass_io::FragmentOutput { + var out: prepass_io::FragmentOutput; +#else // DEFERRED_PREPASS +fn fragment( + in: mesh_vertex_output::MeshVertexOutput, + @builtin(front_facing) is_front: bool, + ) -> @location(0) vec4 { +#endif // DEFERRED_PREPASS var output_color: vec4 = pbr_bindings::material.base_color; let is_orthographic = view.projection[3].w == 1.0; @@ -57,13 +78,13 @@ fn fragment( if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias); } -#endif +#endif // VERTEX_UVS // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { // Prepare a 'processed' StandardMaterial by sampling all textures to resolve // the material members - var pbr_input: pbr_functions::PbrInput; + var pbr_input: pbr_types::PbrInput; pbr_input.material.base_color = output_color; pbr_input.material.reflectance = pbr_bindings::material.reflectance; @@ -99,11 +120,13 @@ fn fragment( occlusion = vec3(textureSampleBias(pbr_bindings::occlusion_texture, pbr_bindings::occlusion_sampler, uv, view.mip_bias).r); } #endif +#ifndef DEFERRED_PREPASS #ifdef SCREEN_SPACE_AMBIENT_OCCLUSION let ssao = textureLoad(screen_space_ambient_occlusion_texture, vec2(in.position.xy), 0i).r; let ssao_multibounce = gtao_multibounce(ssao, pbr_input.material.base_color.rgb); occlusion = min(occlusion, ssao_multibounce); -#endif +#endif // SCREEN_SPACE_AMBIENT_OCCLUSION +#endif // DEFERRED_PREPASS pbr_input.occlusion = occlusion; pbr_input.frag_coord = in.position; @@ -139,12 +162,40 @@ fn fragment( pbr_input.occlusion = occlusion; pbr_input.flags = mesh[in.instance_index].flags; - +#ifdef DEFERRED_PREPASS + pbr_functions::alpha_discard(pbr_bindings::material, output_color); + out.deferred = pbr_deferred_functions::deferred_gbuffer_from_pbr_input(pbr_input); + out.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; +#ifdef NORMAL_PREPASS + out.normal = vec4(pbr_input.N * 0.5 + vec3(0.5), 1.0); +#endif // NORMAL_PREPASS +#else // DEFERRED_PREPASS output_color = pbr_functions::pbr(pbr_input); - } else { - output_color = pbr_functions::alpha_discard(pbr_bindings::material, output_color); +#endif // DEFERRED_PREPASS + } else { // if UNLIT_BIT != 0 + pbr_functions::alpha_discard(pbr_bindings::material, output_color); +#ifdef DEFERRED_PREPASS + var pbr_input = pbr_types::pbr_input_new(); + pbr_input.flags = mesh[in.instance_index].flags; + pbr_input.material.flags = pbr_bindings::material.flags; + pbr_input.material.base_color = output_color; + pbr_input.world_position = in.world_position; + pbr_input.world_normal = in.world_normal; + pbr_input.frag_coord = in.position; + out.deferred = pbr_deferred_functions::deferred_gbuffer_from_pbr_input(pbr_input); + out.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; +#ifdef NORMAL_PREPASS + out.normal = vec4(in.world_normal * 0.5 + vec3(0.5), 1.0); +#endif +#endif // DEFERRED_PREPASS } +#ifdef DEFERRED_PREPASS +#ifdef MOTION_VECTOR_PREPASS + out.motion_vector = pbr_prepass_functions::calculate_motion_vector(in.world_position, in.previous_world_position); +#endif // MOTION_VECTOR_PREPASS + return out; +#else //DEFERRED_PREPASS // fog if (fog.mode != FOG_MODE_OFF && (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) { output_color = pbr_functions::apply_fog(fog, output_color, in.world_position.xyz, view.world_position.xyz); @@ -165,5 +216,7 @@ fn fragment( #ifdef PREMULTIPLY_ALPHA output_color = pbr_functions::premultiply_alpha(pbr_bindings::material.flags, output_color); #endif + return output_color; +#endif //DEFERRED_PREPASS } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 6677eb70e5906..a17457c5058f6 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -17,7 +17,6 @@ #import bevy_pbr::environment_map #endif -#import bevy_pbr::mesh_bindings mesh #import bevy_pbr::mesh_types MESH_FLAGS_SHADOW_RECEIVER_BIT fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4) -> vec4 { @@ -137,47 +136,9 @@ fn calculate_view( return V; } -struct PbrInput { - material: pbr_types::StandardMaterial, - occlusion: vec3, - frag_coord: vec4, - world_position: vec4, - // Normalized world normal used for shadow mapping as normal-mapping is not used for shadow - // mapping - world_normal: vec3, - // Normalized normal-mapped world normal used for lighting - N: vec3, - // Normalized view vector in world space, pointing from the fragment world position toward the - // view world position - V: vec3, - is_orthographic: bool, - flags: u32, -}; - -// Creates a PbrInput with default values -fn pbr_input_new() -> PbrInput { - var pbr_input: PbrInput; - - pbr_input.material = pbr_types::standard_material_new(); - pbr_input.occlusion = vec3(1.0); - - pbr_input.frag_coord = vec4(0.0, 0.0, 0.0, 1.0); - pbr_input.world_position = vec4(0.0, 0.0, 0.0, 1.0); - pbr_input.world_normal = vec3(0.0, 0.0, 1.0); - - pbr_input.is_orthographic = false; - - pbr_input.N = vec3(0.0, 0.0, 1.0); - pbr_input.V = vec3(1.0, 0.0, 0.0); - - pbr_input.flags = 0u; - - return pbr_input; -} - #ifndef PREPASS_FRAGMENT fn pbr( - in: PbrInput, + in: pbr_types::PbrInput, ) -> vec4 { var output_color: vec4 = in.material.base_color; diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index 542e540309a5c..c1c05df25ecd7 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -1,87 +1,22 @@ -#import bevy_pbr::prepass_bindings +#import bevy_pbr::pbr_prepass_functions #import bevy_pbr::pbr_bindings #import bevy_pbr::pbr_types #ifdef NORMAL_PREPASS #import bevy_pbr::pbr_functions #endif // NORMAL_PREPASS -struct FragmentInput { - @builtin(front_facing) is_front: bool, - @builtin(position) frag_coord: vec4, -#ifdef VERTEX_UVS - @location(0) uv: vec2, -#endif // VERTEX_UVS - -#ifdef NORMAL_PREPASS - @location(1) world_normal: vec3, -#ifdef VERTEX_TANGENTS - @location(2) world_tangent: vec4, -#endif // VERTEX_TANGENTS -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(3) world_position: vec4, - @location(4) previous_world_position: vec4, -#endif // MOTION_VECTOR_PREPASS - -#ifdef DEPTH_CLAMP_ORTHO - @location(5) clip_position_unclamped: vec4, -#endif // DEPTH_CLAMP_ORTHO -}; - -// Cutoff used for the premultiplied alpha modes BLEND and ADD. -const PREMULTIPLIED_ALPHA_CUTOFF = 0.05; - -// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff -fn prepass_alpha_discard(in: FragmentInput) { - -#ifdef MAY_DISCARD - var output_color: vec4 = bevy_pbr::pbr_bindings::material.base_color; - -#ifdef VERTEX_UVS - if (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { - output_color = output_color * textureSampleBias(bevy_pbr::pbr_bindings::base_color_texture, bevy_pbr::pbr_bindings::base_color_sampler, in.uv, bevy_pbr::prepass_bindings::view.mip_bias); - } -#endif // VERTEX_UVS - - let alpha_mode = bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { - if output_color.a < bevy_pbr::pbr_bindings::material.alpha_cutoff { - discard; - } - } else if (alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { - if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF { - discard; - } - } else if alpha_mode == bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED { - if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) { - discard; - } - } - -#endif // MAY_DISCARD -} - +#import bevy_pbr::prepass_io as prepass_io +#import bevy_pbr::mesh_view_bindings view + #ifdef PREPASS_FRAGMENT -struct FragmentOutput { -#ifdef NORMAL_PREPASS - @location(0) normal: vec4, -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(1) motion_vector: vec2, -#endif // MOTION_VECTOR_PREPASS - -#ifdef DEPTH_CLAMP_ORTHO - @builtin(frag_depth) frag_depth: f32, -#endif // DEPTH_CLAMP_ORTHO -} - @fragment -fn fragment(in: FragmentInput) -> FragmentOutput { - prepass_alpha_discard(in); +fn fragment( + in: prepass_io::FragmentInput, + @builtin(front_facing) is_front: bool, +) -> prepass_io::FragmentOutput { + bevy_pbr::pbr_prepass_functions::prepass_alpha_discard(in); - var out: FragmentOutput; + var out: prepass_io::FragmentOutput; #ifdef DEPTH_CLAMP_ORTHO out.frag_depth = in.clip_position_unclamped.z; @@ -93,7 +28,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput { let world_normal = bevy_pbr::pbr_functions::prepare_world_normal( in.world_normal, (bevy_pbr::pbr_bindings::material.flags & bevy_pbr::pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u, - in.is_front, + is_front, ); let normal = bevy_pbr::pbr_functions::apply_normal_mapping( @@ -107,7 +42,7 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #ifdef VERTEX_UVS in.uv, #endif // VERTEX_UVS - bevy_pbr::prepass_bindings::view.mip_bias, + view.mip_bias, ); out.normal = vec4(normal * 0.5 + vec3(0.5), 1.0); @@ -117,24 +52,14 @@ fn fragment(in: FragmentInput) -> FragmentOutput { #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - let clip_position_t = bevy_pbr::prepass_bindings::view.unjittered_view_proj * in.world_position; - let clip_position = clip_position_t.xy / clip_position_t.w; - let previous_clip_position_t = bevy_pbr::prepass_bindings::previous_view_proj * in.previous_world_position; - let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w; - // These motion vectors are used as offsets to UV positions and are stored - // in the range -1,1 to allow offsetting from the one corner to the - // diagonally-opposite corner in UV coordinates, in either direction. - // A difference between diagonally-opposite corners of clip space is in the - // range -2,2, so this needs to be scaled by 0.5. And the V direction goes - // down where clip space y goes up, so y needs to be flipped. - out.motion_vector = (clip_position - previous_clip_position) * vec2(0.5, -0.5); -#endif // MOTION_VECTOR_PREPASS + out.motion_vector = bevy_pbr::pbr_prepass_functions::calculate_motion_vector(in.world_position, in.previous_world_position); +#endif return out; } #else @fragment -fn fragment(in: FragmentInput) { - prepass_alpha_discard(in); +fn fragment(in: prepass_io::FragmentInput) { + bevy_pbr::pbr_prepass_functions::prepass_alpha_discard(in); } #endif // PREPASS_FRAGMENT diff --git a/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl new file mode 100644 index 0000000000000..88fb2316e89da --- /dev/null +++ b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl @@ -0,0 +1,57 @@ +#define_import_path bevy_pbr::pbr_prepass_functions +#import bevy_pbr::prepass_io as prepass_io +#import bevy_pbr::prepass_bindings previous_view_proj +#import bevy_pbr::mesh_view_bindings view + +#import bevy_pbr::pbr_bindings as pbr_bindings +#import bevy_pbr::pbr_types as pbr_types + + +// Cutoff used for the premultiplied alpha modes BLEND and ADD. +const PREMULTIPLIED_ALPHA_CUTOFF = 0.05; + +// We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff +fn prepass_alpha_discard(in: prepass_io::FragmentInput) { + +#ifdef MAY_DISCARD + var output_color: vec4 = pbr_bindings::material.base_color; + +#ifdef VERTEX_UVS + if (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { + output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, in.uv, view.mip_bias); + } +#endif // VERTEX_UVS + + let alpha_mode = pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { + if output_color.a < pbr_bindings::material.alpha_cutoff { + discard; + } + } else if (alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { + if output_color.a < PREMULTIPLIED_ALPHA_CUTOFF { + discard; + } + } else if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED { + if all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) { + discard; + } + } + +#endif // MAY_DISCARD +} + +#ifdef MOTION_VECTOR_PREPASS +fn calculate_motion_vector(world_position: vec4, previous_world_position: vec4) -> vec2 { + let clip_position_t = view.unjittered_view_proj * world_position; + let clip_position = clip_position_t.xy / clip_position_t.w; + let previous_clip_position_t = previous_view_proj * previous_world_position; + let previous_clip_position = previous_clip_position_t.xy / previous_clip_position_t.w; + // These motion vectors are used as offsets to UV positions and are stored + // in the range -1,1 to allow offsetting from the one corner to the + // diagonally-opposite corner in UV coordinates, in either direction. + // A difference between diagonally-opposite corners of clip space is in the + // range -2,2, so this needs to be scaled by 0.5. And the V direction goes + // down where clip space y goes up, so y needs to be flipped. + return (clip_position - previous_clip_position) * vec2(0.5, -0.5); +} +#endif // MOTION_VECTOR_PREPASS \ No newline at end of file diff --git a/crates/bevy_pbr/src/render/pbr_types.wgsl b/crates/bevy_pbr/src/render/pbr_types.wgsl index 85cbed505fb27..c9041fe017a9b 100644 --- a/crates/bevy_pbr/src/render/pbr_types.wgsl +++ b/crates/bevy_pbr/src/render/pbr_types.wgsl @@ -12,8 +12,14 @@ struct StandardMaterial { parallax_depth_scale: f32, max_parallax_layer_count: f32, max_relief_mapping_search_steps: u32, + /// ID for specifying which deferred lighting pass should be used for rendering this material, if any. + deferred_lighting_pass_id: u32, }; +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE: if these flags are updated or changed. Be sure to also update +// deferred_flags_from_mesh_material_flags and mesh_material_flags_from_deferred_flags +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! const STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u; const STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u; const STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; @@ -34,6 +40,7 @@ const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MULTIPLY: u32 = 2684354560u; // ↑ To calculate/verify the values above, use the following playground: // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7792f8dd6fc6a8d4d0b6b1776898a7f4 + // Creates a StandardMaterial with default values fn standard_material_new() -> StandardMaterial { var material: StandardMaterial; @@ -49,6 +56,45 @@ fn standard_material_new() -> StandardMaterial { material.parallax_depth_scale = 0.1; material.max_parallax_layer_count = 16.0; material.max_relief_mapping_search_steps = 5u; - + material.deferred_lighting_pass_id = 1u; + return material; } + +struct PbrInput { + material: StandardMaterial, + occlusion: vec3, + frag_coord: vec4, + world_position: vec4, + // Normalized world normal used for shadow mapping as normal-mapping is not used for shadow + // mapping + world_normal: vec3, + // Normalized normal-mapped world normal used for lighting + N: vec3, + // Normalized view vector in world space, pointing from the fragment world position toward the + // view world position + V: vec3, + is_orthographic: bool, + flags: u32, +}; + +// Creates a PbrInput with default values +fn pbr_input_new() -> PbrInput { + var pbr_input: PbrInput; + + pbr_input.material = standard_material_new(); + pbr_input.occlusion = vec3(1.0); + + pbr_input.frag_coord = vec4(0.0, 0.0, 0.0, 1.0); + pbr_input.world_position = vec4(0.0, 0.0, 0.0, 1.0); + pbr_input.world_normal = vec3(0.0, 0.0, 1.0); + + pbr_input.is_orthographic = false; + + pbr_input.N = vec3(0.0, 0.0, 1.0); + pbr_input.V = vec3(1.0, 0.0, 0.0); + + pbr_input.flags = 0u; + + return pbr_input; +} diff --git a/crates/bevy_pbr/src/render/rgb9e5.wgsl b/crates/bevy_pbr/src/render/rgb9e5.wgsl new file mode 100644 index 0000000000000..c635c83dfcc4c --- /dev/null +++ b/crates/bevy_pbr/src/render/rgb9e5.wgsl @@ -0,0 +1,63 @@ +#define_import_path bevy_pbr::rgb9e5 + +const RGB9E5_EXPONENT_BITS = 5u; +const RGB9E5_MANTISSA_BITS = 9; +const RGB9E5_MANTISSA_BITSU = 9u; +const RGB9E5_EXP_BIAS = 15; +const RGB9E5_MAX_VALID_BIASED_EXP = 31u; + +//#define MAX_RGB9E5_EXP (RGB9E5_MAX_VALID_BIASED_EXP - RGB9E5_EXP_BIAS) +//#define RGB9E5_MANTISSA_VALUES (1< i32 { + let f = bitcast(x); + let biasedexponent = (f & 0x7F800000u) >> 23u; + return i32(biasedexponent) - 127; +} + +// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt +fn vec3_to_rgb9e5_(rgb_in: vec3) -> u32 { + let rgb = clamp(rgb_in, vec3(0.0), vec3(MAX_RGB9E5_)); + + let maxrgb = max(rgb.r, max(rgb.g, rgb.b)); + var exp_shared = max(-RGB9E5_EXP_BIAS - 1, floor_log2_(maxrgb)) + 1 + RGB9E5_EXP_BIAS; + var denom = exp2(f32(exp_shared - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS)); + + let maxm = i32(floor(maxrgb / denom + 0.5)); + if (maxm == RGB9E5_MANTISSA_VALUES) { + denom *= 2.0; + exp_shared += 1; + } + + let n = vec3(floor(rgb / denom + 0.5)); + + return (u32(exp_shared) << 27u) | (n.b << 18u) | (n.g << 9u) | (n.r << 0u); +} + +// Builtin extractBits() is not working on WEBGL or DX12 +// DX12: HLSL: Unimplemented("write_expr_math ExtractBits") +fn extract_bits(value: u32, offset: u32, bits: u32) -> u32 { + let mask = (1u << bits) - 1u; + return (value >> offset) & mask; +} + +fn rgb9e5_to_vec3_(v: u32) -> vec3 { + let exponent = i32(extract_bits(v, 27u, RGB9E5_EXPONENT_BITS)) - RGB9E5_EXP_BIAS - RGB9E5_MANTISSA_BITS; + let scale = exp2(f32(exponent)); + + return vec3( + f32(extract_bits(v, 0u, RGB9E5_MANTISSA_BITSU)), + f32(extract_bits(v, 9u, RGB9E5_MANTISSA_BITSU)), + f32(extract_bits(v, 18u, RGB9E5_MANTISSA_BITSU)) + ) * scale; +} diff --git a/crates/bevy_pbr/src/render/utils.wgsl b/crates/bevy_pbr/src/render/utils.wgsl index 6c8a87a5a43ef..89592f89cbf5e 100644 --- a/crates/bevy_pbr/src/render/utils.wgsl +++ b/crates/bevy_pbr/src/render/utils.wgsl @@ -1,4 +1,5 @@ #define_import_path bevy_pbr::utils +#import bevy_pbr::rgb9e5 const PI: f32 = 3.141592653589793; const HALF_PI: f32 = 1.57079632679; @@ -27,3 +28,23 @@ fn random1D(s: f32) -> f32 { fn coords_to_viewport_uv(position: vec2, viewport: vec4) -> vec2 { return (position - viewport.xy) / viewport.zw; } + +// https://jcgt.org/published/0003/02/01/paper.pdf + +// For encoding normals or unit direction vectors as octahedral coordinates. +fn octahedral_encode(v: vec3) -> vec2 { + var n = v / (abs(v.x) + abs(v.y) + abs(v.z)); + let octahedral_wrap = (1.0 - abs(n.yx)) * select(vec2(-1.0), vec2(1.0), n.xy > 0.0); + let n_xy = select(octahedral_wrap, n.xy, n.z >= 0.0); + return n_xy * 0.5 + 0.5; +} + +// For decoding normals or unit direction vectors from octahedral coordinates. +fn octahedral_decode(v: vec2) -> vec3 { + let f = v * 2.0 - 1.0; + var n = vec3(f.xy, 1.0 - abs(f.x) - abs(f.y)); + let t = saturate(-n.z); + let w = select(vec2(t), vec2(-t), n.xy >= vec2(0.0)); + n = vec3(n.xy + w, n.z); + return normalize(n); +} diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 3a31c0afbe620..eaabea3772654 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -127,8 +127,8 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { .add_render_graph_edges( CORE_3D, &[ - // PREPASS -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS - bevy_core_pipeline::core_3d::graph::node::PREPASS, + // END_PRE_PASSES -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS + bevy_core_pipeline::core_3d::graph::node::END_PREPASSES, draw_3d_graph::node::SCREEN_SPACE_AMBIENT_OCCLUSION, bevy_core_pipeline::core_3d::graph::node::START_MAIN_PASS, ], @@ -797,6 +797,7 @@ fn prepare_ssao_bind_groups( mip_level_count: Some(1), ..default() }; + let preprocess_depth_bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("ssao_preprocess_depth_bind_group"), layout: &pipelines.preprocess_depth_bind_group_layout, diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 24e91bc224dcc..4075963a614a2 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -61,7 +61,6 @@ fn fallback_image_new( value: u8, ) -> GpuImage { // TODO make this configurable per channel - let data = vec![value; format.pixel_size()]; let extents = Extent3d { width: 1, @@ -72,19 +71,29 @@ fn fallback_image_new( }, }; - let image_dimension = dimension.compatible_texture_dimension(); + // We can't create textures with data when it's a depth texture or when using multiple samples + let create_texture_with_data = !format.is_depth_stencil_format() && samples == 1; - let mut image = Image::new_fill(extents, image_dimension, &data, format); + let image_dimension = dimension.compatible_texture_dimension(); + let mut image = if create_texture_with_data { + let data = vec![value; format.pixel_size()]; + Image::new_fill(extents, image_dimension, &data, format) + } else { + let mut image = Image::default(); + image.texture_descriptor.dimension = TextureDimension::D2; + image.texture_descriptor.size = extents; + image.texture_descriptor.format = format; + image + }; image.texture_descriptor.sample_count = samples; if image_dimension == TextureDimension::D2 { image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; } - // We can't create textures with data when it's a depth texture or when using multiple samples - let texture = if format.is_depth_stencil_format() || samples > 1 { - render_device.create_texture(&image.texture_descriptor) - } else { + let texture = if create_texture_with_data { render_device.create_texture_with_data(render_queue, &image.texture_descriptor, &image.data) + } else { + render_device.create_texture(&image.texture_descriptor) }; let texture_view = texture.create_view(&TextureViewDescriptor { @@ -207,64 +216,30 @@ impl FromWorld for FallbackImageCubemap { } } -// TODO these could be combined in one FallbackImage cache. - -/// A Cache of fallback textures that uses the sample count as a key +/// A Cache of fallback textures that uses the sample count and `TextureFormat` as a key /// /// # WARNING /// Images using MSAA with sample count > 1 are not initialized with data, therefore, /// you shouldn't sample them before writing data to them first. #[derive(Resource, Deref, DerefMut, Default)] -pub struct FallbackImageMsaaCache(HashMap); - -/// A Cache of fallback depth textures that uses the sample count as a key -/// -/// # WARNING -/// Depth images are never initialized with data, therefore, -/// you shouldn't sample them before writing data to them first. -#[derive(Resource, Deref, DerefMut, Default)] -pub struct FallbackImageDepthCache(HashMap); - -#[derive(SystemParam)] -pub struct FallbackImagesMsaa<'w> { - cache: ResMut<'w, FallbackImageMsaaCache>, - render_device: Res<'w, RenderDevice>, - render_queue: Res<'w, RenderQueue>, - default_sampler: Res<'w, DefaultImageSampler>, -} - -impl<'w> FallbackImagesMsaa<'w> { - pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage { - self.cache.entry(sample_count).or_insert_with(|| { - fallback_image_new( - &self.render_device, - &self.render_queue, - &self.default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D2, - sample_count, - 255, - ) - }) - } -} +pub struct FallbackImageFormatMsaaCache(HashMap<(u32, TextureFormat), GpuImage>); #[derive(SystemParam)] -pub struct FallbackImagesDepth<'w> { - cache: ResMut<'w, FallbackImageDepthCache>, +pub struct FallbackImageMsaa<'w> { + cache: ResMut<'w, FallbackImageFormatMsaaCache>, render_device: Res<'w, RenderDevice>, render_queue: Res<'w, RenderQueue>, default_sampler: Res<'w, DefaultImageSampler>, } -impl<'w> FallbackImagesDepth<'w> { - pub fn image_for_samplecount(&mut self, sample_count: u32) -> &GpuImage { - self.cache.entry(sample_count).or_insert_with(|| { +impl<'w> FallbackImageMsaa<'w> { + pub fn image_for_samplecount(&mut self, sample_count: u32, format: TextureFormat) -> &GpuImage { + self.cache.entry((sample_count, format)).or_insert_with(|| { fallback_image_new( &self.render_device, &self.render_queue, &self.default_sampler, - TextureFormat::Depth32Float, + format, TextureViewDimension::D2, sample_count, 255, diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 7f08ec46c93a3..731da8ff6f63b 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -144,8 +144,7 @@ impl Plugin for ImagePlugin { .init_resource::() .init_resource::() .init_resource::() - .init_resource::() - .init_resource::(); + .init_resource::(); } } } diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs new file mode 100644 index 0000000000000..951f9b0f6c1ee --- /dev/null +++ b/examples/3d/deferred_rendering.rs @@ -0,0 +1,428 @@ +//! This example compares Forward, Forward + Prepass, and Deferred rendering. + +use std::f32::consts::*; + +use bevy::{ + core_pipeline::{ + fxaa::Fxaa, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, + }, + pbr::NotShadowReceiver, + pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, + pbr::{DefaultOpaqueRendererMethod, NotShadowCaster, OpaqueRendererMethod}, + prelude::*, + render::render_resource::TextureFormat, +}; + +fn main() { + App::new() + .insert_resource(Msaa::Off) + .insert_resource(DefaultOpaqueRendererMethod::deferred()) + .insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 1.0 / 5.0f32, + }) + .insert_resource(DirectionalLightShadowMap { size: 4096 }) + .add_plugins(DefaultPlugins) + .insert_resource(Normal(None)) + .insert_resource(Pause(true)) + .add_systems(Startup, (setup, setup_parallax)) + .add_systems( + Update, + (animate_light_direction, switch_mode, spin, update_normal), + ) + .run(); +} + +fn setup( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, + mut meshes: ResMut>, +) { + commands.spawn(( + Camera3dBundle { + camera: Camera { + // Deferred both supports both hdr: true and hdr: false + hdr: false, + ..default() + }, + transform: Transform::from_xyz(0.7, 0.7, 1.0) + .looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), + ..default() + }, + FogSettings { + color: Color::rgba(0.05, 0.05, 0.05, 1.0), + falloff: FogFalloff::Linear { + start: 1.0, + end: 8.0, + }, + ..default() + }, + EnvironmentMapLight { + diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), + specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), + }, + DepthPrepass, + MotionVectorPrepass, + DeferredPrepass, + Fxaa::default(), + )); + + commands.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + shadows_enabled: true, + ..default() + }, + cascade_shadow_config: CascadeShadowConfigBuilder { + num_cascades: 3, + maximum_distance: 10.0, + ..default() + } + .into(), + transform: Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 0.0, -FRAC_PI_4)), + ..default() + }); + + // FlightHelmet + let helmet_scene = asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"); + + commands.spawn(SceneBundle { + scene: helmet_scene.clone(), + ..default() + }); + commands.spawn(SceneBundle { + scene: helmet_scene, + transform: Transform::from_xyz(-3.0, 0.0, -3.0), + ..default() + }); + + let mut forward_mat: StandardMaterial = Color::rgb(0.1, 0.2, 0.1).into(); + forward_mat.opaque_render_method = OpaqueRendererMethod::Forward; + let forward_mat_h = materials.add(forward_mat); + + // Plane + commands.spawn(PbrBundle { + mesh: meshes.add(shape::Plane::from_size(50.0).into()), + material: forward_mat_h.clone(), + ..default() + }); + + let cube_h = meshes.add(Mesh::from(shape::Cube { size: 0.1 })); + let sphere_h = meshes.add(Mesh::from(shape::UVSphere { + radius: 0.125, + sectors: 128, + stacks: 128, + })); + + // Cubes + commands.spawn(PbrBundle { + mesh: cube_h.clone(), + material: forward_mat_h.clone(), + transform: Transform::from_xyz(-0.3, 0.5, -0.2), + ..default() + }); + commands.spawn(PbrBundle { + mesh: cube_h, + material: forward_mat_h, + transform: Transform::from_xyz(0.2, 0.5, 0.2), + ..default() + }); + + let sphere_color = Color::rgb(10.0, 4.0, 1.0); + let sphere_pos = Transform::from_xyz(0.4, 0.5, -0.8); + // Emissive sphere + let mut unlit_mat: StandardMaterial = sphere_color.into(); + unlit_mat.unlit = true; + commands.spawn(( + PbrBundle { + mesh: sphere_h.clone(), + material: materials.add(unlit_mat), + transform: sphere_pos, + ..default() + }, + NotShadowCaster, + )); + // Light + commands.spawn(PointLightBundle { + point_light: PointLight { + intensity: 1.0, + radius: 0.125, + shadows_enabled: true, + color: sphere_color, + ..default() + }, + transform: sphere_pos, + ..default() + }); + + // Spheres + for i in 0..6 { + let j = i % 3; + let s_val = if i < 3 { 0.0 } else { 0.2 }; + let material = if j == 0 { + materials.add(StandardMaterial { + base_color: Color::rgb(s_val, s_val, 1.0), + perceptual_roughness: 0.089, + metallic: 0.0, + ..default() + }) + } else if j == 1 { + materials.add(StandardMaterial { + base_color: Color::rgb(s_val, 1.0, s_val), + perceptual_roughness: 0.089, + metallic: 0.0, + ..default() + }) + } else { + materials.add(StandardMaterial { + base_color: Color::rgb(1.0, s_val, s_val), + perceptual_roughness: 0.089, + metallic: 0.0, + ..default() + }) + }; + commands.spawn(PbrBundle { + mesh: sphere_h.clone(), + material, + transform: Transform::from_xyz( + j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } - 0.4, + 0.125, + -j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } + 0.4, + ), + ..default() + }); + } + + // sky + commands.spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Box::default())), + material: materials.add(StandardMaterial { + base_color: Color::hex("888888").unwrap(), + unlit: true, + cull_mode: None, + ..default() + }), + transform: Transform::from_scale(Vec3::splat(1_000_000.0)), + ..default() + }, + NotShadowCaster, + NotShadowReceiver, + )); + + // Example instructions + commands.spawn( + TextBundle::from_section( + "", + TextStyle { + font_size: 18.0, + color: Color::WHITE, + ..default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(10.0), + left: Val::Px(10.0), + ..default() + }), + ); +} + +#[derive(Resource)] +struct Pause(bool); + +fn animate_light_direction( + time: Res