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#[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 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 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 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 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 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}