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
6596 views
1
//! Miscellaneous built-in postprocessing effects.
2
//!
3
//! Currently, this consists only of chromatic aberration.
4
5
use bevy_app::{App, Plugin};
6
use bevy_asset::{
7
embedded_asset, load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUsages,
8
};
9
use bevy_camera::Camera;
10
use bevy_derive::{Deref, DerefMut};
11
use bevy_ecs::{
12
component::Component,
13
entity::Entity,
14
query::{QueryItem, With},
15
reflect::ReflectComponent,
16
resource::Resource,
17
schedule::IntoScheduleConfigs as _,
18
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
19
world::World,
20
};
21
use bevy_image::{BevyDefault, Image};
22
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
23
use bevy_render::{
24
diagnostic::RecordDiagnostics,
25
extract_component::{ExtractComponent, ExtractComponentPlugin},
26
render_asset::RenderAssets,
27
render_graph::{
28
NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,
29
},
30
render_resource::{
31
binding_types::{sampler, texture_2d, uniform_buffer},
32
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
33
ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, FilterMode, FragmentState,
34
Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
35
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
36
ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDimension,
37
TextureFormat, TextureSampleType,
38
},
39
renderer::{RenderContext, RenderDevice, RenderQueue},
40
texture::GpuImage,
41
view::{ExtractedView, ViewTarget},
42
Render, RenderApp, RenderStartup, RenderSystems,
43
};
44
use bevy_shader::{load_shader_library, Shader};
45
use bevy_utils::prelude::default;
46
47
use bevy_core_pipeline::{
48
core_2d::graph::{Core2d, Node2d},
49
core_3d::graph::{Core3d, Node3d},
50
FullscreenShader,
51
};
52
53
/// The default chromatic aberration intensity amount, in a fraction of the
54
/// window size.
55
const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02;
56
57
/// The default maximum number of samples for chromatic aberration.
58
const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8;
59
60
/// The raw RGBA data for the default chromatic aberration gradient.
61
///
62
/// This consists of one red pixel, one green pixel, and one blue pixel, in that
63
/// order.
64
static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] =
65
[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];
66
67
#[derive(Resource)]
68
struct DefaultChromaticAberrationLut(Handle<Image>);
69
70
/// A plugin that implements a built-in postprocessing stack with some common
71
/// effects.
72
///
73
/// Currently, this only consists of chromatic aberration.
74
#[derive(Default)]
75
pub struct EffectStackPlugin;
76
77
/// Adds colored fringes to the edges of objects in the scene.
78
///
79
/// [Chromatic aberration] simulates the effect when lenses fail to focus all
80
/// colors of light toward a single point. It causes rainbow-colored streaks to
81
/// appear, which are especially apparent on the edges of objects. Chromatic
82
/// aberration is commonly used for collision effects, especially in horror
83
/// games.
84
///
85
/// Bevy's implementation is based on that of *Inside* ([Gjøl & Svendsen 2016]).
86
/// It's based on a customizable lookup texture, which allows for changing the
87
/// color pattern. By default, the color pattern is simply a 3×1 pixel texture
88
/// consisting of red, green, and blue, in that order, but you can change it to
89
/// any image in order to achieve different effects.
90
///
91
/// [Chromatic aberration]: https://en.wikipedia.org/wiki/Chromatic_aberration
92
///
93
/// [Gjøl & Svendsen 2016]: https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf
94
#[derive(Reflect, Component, Clone)]
95
#[reflect(Component, Default, Clone)]
96
pub struct ChromaticAberration {
97
/// The lookup texture that determines the color gradient.
98
///
99
/// By default (if None), this is a 3×1 texel texture consisting of one red
100
/// pixel, one green pixel, and one blue texel, in that order. This
101
/// recreates the most typical chromatic aberration pattern. However, you
102
/// can change it to achieve different artistic effects.
103
///
104
/// The texture is always sampled in its vertical center, so it should
105
/// ordinarily have a height of 1 texel.
106
pub color_lut: Option<Handle<Image>>,
107
108
/// The size of the streaks around the edges of objects, as a fraction of
109
/// the window size.
110
///
111
/// The default value is 0.02.
112
pub intensity: f32,
113
114
/// A cap on the number of texture samples that will be performed.
115
///
116
/// Higher values result in smoother-looking streaks but are slower.
117
///
118
/// The default value is 8.
119
pub max_samples: u32,
120
}
121
122
/// GPU pipeline data for the built-in postprocessing stack.
123
///
124
/// This is stored in the render world.
125
#[derive(Resource)]
126
pub struct PostProcessingPipeline {
127
/// The layout of bind group 0, containing the source, LUT, and settings.
128
bind_group_layout: BindGroupLayout,
129
/// Specifies how to sample the source framebuffer texture.
130
source_sampler: Sampler,
131
/// Specifies how to sample the chromatic aberration gradient.
132
chromatic_aberration_lut_sampler: Sampler,
133
/// The asset handle for the fullscreen vertex shader.
134
fullscreen_shader: FullscreenShader,
135
/// The fragment shader asset handle.
136
fragment_shader: Handle<Shader>,
137
}
138
139
/// A key that uniquely identifies a built-in postprocessing pipeline.
140
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
141
pub struct PostProcessingPipelineKey {
142
/// The format of the source and destination textures.
143
texture_format: TextureFormat,
144
}
145
146
/// A component attached to cameras in the render world that stores the
147
/// specialized pipeline ID for the built-in postprocessing stack.
148
#[derive(Component, Deref, DerefMut)]
149
pub struct PostProcessingPipelineId(CachedRenderPipelineId);
150
151
/// The on-GPU version of the [`ChromaticAberration`] settings.
152
///
153
/// See the documentation for [`ChromaticAberration`] for more information on
154
/// each of these fields.
155
#[derive(ShaderType)]
156
pub struct ChromaticAberrationUniform {
157
/// The intensity of the effect, in a fraction of the screen.
158
intensity: f32,
159
/// A cap on the number of samples of the source texture that the shader
160
/// will perform.
161
max_samples: u32,
162
/// Padding data.
163
unused_1: u32,
164
/// Padding data.
165
unused_2: u32,
166
}
167
168
/// A resource, part of the render world, that stores the
169
/// [`ChromaticAberrationUniform`]s for each view.
170
#[derive(Resource, Deref, DerefMut, Default)]
171
pub struct PostProcessingUniformBuffers {
172
chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,
173
}
174
175
/// A component, part of the render world, that stores the appropriate byte
176
/// offset within the [`PostProcessingUniformBuffers`] for the camera it's
177
/// attached to.
178
#[derive(Component, Deref, DerefMut)]
179
pub struct PostProcessingUniformBufferOffsets {
180
chromatic_aberration: u32,
181
}
182
183
/// The render node that runs the built-in postprocessing stack.
184
#[derive(Default)]
185
pub struct PostProcessingNode;
186
187
impl Plugin for EffectStackPlugin {
188
fn build(&self, app: &mut App) {
189
load_shader_library!(app, "chromatic_aberration.wgsl");
190
191
embedded_asset!(app, "post_process.wgsl");
192
193
// Load the default chromatic aberration LUT.
194
let mut assets = app.world_mut().resource_mut::<Assets<_>>();
195
let default_lut = assets.add(Image::new(
196
Extent3d {
197
width: 3,
198
height: 1,
199
depth_or_array_layers: 1,
200
},
201
TextureDimension::D2,
202
DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),
203
TextureFormat::Rgba8UnormSrgb,
204
RenderAssetUsages::RENDER_WORLD,
205
));
206
207
app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default());
208
209
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
210
return;
211
};
212
213
render_app
214
.insert_resource(DefaultChromaticAberrationLut(default_lut))
215
.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()
216
.init_resource::<PostProcessingUniformBuffers>()
217
.add_systems(RenderStartup, init_post_processing_pipeline)
218
.add_systems(
219
Render,
220
(
221
prepare_post_processing_pipelines,
222
prepare_post_processing_uniforms,
223
)
224
.in_set(RenderSystems::Prepare),
225
)
226
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
227
Core3d,
228
Node3d::PostProcessing,
229
)
230
.add_render_graph_edges(
231
Core3d,
232
(
233
Node3d::DepthOfField,
234
Node3d::PostProcessing,
235
Node3d::Tonemapping,
236
),
237
)
238
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
239
Core2d,
240
Node2d::PostProcessing,
241
)
242
.add_render_graph_edges(
243
Core2d,
244
(Node2d::Bloom, Node2d::PostProcessing, Node2d::Tonemapping),
245
);
246
}
247
}
248
249
impl Default for ChromaticAberration {
250
fn default() -> Self {
251
Self {
252
color_lut: None,
253
intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY,
254
max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES,
255
}
256
}
257
}
258
259
pub fn init_post_processing_pipeline(
260
mut commands: Commands,
261
render_device: Res<RenderDevice>,
262
fullscreen_shader: Res<FullscreenShader>,
263
asset_server: Res<AssetServer>,
264
) {
265
// Create our single bind group layout.
266
let bind_group_layout = render_device.create_bind_group_layout(
267
Some("postprocessing bind group layout"),
268
&BindGroupLayoutEntries::sequential(
269
ShaderStages::FRAGMENT,
270
(
271
// Chromatic aberration source:
272
texture_2d(TextureSampleType::Float { filterable: true }),
273
// Chromatic aberration source sampler:
274
sampler(SamplerBindingType::Filtering),
275
// Chromatic aberration LUT:
276
texture_2d(TextureSampleType::Float { filterable: true }),
277
// Chromatic aberration LUT sampler:
278
sampler(SamplerBindingType::Filtering),
279
// Chromatic aberration settings:
280
uniform_buffer::<ChromaticAberrationUniform>(true),
281
),
282
),
283
);
284
285
// Both source and chromatic aberration LUTs should be sampled
286
// bilinearly.
287
288
let source_sampler = render_device.create_sampler(&SamplerDescriptor {
289
mipmap_filter: FilterMode::Linear,
290
min_filter: FilterMode::Linear,
291
mag_filter: FilterMode::Linear,
292
..default()
293
});
294
295
let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {
296
mipmap_filter: FilterMode::Linear,
297
min_filter: FilterMode::Linear,
298
mag_filter: FilterMode::Linear,
299
..default()
300
});
301
302
commands.insert_resource(PostProcessingPipeline {
303
bind_group_layout,
304
source_sampler,
305
chromatic_aberration_lut_sampler,
306
fullscreen_shader: fullscreen_shader.clone(),
307
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "post_process.wgsl"),
308
});
309
}
310
311
impl SpecializedRenderPipeline for PostProcessingPipeline {
312
type Key = PostProcessingPipelineKey;
313
314
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
315
RenderPipelineDescriptor {
316
label: Some("postprocessing".into()),
317
layout: vec![self.bind_group_layout.clone()],
318
vertex: self.fullscreen_shader.to_vertex_state(),
319
fragment: Some(FragmentState {
320
shader: self.fragment_shader.clone(),
321
targets: vec![Some(ColorTargetState {
322
format: key.texture_format,
323
blend: None,
324
write_mask: ColorWrites::ALL,
325
})],
326
..default()
327
}),
328
..default()
329
}
330
}
331
}
332
333
impl ViewNode for PostProcessingNode {
334
type ViewQuery = (
335
Read<ViewTarget>,
336
Read<PostProcessingPipelineId>,
337
Read<ChromaticAberration>,
338
Read<PostProcessingUniformBufferOffsets>,
339
);
340
341
fn run<'w>(
342
&self,
343
_: &mut RenderGraphContext,
344
render_context: &mut RenderContext<'w>,
345
(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, '_, Self::ViewQuery>,
346
world: &'w World,
347
) -> Result<(), NodeRunError> {
348
let pipeline_cache = world.resource::<PipelineCache>();
349
let post_processing_pipeline = world.resource::<PostProcessingPipeline>();
350
let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>();
351
let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>();
352
let default_lut = world.resource::<DefaultChromaticAberrationLut>();
353
354
// We need a render pipeline to be prepared.
355
let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {
356
return Ok(());
357
};
358
359
// We need the chromatic aberration LUT to be present.
360
let Some(chromatic_aberration_lut) = gpu_image_assets.get(
361
chromatic_aberration
362
.color_lut
363
.as_ref()
364
.unwrap_or(&default_lut.0),
365
) else {
366
return Ok(());
367
};
368
369
// We need the postprocessing settings to be uploaded to the GPU.
370
let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers
371
.chromatic_aberration
372
.binding()
373
else {
374
return Ok(());
375
};
376
377
let diagnostics = render_context.diagnostic_recorder();
378
379
// Use the [`PostProcessWrite`] infrastructure, since this is a
380
// full-screen pass.
381
let post_process = view_target.post_process_write();
382
383
let pass_descriptor = RenderPassDescriptor {
384
label: Some("postprocessing"),
385
color_attachments: &[Some(RenderPassColorAttachment {
386
view: post_process.destination,
387
depth_slice: None,
388
resolve_target: None,
389
ops: Operations::default(),
390
})],
391
depth_stencil_attachment: None,
392
timestamp_writes: None,
393
occlusion_query_set: None,
394
};
395
396
let bind_group = render_context.render_device().create_bind_group(
397
Some("postprocessing bind group"),
398
&post_processing_pipeline.bind_group_layout,
399
&BindGroupEntries::sequential((
400
post_process.source,
401
&post_processing_pipeline.source_sampler,
402
&chromatic_aberration_lut.texture_view,
403
&post_processing_pipeline.chromatic_aberration_lut_sampler,
404
chromatic_aberration_uniform_buffer_binding,
405
)),
406
);
407
408
let mut render_pass = render_context
409
.command_encoder()
410
.begin_render_pass(&pass_descriptor);
411
let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing");
412
413
render_pass.set_pipeline(pipeline);
414
render_pass.set_bind_group(0, &bind_group, &[**post_processing_uniform_buffer_offsets]);
415
render_pass.draw(0..3, 0..1);
416
417
pass_span.end(&mut render_pass);
418
419
Ok(())
420
}
421
}
422
423
/// Specializes the built-in postprocessing pipeline for each applicable view.
424
pub fn prepare_post_processing_pipelines(
425
mut commands: Commands,
426
pipeline_cache: Res<PipelineCache>,
427
mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,
428
post_processing_pipeline: Res<PostProcessingPipeline>,
429
views: Query<(Entity, &ExtractedView), With<ChromaticAberration>>,
430
) {
431
for (entity, view) in views.iter() {
432
let pipeline_id = pipelines.specialize(
433
&pipeline_cache,
434
&post_processing_pipeline,
435
PostProcessingPipelineKey {
436
texture_format: if view.hdr {
437
ViewTarget::TEXTURE_FORMAT_HDR
438
} else {
439
TextureFormat::bevy_default()
440
},
441
},
442
);
443
444
commands
445
.entity(entity)
446
.insert(PostProcessingPipelineId(pipeline_id));
447
}
448
}
449
450
/// Gathers the built-in postprocessing settings for every view and uploads them
451
/// to the GPU.
452
pub fn prepare_post_processing_uniforms(
453
mut commands: Commands,
454
mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,
455
render_device: Res<RenderDevice>,
456
render_queue: Res<RenderQueue>,
457
mut views: Query<(Entity, &ChromaticAberration)>,
458
) {
459
post_processing_uniform_buffers.clear();
460
461
// Gather up all the postprocessing settings.
462
for (view_entity, chromatic_aberration) in views.iter_mut() {
463
let chromatic_aberration_uniform_buffer_offset =
464
post_processing_uniform_buffers.push(&ChromaticAberrationUniform {
465
intensity: chromatic_aberration.intensity,
466
max_samples: chromatic_aberration.max_samples,
467
unused_1: 0,
468
unused_2: 0,
469
});
470
commands
471
.entity(view_entity)
472
.insert(PostProcessingUniformBufferOffsets {
473
chromatic_aberration: chromatic_aberration_uniform_buffer_offset,
474
});
475
}
476
477
// Upload to the GPU.
478
post_processing_uniform_buffers.write_buffer(&render_device, &render_queue);
479
}
480
481
impl ExtractComponent for ChromaticAberration {
482
type QueryData = Read<ChromaticAberration>;
483
484
type QueryFilter = With<Camera>;
485
486
type Out = ChromaticAberration;
487
488
fn extract_component(
489
chromatic_aberration: QueryItem<'_, '_, Self::QueryData>,
490
) -> Option<Self::Out> {
491
// Skip the postprocessing phase entirely if the intensity is zero.
492
if chromatic_aberration.intensity > 0.0 {
493
Some(chromatic_aberration.clone())
494
} else {
495
None
496
}
497
}
498
}
499
500