Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_post_process/src/effect_stack/mod.rs
9327 views
1
//! Miscellaneous built-in postprocessing effects.
2
//!
3
//! Includes:
4
//!
5
//! - Chromatic Aberration
6
//! - Vignette
7
8
mod chromatic_aberration;
9
mod vignette;
10
11
use bevy_color::ColorToComponents;
12
pub use chromatic_aberration::{ChromaticAberration, ChromaticAberrationUniform};
13
pub use vignette::{Vignette, VignetteUniform};
14
15
use crate::effect_stack::chromatic_aberration::{
16
DefaultChromaticAberrationLut, DEFAULT_CHROMATIC_ABERRATION_LUT_DATA,
17
};
18
19
use bevy_app::{App, Plugin};
20
use bevy_asset::{
21
embedded_asset, load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUsages,
22
};
23
use bevy_derive::{Deref, DerefMut};
24
use bevy_ecs::{
25
component::Component,
26
entity::Entity,
27
query::{AnyOf, Or, With},
28
resource::Resource,
29
schedule::IntoScheduleConfigs as _,
30
system::{Commands, Query, Res, ResMut},
31
};
32
use bevy_image::{BevyDefault, Image};
33
use bevy_render::{
34
diagnostic::RecordDiagnostics,
35
extract_component::ExtractComponentPlugin,
36
render_asset::RenderAssets,
37
render_resource::{
38
binding_types::{sampler, texture_2d, uniform_buffer},
39
BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
40
CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d,
41
FilterMode, FragmentState, MipmapFilterMode, Operations, PipelineCache,
42
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
43
SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedRenderPipeline,
44
SpecializedRenderPipelines, TextureDimension, TextureFormat, TextureSampleType,
45
},
46
renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
47
texture::GpuImage,
48
view::{ExtractedView, ViewTarget},
49
Render, RenderApp, RenderStartup, RenderSystems,
50
};
51
use bevy_shader::{load_shader_library, Shader};
52
use bevy_utils::prelude::default;
53
54
use crate::{bloom::bloom, dof::depth_of_field};
55
use bevy_core_pipeline::{
56
schedule::{Core2d, Core3d},
57
tonemapping::tonemapping,
58
FullscreenShader,
59
};
60
61
/// A plugin that implements a built-in postprocessing stack with some common
62
/// effects.
63
///
64
/// Includes:
65
///
66
/// - Chromatic Aberration
67
/// - Vignette
68
#[derive(Default)]
69
pub struct EffectStackPlugin;
70
71
/// GPU pipeline data for the built-in postprocessing stack.
72
///
73
/// This is stored in the render world.
74
#[derive(Resource)]
75
pub struct PostProcessingPipeline {
76
/// The layout of bind group 0, containing the source, LUT, and settings.
77
bind_group_layout: BindGroupLayoutDescriptor,
78
/// Specifies how to sample the source framebuffer texture.
79
source_sampler: Sampler,
80
/// Specifies how to sample the chromatic aberration gradient.
81
chromatic_aberration_lut_sampler: Sampler,
82
/// The asset handle for the fullscreen vertex shader.
83
fullscreen_shader: FullscreenShader,
84
/// The fragment shader asset handle.
85
fragment_shader: Handle<Shader>,
86
}
87
88
/// A key that uniquely identifies a built-in postprocessing pipeline.
89
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
90
pub struct PostProcessingPipelineKey {
91
/// The format of the source and destination textures.
92
texture_format: TextureFormat,
93
}
94
95
/// A component attached to cameras in the render world that stores the
96
/// specialized pipeline ID for the built-in postprocessing stack.
97
#[derive(Component, Deref, DerefMut)]
98
pub struct PostProcessingPipelineId(CachedRenderPipelineId);
99
100
/// A resource, part of the render world, that stores the uniform buffers for
101
/// post-processing effects.
102
///
103
/// This currently holds buffers for [`ChromaticAberrationUniform`] and
104
/// [`VignetteUniform`], allowing them to be uploaded to the GPU efficiently.
105
#[derive(Resource, Default)]
106
pub struct PostProcessingUniformBuffers {
107
chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,
108
vignette: DynamicUniformBuffer<VignetteUniform>,
109
}
110
111
/// A component, part of the render world, that stores the appropriate byte
112
/// offset within the [`PostProcessingUniformBuffers`] for the camera it's
113
/// attached to.
114
#[derive(Component)]
115
pub struct PostProcessingUniformBufferOffsets {
116
chromatic_aberration: u32,
117
vignette: u32,
118
}
119
120
impl Plugin for EffectStackPlugin {
121
fn build(&self, app: &mut App) {
122
load_shader_library!(app, "chromatic_aberration.wgsl");
123
load_shader_library!(app, "vignette.wgsl");
124
125
embedded_asset!(app, "post_process.wgsl");
126
127
// Load the default chromatic aberration LUT.
128
let mut assets = app.world_mut().resource_mut::<Assets<_>>();
129
let default_lut = assets.add(Image::new(
130
Extent3d {
131
width: 3,
132
height: 1,
133
depth_or_array_layers: 1,
134
},
135
TextureDimension::D2,
136
DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),
137
TextureFormat::Rgba8UnormSrgb,
138
RenderAssetUsages::RENDER_WORLD,
139
));
140
141
app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default())
142
.add_plugins(ExtractComponentPlugin::<Vignette>::default());
143
144
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
145
return;
146
};
147
148
render_app
149
.insert_resource(DefaultChromaticAberrationLut(default_lut))
150
.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()
151
.init_resource::<PostProcessingUniformBuffers>()
152
.add_systems(RenderStartup, init_post_processing_pipeline)
153
.add_systems(
154
Render,
155
(
156
prepare_post_processing_pipelines,
157
prepare_post_processing_uniforms,
158
)
159
.in_set(RenderSystems::Prepare),
160
)
161
.add_systems(
162
Core3d,
163
post_processing.after(depth_of_field).before(tonemapping),
164
)
165
.add_systems(Core2d, post_processing.after(bloom).before(tonemapping));
166
}
167
}
168
169
pub fn init_post_processing_pipeline(
170
mut commands: Commands,
171
render_device: Res<RenderDevice>,
172
fullscreen_shader: Res<FullscreenShader>,
173
asset_server: Res<AssetServer>,
174
) {
175
// Create our single bind group layout.
176
let bind_group_layout = BindGroupLayoutDescriptor::new(
177
"postprocessing bind group layout",
178
&BindGroupLayoutEntries::sequential(
179
ShaderStages::FRAGMENT,
180
(
181
// Common source:
182
texture_2d(TextureSampleType::Float { filterable: true }),
183
// Common source sampler:
184
sampler(SamplerBindingType::Filtering),
185
// Chromatic aberration LUT:
186
texture_2d(TextureSampleType::Float { filterable: true }),
187
// Chromatic aberration LUT sampler:
188
sampler(SamplerBindingType::Filtering),
189
// Chromatic aberration settings:
190
uniform_buffer::<ChromaticAberrationUniform>(true),
191
// Vignette settings:
192
uniform_buffer::<VignetteUniform>(true),
193
),
194
),
195
);
196
197
// Both source and chromatic aberration LUTs should be sampled
198
// bilinearly.
199
200
let source_sampler = render_device.create_sampler(&SamplerDescriptor {
201
mipmap_filter: MipmapFilterMode::Linear,
202
min_filter: FilterMode::Linear,
203
mag_filter: FilterMode::Linear,
204
..default()
205
});
206
207
let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {
208
mipmap_filter: MipmapFilterMode::Linear,
209
min_filter: FilterMode::Linear,
210
mag_filter: FilterMode::Linear,
211
..default()
212
});
213
214
commands.insert_resource(PostProcessingPipeline {
215
bind_group_layout,
216
source_sampler,
217
chromatic_aberration_lut_sampler,
218
fullscreen_shader: fullscreen_shader.clone(),
219
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "post_process.wgsl"),
220
});
221
}
222
223
impl SpecializedRenderPipeline for PostProcessingPipeline {
224
type Key = PostProcessingPipelineKey;
225
226
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
227
RenderPipelineDescriptor {
228
label: Some("postprocessing".into()),
229
layout: vec![self.bind_group_layout.clone()],
230
vertex: self.fullscreen_shader.to_vertex_state(),
231
fragment: Some(FragmentState {
232
shader: self.fragment_shader.clone(),
233
targets: vec![Some(ColorTargetState {
234
format: key.texture_format,
235
blend: None,
236
write_mask: ColorWrites::ALL,
237
})],
238
..default()
239
}),
240
..default()
241
}
242
}
243
}
244
245
pub(crate) fn post_processing(
246
view: ViewQuery<(
247
&ViewTarget,
248
&PostProcessingPipelineId,
249
AnyOf<(&ChromaticAberration, &Vignette)>,
250
&PostProcessingUniformBufferOffsets,
251
)>,
252
pipeline_cache: Res<PipelineCache>,
253
post_processing_pipeline: Res<PostProcessingPipeline>,
254
post_processing_uniform_buffers: Res<PostProcessingUniformBuffers>,
255
gpu_image_assets: Res<RenderAssets<GpuImage>>,
256
default_lut: Res<DefaultChromaticAberrationLut>,
257
mut ctx: RenderContext,
258
) {
259
let (view_target, pipeline_id, post_effects, post_processing_uniform_buffer_offsets) =
260
view.into_inner();
261
262
let (maybe_chromatic_aberration, maybe_vignette) = post_effects;
263
264
if maybe_chromatic_aberration.is_none() && maybe_vignette.is_none() {
265
return;
266
}
267
268
// We need a render pipeline to be prepared.
269
let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {
270
return;
271
};
272
273
// We need the chromatic aberration LUT to be present.
274
let Some(chromatic_aberration_lut) = gpu_image_assets.get(
275
maybe_chromatic_aberration
276
.and_then(|ca| ca.color_lut.as_ref())
277
.unwrap_or(&default_lut.0),
278
) else {
279
return;
280
};
281
282
// We need the postprocessing settings to be uploaded to the GPU.
283
let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers
284
.chromatic_aberration
285
.binding()
286
else {
287
return;
288
};
289
290
let Some(vignette_uniform_buffer_binding) = post_processing_uniform_buffers.vignette.binding()
291
else {
292
return;
293
};
294
295
// Use the [`PostProcessWrite`] infrastructure, since this is a full-screen pass.
296
let post_process = view_target.post_process_write();
297
298
let pass_descriptor = RenderPassDescriptor {
299
label: Some("postprocessing"),
300
color_attachments: &[Some(RenderPassColorAttachment {
301
view: post_process.destination,
302
depth_slice: None,
303
resolve_target: None,
304
ops: Operations::default(),
305
})],
306
depth_stencil_attachment: None,
307
timestamp_writes: None,
308
occlusion_query_set: None,
309
multiview_mask: None,
310
};
311
312
let bind_group = ctx.render_device().create_bind_group(
313
Some("postprocessing bind group"),
314
&pipeline_cache.get_bind_group_layout(&post_processing_pipeline.bind_group_layout),
315
&BindGroupEntries::sequential((
316
post_process.source,
317
&post_processing_pipeline.source_sampler,
318
&chromatic_aberration_lut.texture_view,
319
&post_processing_pipeline.chromatic_aberration_lut_sampler,
320
chromatic_aberration_uniform_buffer_binding,
321
vignette_uniform_buffer_binding,
322
)),
323
);
324
325
let diagnostics = ctx.diagnostic_recorder();
326
let diagnostics = diagnostics.as_deref();
327
328
let mut render_pass = ctx.begin_tracked_render_pass(pass_descriptor);
329
let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing");
330
331
render_pass.set_render_pipeline(pipeline);
332
render_pass.set_bind_group(
333
0,
334
&bind_group,
335
&[
336
post_processing_uniform_buffer_offsets.chromatic_aberration,
337
post_processing_uniform_buffer_offsets.vignette,
338
],
339
);
340
render_pass.draw(0..3, 0..1);
341
342
pass_span.end(&mut render_pass);
343
}
344
345
/// Specializes the built-in postprocessing pipeline for each applicable view.
346
pub fn prepare_post_processing_pipelines(
347
mut commands: Commands,
348
pipeline_cache: Res<PipelineCache>,
349
mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,
350
post_processing_pipeline: Res<PostProcessingPipeline>,
351
views: Query<(Entity, &ExtractedView), Or<(With<ChromaticAberration>, With<Vignette>)>>,
352
) {
353
for (entity, view) in views.iter() {
354
let pipeline_id = pipelines.specialize(
355
&pipeline_cache,
356
&post_processing_pipeline,
357
PostProcessingPipelineKey {
358
texture_format: if view.hdr {
359
ViewTarget::TEXTURE_FORMAT_HDR
360
} else {
361
TextureFormat::bevy_default()
362
},
363
},
364
);
365
366
commands
367
.entity(entity)
368
.insert(PostProcessingPipelineId(pipeline_id));
369
}
370
}
371
372
/// Gathers the built-in postprocessing settings for every view and uploads them
373
/// to the GPU.
374
pub fn prepare_post_processing_uniforms(
375
mut commands: Commands,
376
mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,
377
render_device: Res<RenderDevice>,
378
render_queue: Res<RenderQueue>,
379
mut views: Query<
380
(Entity, Option<&ChromaticAberration>, Option<&Vignette>),
381
Or<(With<ChromaticAberration>, With<Vignette>)>,
382
>,
383
) {
384
post_processing_uniform_buffers.chromatic_aberration.clear();
385
post_processing_uniform_buffers.vignette.clear();
386
387
// Gather up all the postprocessing settings.
388
for (view_entity, maybe_chromatic_aberration, maybe_vignette) in views.iter_mut() {
389
let chromatic_aberration_uniform_buffer_offset =
390
if let Some(chromatic_aberration) = maybe_chromatic_aberration {
391
post_processing_uniform_buffers.chromatic_aberration.push(
392
&ChromaticAberrationUniform {
393
intensity: chromatic_aberration.intensity,
394
max_samples: chromatic_aberration.max_samples,
395
unused_1: 0,
396
unused_2: 0,
397
},
398
)
399
} else {
400
post_processing_uniform_buffers
401
.chromatic_aberration
402
.push(&ChromaticAberrationUniform::default())
403
};
404
405
let vignette_uniform_buffer_offset = if let Some(vignette) = maybe_vignette {
406
post_processing_uniform_buffers
407
.vignette
408
.push(&VignetteUniform {
409
intensity: vignette.intensity,
410
radius: vignette.radius,
411
smoothness: vignette.smoothness,
412
roundness: vignette.roundness,
413
center: vignette.center,
414
edge_compensation: vignette.edge_compensation,
415
unused: 0,
416
color: vignette.color.to_srgba().to_vec4(),
417
})
418
} else {
419
post_processing_uniform_buffers
420
.vignette
421
.push(&VignetteUniform::default())
422
};
423
424
commands
425
.entity(view_entity)
426
.insert(PostProcessingUniformBufferOffsets {
427
chromatic_aberration: chromatic_aberration_uniform_buffer_offset,
428
vignette: vignette_uniform_buffer_offset,
429
});
430
}
431
432
// Upload to the GPU.
433
post_processing_uniform_buffers
434
.chromatic_aberration
435
.write_buffer(&render_device, &render_queue);
436
post_processing_uniform_buffers
437
.vignette
438
.write_buffer(&render_device, &render_queue);
439
}
440
441