bevy_pbr/deferred/
mod.rs

1use crate::{
2    graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight,
3    MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion,
4    ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
5    ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
6    TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
7};
8use crate::{
9    DistanceFog, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
10    ViewLightsUniformOffset,
11};
12use bevy_app::prelude::*;
13use bevy_asset::{load_internal_asset, weak_handle, Handle};
14use bevy_core_pipeline::{
15    core_3d::graph::{Core3d, Node3d},
16    deferred::{
17        copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
18    },
19    prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
20    tonemapping::{DebandDither, Tonemapping},
21};
22use bevy_ecs::{prelude::*, query::QueryItem};
23use bevy_image::BevyDefault as _;
24use bevy_render::{
25    extract_component::{
26        ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
27    },
28    render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
29    render_resource::{binding_types::uniform_buffer, *},
30    renderer::{RenderContext, RenderDevice},
31    view::{ExtractedView, ViewTarget, ViewUniformOffset},
32    Render, RenderApp, RenderSet,
33};
34
35pub struct DeferredPbrLightingPlugin;
36
37pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle<Shader> =
38    weak_handle!("f4295279-8890-4748-b654-ca4d2183df1c");
39
40pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1;
41
42/// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass.
43///
44/// Will be automatically added to entities with the [`DeferredPrepass`] component that don't already have a [`PbrDeferredLightingDepthId`].
45#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
46pub struct PbrDeferredLightingDepthId {
47    depth_id: u32,
48
49    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
50    _webgl2_padding_0: f32,
51    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
52    _webgl2_padding_1: f32,
53    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
54    _webgl2_padding_2: f32,
55}
56
57impl PbrDeferredLightingDepthId {
58    pub fn new(value: u8) -> PbrDeferredLightingDepthId {
59        PbrDeferredLightingDepthId {
60            depth_id: value as u32,
61
62            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
63            _webgl2_padding_0: 0.0,
64            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
65            _webgl2_padding_1: 0.0,
66            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
67            _webgl2_padding_2: 0.0,
68        }
69    }
70
71    pub fn set(&mut self, value: u8) {
72        self.depth_id = value as u32;
73    }
74
75    pub fn get(&self) -> u8 {
76        self.depth_id as u8
77    }
78}
79
80impl Default for PbrDeferredLightingDepthId {
81    fn default() -> Self {
82        PbrDeferredLightingDepthId {
83            depth_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID as u32,
84
85            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
86            _webgl2_padding_0: 0.0,
87            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
88            _webgl2_padding_1: 0.0,
89            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
90            _webgl2_padding_2: 0.0,
91        }
92    }
93}
94
95impl Plugin for DeferredPbrLightingPlugin {
96    fn build(&self, app: &mut App) {
97        app.add_plugins((
98            ExtractComponentPlugin::<PbrDeferredLightingDepthId>::default(),
99            UniformComponentPlugin::<PbrDeferredLightingDepthId>::default(),
100        ))
101        .add_systems(PostUpdate, insert_deferred_lighting_pass_id_component);
102
103        load_internal_asset!(
104            app,
105            DEFERRED_LIGHTING_SHADER_HANDLE,
106            "deferred_lighting.wgsl",
107            Shader::from_wgsl
108        );
109
110        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
111            return;
112        };
113
114        render_app
115            .init_resource::<SpecializedRenderPipelines<DeferredLightingLayout>>()
116            .add_systems(
117                Render,
118                (prepare_deferred_lighting_pipelines.in_set(RenderSet::Prepare),),
119            )
120            .add_render_graph_node::<ViewNodeRunner<DeferredOpaquePass3dPbrLightingNode>>(
121                Core3d,
122                NodePbr::DeferredLightingPass,
123            )
124            .add_render_graph_edges(
125                Core3d,
126                (
127                    Node3d::StartMainPass,
128                    NodePbr::DeferredLightingPass,
129                    Node3d::MainOpaquePass,
130                ),
131            );
132    }
133
134    fn finish(&self, app: &mut App) {
135        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
136            return;
137        };
138
139        render_app.init_resource::<DeferredLightingLayout>();
140    }
141}
142
143#[derive(Default)]
144pub struct DeferredOpaquePass3dPbrLightingNode;
145
146impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
147    type ViewQuery = (
148        &'static ViewUniformOffset,
149        &'static ViewLightsUniformOffset,
150        &'static ViewFogUniformOffset,
151        &'static ViewLightProbesUniformOffset,
152        &'static ViewScreenSpaceReflectionsUniformOffset,
153        &'static ViewEnvironmentMapUniformOffset,
154        &'static MeshViewBindGroup,
155        &'static ViewTarget,
156        &'static DeferredLightingIdDepthTexture,
157        &'static DeferredLightingPipeline,
158    );
159
160    fn run(
161        &self,
162        _graph_context: &mut RenderGraphContext,
163        render_context: &mut RenderContext,
164        (
165            view_uniform_offset,
166            view_lights_offset,
167            view_fog_offset,
168            view_light_probes_offset,
169            view_ssr_offset,
170            view_environment_map_offset,
171            mesh_view_bind_group,
172            target,
173            deferred_lighting_id_depth_texture,
174            deferred_lighting_pipeline,
175        ): QueryItem<Self::ViewQuery>,
176        world: &World,
177    ) -> Result<(), NodeRunError> {
178        let pipeline_cache = world.resource::<PipelineCache>();
179        let deferred_lighting_layout = world.resource::<DeferredLightingLayout>();
180
181        let Some(pipeline) =
182            pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id)
183        else {
184            return Ok(());
185        };
186
187        let deferred_lighting_pass_id =
188            world.resource::<ComponentUniforms<PbrDeferredLightingDepthId>>();
189        let Some(deferred_lighting_pass_id_binding) =
190            deferred_lighting_pass_id.uniforms().binding()
191        else {
192            return Ok(());
193        };
194
195        let bind_group_1 = render_context.render_device().create_bind_group(
196            "deferred_lighting_layout_group_1",
197            &deferred_lighting_layout.bind_group_layout_1,
198            &BindGroupEntries::single(deferred_lighting_pass_id_binding),
199        );
200
201        let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
202            label: Some("deferred_lighting_pass"),
203            color_attachments: &[Some(target.get_color_attachment())],
204            depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
205                view: &deferred_lighting_id_depth_texture.texture.default_view,
206                depth_ops: Some(Operations {
207                    load: LoadOp::Load,
208                    store: StoreOp::Discard,
209                }),
210                stencil_ops: None,
211            }),
212            timestamp_writes: None,
213            occlusion_query_set: None,
214        });
215
216        render_pass.set_render_pipeline(pipeline);
217        render_pass.set_bind_group(
218            0,
219            &mesh_view_bind_group.value,
220            &[
221                view_uniform_offset.offset,
222                view_lights_offset.offset,
223                view_fog_offset.offset,
224                **view_light_probes_offset,
225                **view_ssr_offset,
226                **view_environment_map_offset,
227            ],
228        );
229        render_pass.set_bind_group(1, &bind_group_1, &[]);
230        render_pass.draw(0..3, 0..1);
231
232        Ok(())
233    }
234}
235
236#[derive(Resource)]
237pub struct DeferredLightingLayout {
238    mesh_pipeline: MeshPipeline,
239    bind_group_layout_1: BindGroupLayout,
240}
241
242#[derive(Component)]
243pub struct DeferredLightingPipeline {
244    pub pipeline_id: CachedRenderPipelineId,
245}
246
247impl SpecializedRenderPipeline for DeferredLightingLayout {
248    type Key = MeshPipelineKey;
249
250    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
251        let mut shader_defs = Vec::new();
252
253        // Let the shader code know that it's running in a deferred pipeline.
254        shader_defs.push("DEFERRED_LIGHTING_PIPELINE".into());
255
256        #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
257        shader_defs.push("WEBGL2".into());
258
259        if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
260            shader_defs.push("TONEMAP_IN_SHADER".into());
261            shader_defs.push(ShaderDefVal::UInt(
262                "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
263                TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
264            ));
265            shader_defs.push(ShaderDefVal::UInt(
266                "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
267                TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
268            ));
269
270            let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
271
272            if method == MeshPipelineKey::TONEMAP_METHOD_NONE {
273                shader_defs.push("TONEMAP_METHOD_NONE".into());
274            } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD {
275                shader_defs.push("TONEMAP_METHOD_REINHARD".into());
276            } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {
277                shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
278            } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED {
279                shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
280            } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX {
281                shader_defs.push("TONEMAP_METHOD_AGX".into());
282            } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM {
283                shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
284            } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {
285                shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
286            } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
287                shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
288            }
289
290            // Debanding is tied to tonemapping in the shader, cannot run without it.
291            if key.contains(MeshPipelineKey::DEBAND_DITHER) {
292                shader_defs.push("DEBAND_DITHER".into());
293            }
294        }
295
296        if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
297            shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into());
298        }
299
300        if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
301            shader_defs.push("ENVIRONMENT_MAP".into());
302        }
303
304        if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) {
305            shader_defs.push("IRRADIANCE_VOLUME".into());
306        }
307
308        if key.contains(MeshPipelineKey::NORMAL_PREPASS) {
309            shader_defs.push("NORMAL_PREPASS".into());
310        }
311
312        if key.contains(MeshPipelineKey::DEPTH_PREPASS) {
313            shader_defs.push("DEPTH_PREPASS".into());
314        }
315
316        if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
317            shader_defs.push("MOTION_VECTOR_PREPASS".into());
318        }
319
320        if key.contains(MeshPipelineKey::SCREEN_SPACE_REFLECTIONS) {
321            shader_defs.push("SCREEN_SPACE_REFLECTIONS".into());
322        }
323
324        if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
325            shader_defs.push("HAS_PREVIOUS_SKIN".into());
326        }
327
328        if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
329            shader_defs.push("HAS_PREVIOUS_MORPH".into());
330        }
331
332        if key.contains(MeshPipelineKey::DISTANCE_FOG) {
333            shader_defs.push("DISTANCE_FOG".into());
334        }
335
336        // Always true, since we're in the deferred lighting pipeline
337        shader_defs.push("DEFERRED_PREPASS".into());
338
339        let shadow_filter_method =
340            key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
341        if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
342            shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
343        } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
344            shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
345        } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
346            shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
347        }
348        if self.mesh_pipeline.binding_arrays_are_usable {
349            shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
350            shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
351        }
352
353        #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
354        shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
355
356        RenderPipelineDescriptor {
357            label: Some("deferred_lighting_pipeline".into()),
358            layout: vec![
359                self.mesh_pipeline.get_view_layout(key.into()).clone(),
360                self.bind_group_layout_1.clone(),
361            ],
362            vertex: VertexState {
363                shader: DEFERRED_LIGHTING_SHADER_HANDLE,
364                shader_defs: shader_defs.clone(),
365                entry_point: "vertex".into(),
366                buffers: Vec::new(),
367            },
368            fragment: Some(FragmentState {
369                shader: DEFERRED_LIGHTING_SHADER_HANDLE,
370                shader_defs,
371                entry_point: "fragment".into(),
372                targets: vec![Some(ColorTargetState {
373                    format: if key.contains(MeshPipelineKey::HDR) {
374                        ViewTarget::TEXTURE_FORMAT_HDR
375                    } else {
376                        TextureFormat::bevy_default()
377                    },
378                    blend: None,
379                    write_mask: ColorWrites::ALL,
380                })],
381            }),
382            primitive: PrimitiveState::default(),
383            depth_stencil: Some(DepthStencilState {
384                format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
385                depth_write_enabled: false,
386                depth_compare: CompareFunction::Equal,
387                stencil: StencilState {
388                    front: StencilFaceState::IGNORE,
389                    back: StencilFaceState::IGNORE,
390                    read_mask: 0,
391                    write_mask: 0,
392                },
393                bias: DepthBiasState {
394                    constant: 0,
395                    slope_scale: 0.0,
396                    clamp: 0.0,
397                },
398            }),
399            multisample: MultisampleState::default(),
400            push_constant_ranges: vec![],
401            zero_initialize_workgroup_memory: false,
402        }
403    }
404}
405
406impl FromWorld for DeferredLightingLayout {
407    fn from_world(world: &mut World) -> Self {
408        let render_device = world.resource::<RenderDevice>();
409        let layout = render_device.create_bind_group_layout(
410            "deferred_lighting_layout",
411            &BindGroupLayoutEntries::single(
412                ShaderStages::VERTEX_FRAGMENT,
413                uniform_buffer::<PbrDeferredLightingDepthId>(false),
414            ),
415        );
416        Self {
417            mesh_pipeline: world.resource::<MeshPipeline>().clone(),
418            bind_group_layout_1: layout,
419        }
420    }
421}
422
423pub fn insert_deferred_lighting_pass_id_component(
424    mut commands: Commands,
425    views: Query<Entity, (With<DeferredPrepass>, Without<PbrDeferredLightingDepthId>)>,
426) {
427    for entity in views.iter() {
428        commands
429            .entity(entity)
430            .insert(PbrDeferredLightingDepthId::default());
431    }
432}
433
434pub fn prepare_deferred_lighting_pipelines(
435    mut commands: Commands,
436    pipeline_cache: Res<PipelineCache>,
437    mut pipelines: ResMut<SpecializedRenderPipelines<DeferredLightingLayout>>,
438    deferred_lighting_layout: Res<DeferredLightingLayout>,
439    views: Query<(
440        Entity,
441        &ExtractedView,
442        Option<&Tonemapping>,
443        Option<&DebandDither>,
444        Option<&ShadowFilteringMethod>,
445        (
446            Has<ScreenSpaceAmbientOcclusion>,
447            Has<ScreenSpaceReflectionsUniform>,
448            Has<DistanceFog>,
449        ),
450        (
451            Has<NormalPrepass>,
452            Has<DepthPrepass>,
453            Has<MotionVectorPrepass>,
454            Has<DeferredPrepass>,
455        ),
456        Has<RenderViewLightProbes<EnvironmentMapLight>>,
457        Has<RenderViewLightProbes<IrradianceVolume>>,
458    )>,
459) {
460    for (
461        entity,
462        view,
463        tonemapping,
464        dither,
465        shadow_filter_method,
466        (ssao, ssr, distance_fog),
467        (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
468        has_environment_maps,
469        has_irradiance_volumes,
470    ) in &views
471    {
472        // If there is no deferred prepass, remove the old pipeline if there was
473        // one. This handles the case in which a view using deferred stops using
474        // it.
475        if !deferred_prepass {
476            commands.entity(entity).remove::<DeferredLightingPipeline>();
477            continue;
478        }
479
480        let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
481
482        if normal_prepass {
483            view_key |= MeshPipelineKey::NORMAL_PREPASS;
484        }
485
486        if depth_prepass {
487            view_key |= MeshPipelineKey::DEPTH_PREPASS;
488        }
489
490        if motion_vector_prepass {
491            view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
492        }
493
494        // Always true, since we're in the deferred lighting pipeline
495        view_key |= MeshPipelineKey::DEFERRED_PREPASS;
496
497        if !view.hdr {
498            if let Some(tonemapping) = tonemapping {
499                view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
500                view_key |= match tonemapping {
501                    Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
502                    Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
503                    Tonemapping::ReinhardLuminance => {
504                        MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
505                    }
506                    Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
507                    Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
508                    Tonemapping::SomewhatBoringDisplayTransform => {
509                        MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
510                    }
511                    Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
512                    Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
513                };
514            }
515            if let Some(DebandDither::Enabled) = dither {
516                view_key |= MeshPipelineKey::DEBAND_DITHER;
517            }
518        }
519
520        if ssao {
521            view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
522        }
523        if ssr {
524            view_key |= MeshPipelineKey::SCREEN_SPACE_REFLECTIONS;
525        }
526        if distance_fog {
527            view_key |= MeshPipelineKey::DISTANCE_FOG;
528        }
529
530        // We don't need to check to see whether the environment map is loaded
531        // because [`gather_light_probes`] already checked that for us before
532        // adding the [`RenderViewEnvironmentMaps`] component.
533        if has_environment_maps {
534            view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
535        }
536
537        if has_irradiance_volumes {
538            view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
539        }
540
541        match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
542            ShadowFilteringMethod::Hardware2x2 => {
543                view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
544            }
545            ShadowFilteringMethod::Gaussian => {
546                view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
547            }
548            ShadowFilteringMethod::Temporal => {
549                view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
550            }
551        }
552
553        let pipeline_id =
554            pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key);
555
556        commands
557            .entity(entity)
558            .insert(DeferredLightingPipeline { pipeline_id });
559    }
560}