Skip to main content

bevy_pbr/deferred/
mod.rs

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