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