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, DepthPrepass, MotionVectorPrepass, NormalPrepass}, 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( "deferred_lighting_layout_group_1", &deferred_lighting_layout.bind_group_layout_1, &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(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 { mesh_pipeline: MeshPipeline, 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(); // 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"))] 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()); } 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()); } // 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_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.mesh_pipeline.get_view_layout(key.into()).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 { mesh_pipeline: world.resource::().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>, ( Has, Has, Has, ), ), With, >, images: Res>, ) { for ( entity, view, tonemapping, dither, environment_map, shadow_filter_method, ssao, (normal_prepass, depth_prepass, motion_vector_prepass), ) in &views { 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.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 }); } }