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