Skip to main content

bevy_pbr/deferred/
mod.rs

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