bevy/crates/bevy_pbr/src/deferred/mod.rs
charlotte 🌸 96dcbc5f8c
Ugrade to wgpu version 25.0 (#19563)
# Objective

Upgrade to `wgpu` version `25.0`.

Depends on https://github.com/bevyengine/naga_oil/pull/121

## Solution

### Problem

The biggest issue we face upgrading is the following requirement:
> To facilitate this change, there was an additional validation rule put
in place: if there is a binding array in a bind group, you may not use
dynamic offset buffers or uniform buffers in that bind group. This
requirement comes from vulkan rules on UpdateAfterBind descriptors.

This is a major difficulty for us, as there are a number of binding
arrays that are used in the view bind group. Note, this requirement does
not affect merely uniform buffors that use dynamic offset but the use of
*any* uniform in a bind group that also has a binding array.

### Attempted fixes

The easiest fix would be to change uniforms to be storage buffers
whenever binding arrays are in use:
```wgsl
#ifdef BINDING_ARRAYS_ARE_USED
@group(0) @binding(0) var<uniform> view: View;
@group(0) @binding(1) var<uniform> lights: types::Lights;
#else
@group(0) @binding(0) var<storage> view: array<View>;
@group(0) @binding(1) var<storage> lights: array<types::Lights>;
#endif
```

This requires passing the view index to the shader so that we know where
to index into the buffer:

```wgsl
struct PushConstants {
    view_index: u32,
}

var<push_constant> push_constants: PushConstants;
```

Using push constants is no problem because binding arrays are only
usable on native anyway.

However, this greatly complicates the ability to access `view` in
shaders. For example:
```wgsl
#ifdef BINDING_ARRAYS_ARE_USED
mesh_view_bindings::view.view_from_world[0].z
#else
mesh_view_bindings::view[mesh_view_bindings::view_index].view_from_world[0].z
#endif
```

Using this approach would work but would have the effect of polluting
our shaders with ifdef spam basically *everywhere*.

Why not use a function? Unfortunately, the following is not valid wgsl
as it returns a binding directly from a function in the uniform path.

```wgsl
fn get_view() -> View {
#if BINDING_ARRAYS_ARE_USED
    let view_index = push_constants.view_index;
    let view = views[view_index];
#endif
    return view;
}
```

This also poses problems for things like lights where we want to return
a ptr to the light data. Returning ptrs from wgsl functions isn't
allowed even if both bindings were buffers.

The next attempt was to simply use indexed buffers everywhere, in both
the binding array and non binding array path. This would be viable if
push constants were available everywhere to pass the view index, but
unfortunately they are not available on webgpu. This means either
passing the view index in a storage buffer (not ideal for such a small
amount of state) or using push constants sometimes and uniform buffers
only on webgpu. However, this kind of conditional layout infects
absolutely everything.

Even if we were to accept just using storage buffer for the view index,
there's also the additional problem that some dynamic offsets aren't
actually per-view but per-use of a setting on a camera, which would
require passing that uniform data on *every* camera regardless of
whether that rendering feature is being used, which is also gross.

As such, although it's gross, the simplest solution just to bump binding
arrays into `@group(1)` and all other bindings up one bind group. This
should still bring us under the device limit of 4 for most users.

### Next steps / looking towards the future

I'd like to avoid needing split our view bind group into multiple parts.
In the future, if `wgpu` were to add `@builtin(draw_index)`, we could
build a list of draw state in gpu processing and avoid the need for any
kind of state change at all (see
https://github.com/gfx-rs/wgpu/issues/6823). This would also provide
significantly more flexibility to handle things like offsets into other
arrays that may not be per-view.

### Testing

Tested a number of examples, there are probably more that are still
broken.

---------

Co-authored-by: François Mockers <mockersf@gmail.com>
Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
2025-06-26 19:41:47 +00:00

571 lines
21 KiB
Rust

use crate::{
graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight,
MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion,
ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
};
use crate::{
DistanceFog, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
ViewLightsUniformOffset,
};
use bevy_app::prelude::*;
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
use bevy_core_pipeline::{
core_3d::graph::{Core3d, Node3d},
deferred::{
copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
},
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
tonemapping::{DebandDither, Tonemapping},
};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_image::BevyDefault as _;
use bevy_render::{
extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
},
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderContext, RenderDevice},
view::{ExtractedView, ViewTarget, ViewUniformOffset},
Render, RenderApp, RenderSystems,
};
pub struct DeferredPbrLightingPlugin;
pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1;
/// Component with a `depth_id` for specifying which corresponding materials should be rendered by this specific PBR deferred lighting pass.
///
/// Will be automatically added to entities with the [`DeferredPrepass`] component that don't already have a [`PbrDeferredLightingDepthId`].
#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
pub struct PbrDeferredLightingDepthId {
depth_id: u32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_0: f32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_1: f32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_2: f32,
}
impl PbrDeferredLightingDepthId {
pub fn new(value: u8) -> PbrDeferredLightingDepthId {
PbrDeferredLightingDepthId {
depth_id: value as u32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_0: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_1: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_2: 0.0,
}
}
pub fn set(&mut self, value: u8) {
self.depth_id = value as u32;
}
pub fn get(&self) -> u8 {
self.depth_id as u8
}
}
impl Default for PbrDeferredLightingDepthId {
fn default() -> Self {
PbrDeferredLightingDepthId {
depth_id: DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID as u32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_0: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_1: 0.0,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding_2: 0.0,
}
}
}
impl Plugin for DeferredPbrLightingPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
ExtractComponentPlugin::<PbrDeferredLightingDepthId>::default(),
UniformComponentPlugin::<PbrDeferredLightingDepthId>::default(),
))
.add_systems(PostUpdate, insert_deferred_lighting_pass_id_component);
embedded_asset!(app, "deferred_lighting.wgsl");
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<SpecializedRenderPipelines<DeferredLightingLayout>>()
.add_systems(
Render,
(prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),),
)
.add_render_graph_node::<ViewNodeRunner<DeferredOpaquePass3dPbrLightingNode>>(
Core3d,
NodePbr::DeferredLightingPass,
)
.add_render_graph_edges(
Core3d,
(
Node3d::StartMainPass,
NodePbr::DeferredLightingPass,
Node3d::MainOpaquePass,
),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<DeferredLightingLayout>();
}
}
#[derive(Default)]
pub struct DeferredOpaquePass3dPbrLightingNode;
impl ViewNode for DeferredOpaquePass3dPbrLightingNode {
type ViewQuery = (
&'static ViewUniformOffset,
&'static ViewLightsUniformOffset,
&'static ViewFogUniformOffset,
&'static ViewLightProbesUniformOffset,
&'static ViewScreenSpaceReflectionsUniformOffset,
&'static ViewEnvironmentMapUniformOffset,
&'static MeshViewBindGroup,
&'static ViewTarget,
&'static DeferredLightingIdDepthTexture,
&'static DeferredLightingPipeline,
);
fn run(
&self,
_graph_context: &mut RenderGraphContext,
render_context: &mut RenderContext,
(
view_uniform_offset,
view_lights_offset,
view_fog_offset,
view_light_probes_offset,
view_ssr_offset,
view_environment_map_offset,
mesh_view_bind_group,
target,
deferred_lighting_id_depth_texture,
deferred_lighting_pipeline,
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let pipeline_cache = world.resource::<PipelineCache>();
let deferred_lighting_layout = world.resource::<DeferredLightingLayout>();
let Some(pipeline) =
pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id)
else {
return Ok(());
};
let deferred_lighting_pass_id =
world.resource::<ComponentUniforms<PbrDeferredLightingDepthId>>();
let Some(deferred_lighting_pass_id_binding) =
deferred_lighting_pass_id.uniforms().binding()
else {
return Ok(());
};
let bind_group_2 = render_context.render_device().create_bind_group(
"deferred_lighting_layout_group_2",
&deferred_lighting_layout.bind_group_layout_2,
&BindGroupEntries::single(deferred_lighting_pass_id_binding),
);
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("deferred_lighting_pass"),
color_attachments: &[Some(target.get_color_attachment())],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &deferred_lighting_id_depth_texture.texture.default_view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: StoreOp::Discard,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_render_pipeline(pipeline);
render_pass.set_bind_group(
0,
&mesh_view_bind_group.main,
&[
view_uniform_offset.offset,
view_lights_offset.offset,
view_fog_offset.offset,
**view_light_probes_offset,
**view_ssr_offset,
**view_environment_map_offset,
],
);
render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]);
render_pass.set_bind_group(2, &bind_group_2, &[]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}
#[derive(Resource)]
pub struct DeferredLightingLayout {
mesh_pipeline: MeshPipeline,
bind_group_layout_2: BindGroupLayout,
deferred_lighting_shader: Handle<Shader>,
}
#[derive(Component)]
pub struct DeferredLightingPipeline {
pub pipeline_id: CachedRenderPipelineId,
}
impl SpecializedRenderPipeline for DeferredLightingLayout {
type Key = MeshPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = Vec::new();
// Let the shader code know that it's running in a deferred pipeline.
shader_defs.push("DEFERRED_LIGHTING_PIPELINE".into());
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("WEBGL2".into());
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
shader_defs.push("TONEMAP_IN_SHADER".into());
shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
));
shader_defs.push(ShaderDefVal::UInt(
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
));
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
if method == MeshPipelineKey::TONEMAP_METHOD_NONE {
shader_defs.push("TONEMAP_METHOD_NONE".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD {
shader_defs.push("TONEMAP_METHOD_REINHARD".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {
shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED {
shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_AGX {
shader_defs.push("TONEMAP_METHOD_AGX".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM {
shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
} else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
}
// Debanding is tied to tonemapping in the shader, cannot run without it.
if key.contains(MeshPipelineKey::DEBAND_DITHER) {
shader_defs.push("DEBAND_DITHER".into());
}
}
if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into());
}
if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
shader_defs.push("ENVIRONMENT_MAP".into());
}
if key.contains(MeshPipelineKey::IRRADIANCE_VOLUME) {
shader_defs.push("IRRADIANCE_VOLUME".into());
}
if key.contains(MeshPipelineKey::NORMAL_PREPASS) {
shader_defs.push("NORMAL_PREPASS".into());
}
if key.contains(MeshPipelineKey::DEPTH_PREPASS) {
shader_defs.push("DEPTH_PREPASS".into());
}
if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
shader_defs.push("MOTION_VECTOR_PREPASS".into());
}
if key.contains(MeshPipelineKey::SCREEN_SPACE_REFLECTIONS) {
shader_defs.push("SCREEN_SPACE_REFLECTIONS".into());
}
if key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
shader_defs.push("HAS_PREVIOUS_SKIN".into());
}
if key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
shader_defs.push("HAS_PREVIOUS_MORPH".into());
}
if key.contains(MeshPipelineKey::DISTANCE_FOG) {
shader_defs.push("DISTANCE_FOG".into());
}
// Always true, since we're in the deferred lighting pipeline
shader_defs.push("DEFERRED_PREPASS".into());
let shadow_filter_method =
key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
}
if self.mesh_pipeline.binding_arrays_are_usable {
shader_defs.push("MULTIPLE_LIGHT_PROBES_IN_ARRAY".into());
shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
}
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
let layout = self.mesh_pipeline.get_view_layout(key.into());
RenderPipelineDescriptor {
label: Some("deferred_lighting_pipeline".into()),
layout: vec![
layout.main_layout.clone(),
layout.binding_array_layout.clone(),
self.bind_group_layout_2.clone(),
],
vertex: VertexState {
shader: self.deferred_lighting_shader.clone(),
shader_defs: shader_defs.clone(),
entry_point: "vertex".into(),
buffers: Vec::new(),
},
fragment: Some(FragmentState {
shader: self.deferred_lighting_shader.clone(),
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: if key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Equal,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
impl FromWorld for DeferredLightingLayout {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let layout = render_device.create_bind_group_layout(
"deferred_lighting_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<PbrDeferredLightingDepthId>(false),
),
);
Self {
mesh_pipeline: world.resource::<MeshPipeline>().clone(),
bind_group_layout_2: layout,
deferred_lighting_shader: load_embedded_asset!(world, "deferred_lighting.wgsl"),
}
}
}
pub fn insert_deferred_lighting_pass_id_component(
mut commands: Commands,
views: Query<Entity, (With<DeferredPrepass>, Without<PbrDeferredLightingDepthId>)>,
) {
for entity in views.iter() {
commands
.entity(entity)
.insert(PbrDeferredLightingDepthId::default());
}
}
pub fn prepare_deferred_lighting_pipelines(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<DeferredLightingLayout>>,
deferred_lighting_layout: Res<DeferredLightingLayout>,
views: Query<(
Entity,
&ExtractedView,
Option<&Tonemapping>,
Option<&DebandDither>,
Option<&ShadowFilteringMethod>,
(
Has<ScreenSpaceAmbientOcclusion>,
Has<ScreenSpaceReflectionsUniform>,
Has<DistanceFog>,
),
(
Has<NormalPrepass>,
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
Has<DeferredPrepass>,
),
Has<RenderViewLightProbes<EnvironmentMapLight>>,
Has<RenderViewLightProbes<IrradianceVolume>>,
Has<SkipDeferredLighting>,
)>,
) {
for (
entity,
view,
tonemapping,
dither,
shadow_filter_method,
(ssao, ssr, distance_fog),
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
has_environment_maps,
has_irradiance_volumes,
skip_deferred_lighting,
) in &views
{
// If there is no deferred prepass or we want to skip the deferred lighting pass,
// remove the old pipeline if there was one. This handles the case in which a
// view using deferred stops using it.
if !deferred_prepass || skip_deferred_lighting {
commands.entity(entity).remove::<DeferredLightingPipeline>();
continue;
}
let mut view_key = MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if depth_prepass {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
}
if motion_vector_prepass {
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
// Always true, since we're in the deferred lighting pipeline
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
if !view.hdr {
if let Some(tonemapping) = tonemapping {
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
view_key |= match tonemapping {
Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE,
Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD,
Tonemapping::ReinhardLuminance => {
MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
}
Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED,
Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX,
Tonemapping::SomewhatBoringDisplayTransform => {
MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
}
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
};
}
if let Some(DebandDither::Enabled) = dither {
view_key |= MeshPipelineKey::DEBAND_DITHER;
}
}
if ssao {
view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
}
if ssr {
view_key |= MeshPipelineKey::SCREEN_SPACE_REFLECTIONS;
}
if distance_fog {
view_key |= MeshPipelineKey::DISTANCE_FOG;
}
// We don't need to check to see whether the environment map is loaded
// because [`gather_light_probes`] already checked that for us before
// adding the [`RenderViewEnvironmentMaps`] component.
if has_environment_maps {
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
}
if has_irradiance_volumes {
view_key |= MeshPipelineKey::IRRADIANCE_VOLUME;
}
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
ShadowFilteringMethod::Hardware2x2 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
}
ShadowFilteringMethod::Gaussian => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
}
ShadowFilteringMethod::Temporal => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
}
}
let pipeline_id =
pipelines.specialize(&pipeline_cache, &deferred_lighting_layout, view_key);
commands
.entity(entity)
.insert(DeferredLightingPipeline { pipeline_id });
}
}
/// Component to skip running the deferred lighting pass in [`DeferredOpaquePass3dPbrLightingNode`] for a specific view.
///
/// This works like [`crate::PbrPlugin::add_default_deferred_lighting_plugin`], but is per-view instead of global.
///
/// Useful for cases where you want to generate a gbuffer, but skip the built-in deferred lighting pass
/// to run your own custom lighting pass instead.
///
/// Insert this component in the render world only.
#[derive(Component, Clone, Copy, Default)]
pub struct SkipDeferredLighting;