bevy_pbr/deferred/
mod.rs

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