use crate::{ graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; use crate::{ DistanceFog, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, ViewLightsUniformOffset, }; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, deferred::{ copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, }, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::BevyDefault as _; use bevy_render::{ extract_component::{ ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderContext, RenderDevice}, view::{ExtractedView, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderSystems, }; pub struct DeferredPbrLightingPlugin; 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", not(feature = "webgpu")))] _webgl2_padding_0: f32, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _webgl2_padding_1: f32, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _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", not(feature = "webgpu")))] _webgl2_padding_0: 0.0, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _webgl2_padding_1: 0.0, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _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", not(feature = "webgpu")))] _webgl2_padding_0: 0.0, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _webgl2_padding_1: 0.0, #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] _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); embedded_asset!(app, "deferred_lighting.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app .init_resource::>() .add_systems( Render, (prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),), ) .add_render_graph_node::>( Core3d, NodePbr::DeferredLightingPass, ) .add_render_graph_edges( Core3d, ( Node3d::StartMainPass, NodePbr::DeferredLightingPass, Node3d::MainOpaquePass, ), ); } fn finish(&self, app: &mut App) { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app.init_resource::(); } } #[derive(Default)] pub struct DeferredOpaquePass3dPbrLightingNode; impl ViewNode for DeferredOpaquePass3dPbrLightingNode { type ViewQuery = ( &'static ViewUniformOffset, &'static ViewLightsUniformOffset, &'static ViewFogUniformOffset, &'static ViewLightProbesUniformOffset, &'static ViewScreenSpaceReflectionsUniformOffset, &'static ViewEnvironmentMapUniformOffset, &'static MeshViewBindGroup, &'static ViewTarget, &'static DeferredLightingIdDepthTexture, &'static DeferredLightingPipeline, ); fn run( &self, _graph_context: &mut RenderGraphContext, render_context: &mut RenderContext, ( view_uniform_offset, view_lights_offset, view_fog_offset, view_light_probes_offset, view_ssr_offset, view_environment_map_offset, mesh_view_bind_group, target, deferred_lighting_id_depth_texture, 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_2 = render_context.render_device().create_bind_group( "deferred_lighting_layout_group_2", &deferred_lighting_layout.bind_group_layout_2, &BindGroupEntries::single(deferred_lighting_pass_id_binding), ); let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { label: Some("deferred_lighting_pass"), color_attachments: &[Some(target.get_color_attachment())], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { view: &deferred_lighting_id_depth_texture.texture.default_view, depth_ops: Some(Operations { load: LoadOp::Load, store: StoreOp::Discard, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, }); render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group( 0, &mesh_view_bind_group.main, &[ view_uniform_offset.offset, view_lights_offset.offset, view_fog_offset.offset, **view_light_probes_offset, **view_ssr_offset, **view_environment_map_offset, ], ); render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]); render_pass.set_bind_group(2, &bind_group_2, &[]); render_pass.draw(0..3, 0..1); Ok(()) } } #[derive(Resource)] pub struct DeferredLightingLayout { mesh_pipeline: MeshPipeline, bind_group_layout_2: BindGroupLayout, deferred_lighting_shader: Handle, } #[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(); // Let the shader code know that it's running in a deferred pipeline. shader_defs.push("DEFERRED_LIGHTING_PIPELINE".into()); #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, )); 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()); } if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) { shader_defs.push("IRRADIANCE_VOLUME".into()); } if key.contains(MeshPipelineKey::NORMAL_PREPASS) { shader_defs.push("NORMAL_PREPASS".into()); } if key.contains(MeshPipelineKey::DEPTH_PREPASS) { shader_defs.push("DEPTH_PREPASS".into()); } if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } if key.contains(MeshPipelineKey::SCREEN_SPACE_REFLECTIONS) { shader_defs.push("SCREEN_SPACE_REFLECTIONS".into()); } if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) { shader_defs.push("HAS_PREVIOUS_SKIN".into()); } if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) { shader_defs.push("HAS_PREVIOUS_MORPH".into()); } if key.contains(MeshPipelineKey::DISTANCE_FOG) { shader_defs.push("DISTANCE_FOG".into()); } // Always true, since we're in the deferred lighting pipeline shader_defs.push("DEFERRED_PREPASS".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_GAUSSIAN { shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into()); } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL { shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into()); } if self.mesh_pipeline.binding_arrays_are_usable { shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into()); shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into()); } #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); let layout = self.mesh_pipeline.get_view_layout(key.into()); RenderPipelineDescriptor { label: Some("deferred_lighting_pipeline".into()), layout: vec![ layout.main_layout.clone(), layout.binding_array_layout.clone(), self.bind_group_layout_2.clone(), ], vertex: VertexState { shader: self.deferred_lighting_shader.clone(), shader_defs: shader_defs.clone(), entry_point: "vertex".into(), buffers: Vec::new(), }, fragment: Some(FragmentState { shader: self.deferred_lighting_shader.clone(), 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![], zero_initialize_workgroup_memory: false, } } } impl FromWorld for DeferredLightingLayout { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); let layout = render_device.create_bind_group_layout( "deferred_lighting_layout", &BindGroupLayoutEntries::single( ShaderStages::VERTEX_FRAGMENT, uniform_buffer::(false), ), ); Self { mesh_pipeline: world.resource::().clone(), bind_group_layout_2: layout, deferred_lighting_shader: load_embedded_asset!(world, "deferred_lighting.wgsl"), } } } 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<&ShadowFilteringMethod>, ( Has, Has, Has, ), ( Has, Has, Has, Has, ), Has>, Has>, Has, )>, ) { for ( entity, view, tonemapping, dither, shadow_filter_method, (ssao, ssr, distance_fog), (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), has_environment_maps, has_irradiance_volumes, skip_deferred_lighting, ) in &views { // If there is no deferred prepass or we want to skip the deferred lighting pass, // remove the old pipeline if there was one. This handles the case in which a // view using deferred stops using it. if !deferred_prepass || skip_deferred_lighting { commands.entity(entity).remove::(); continue; } let mut view_key = MeshPipelineKey::from_hdr(view.hdr); if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; } if depth_prepass { view_key |= MeshPipelineKey::DEPTH_PREPASS; } if motion_vector_prepass { view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } // Always true, since we're in the deferred lighting pipeline view_key |= MeshPipelineKey::DEFERRED_PREPASS; 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 { view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; } if ssr { view_key |= MeshPipelineKey::SCREEN_SPACE_REFLECTIONS; } if distance_fog { view_key |= MeshPipelineKey::DISTANCE_FOG; } // We don't need to check to see whether the environment map is loaded // because [`gather_light_probes`] already checked that for us before // adding the [`RenderViewEnvironmentMaps`] component. if has_environment_maps { view_key |= MeshPipelineKey::ENVIRONMENT_MAP; } if has_irradiance_volumes { view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; } match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { ShadowFilteringMethod::Hardware2x2 => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; } ShadowFilteringMethod::Gaussian => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN; } ShadowFilteringMethod::Temporal => { view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL; } } let pipeline_id = pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key); commands .entity(entity) .insert(DeferredLightingPipeline { pipeline_id }); } } /// Component to skip running the deferred lighting pass in [`DeferredOpaquePass3dPbrLightingNode`] for a specific view. /// /// This works like [`crate::PbrPlugin::add_default_deferred_lighting_plugin`], but is per-view instead of global. /// /// Useful for cases where you want to generate a gbuffer, but skip the built-in deferred lighting pass /// to run your own custom lighting pass instead. /// /// Insert this component in the render world only. #[derive(Component, Clone, Copy, Default)] pub struct SkipDeferredLighting;