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