Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_anti_alias/src/taa/mod.rs
6596 views
1
use bevy_app::{App, Plugin};
2
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
3
use bevy_camera::{Camera, Camera3d, Projection};
4
use bevy_core_pipeline::{
5
core_3d::graph::{Core3d, Node3d},
6
prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
7
FullscreenShader,
8
};
9
use bevy_diagnostic::FrameCount;
10
use bevy_ecs::{
11
prelude::{Component, Entity, ReflectComponent},
12
query::{QueryItem, With},
13
resource::Resource,
14
schedule::IntoScheduleConfigs,
15
system::{Commands, Query, Res, ResMut},
16
world::World,
17
};
18
use bevy_image::{BevyDefault as _, ToExtents};
19
use bevy_math::vec2;
20
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
21
use bevy_render::{
22
camera::{ExtractedCamera, MipBias, TemporalJitter},
23
diagnostic::RecordDiagnostics,
24
render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},
25
render_resource::{
26
binding_types::{sampler, texture_2d, texture_depth_2d},
27
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
28
ColorTargetState, ColorWrites, FilterMode, FragmentState, Operations, PipelineCache,
29
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
30
SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedRenderPipeline,
31
SpecializedRenderPipelines, TextureDescriptor, TextureDimension, TextureFormat,
32
TextureSampleType, TextureUsages,
33
},
34
renderer::{RenderContext, RenderDevice},
35
sync_component::SyncComponentPlugin,
36
sync_world::RenderEntity,
37
texture::{CachedTexture, TextureCache},
38
view::{ExtractedView, Msaa, ViewTarget},
39
ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup, RenderSystems,
40
};
41
use bevy_shader::Shader;
42
use bevy_utils::default;
43
use tracing::warn;
44
45
/// Plugin for temporal anti-aliasing.
46
///
47
/// See [`TemporalAntiAliasing`] for more details.
48
#[derive(Default)]
49
pub struct TemporalAntiAliasPlugin;
50
51
impl Plugin for TemporalAntiAliasPlugin {
52
fn build(&self, app: &mut App) {
53
embedded_asset!(app, "taa.wgsl");
54
55
app.add_plugins(SyncComponentPlugin::<TemporalAntiAliasing>::default());
56
57
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
58
return;
59
};
60
render_app
61
.init_resource::<SpecializedRenderPipelines<TaaPipeline>>()
62
.add_systems(RenderStartup, init_taa_pipeline)
63
.add_systems(ExtractSchedule, extract_taa_settings)
64
.add_systems(
65
Render,
66
(
67
prepare_taa_jitter.in_set(RenderSystems::ManageViews),
68
prepare_taa_pipelines.in_set(RenderSystems::Prepare),
69
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
70
),
71
)
72
.add_render_graph_node::<ViewNodeRunner<TemporalAntiAliasNode>>(Core3d, Node3d::Taa)
73
.add_render_graph_edges(
74
Core3d,
75
(
76
Node3d::EndMainPass,
77
Node3d::MotionBlur, // Running before TAA reduces edge artifacts and noise
78
Node3d::Taa,
79
Node3d::Bloom,
80
Node3d::Tonemapping,
81
),
82
);
83
}
84
}
85
86
/// Component to apply temporal anti-aliasing to a 3D perspective camera.
87
///
88
/// Temporal anti-aliasing (TAA) is a form of image smoothing/filtering, like
89
/// multisample anti-aliasing (MSAA), or fast approximate anti-aliasing (FXAA).
90
/// TAA works by blending (averaging) each frame with the past few frames.
91
///
92
/// # Tradeoffs
93
///
94
/// Pros:
95
/// * Filters more types of aliasing than MSAA, such as textures and singular bright pixels (specular aliasing)
96
/// * Cost scales with screen/view resolution, unlike MSAA which scales with number of triangles
97
/// * Greatly increases the quality of stochastic rendering techniques such as SSAO, certain shadow map sampling methods, etc
98
///
99
/// Cons:
100
/// * Chance of "ghosting" - ghostly trails left behind moving objects
101
/// * Thin geometry, lighting detail, or texture lines may flicker noisily or disappear
102
///
103
/// Because TAA blends past frames with the current frame, when the frames differ too much
104
/// (such as with fast moving objects or camera cuts), ghosting artifacts may occur.
105
///
106
/// Artifacts tend to be reduced at higher framerates and rendering resolution.
107
///
108
/// # Usage Notes
109
///
110
/// Any camera with this component must also disable [`Msaa`] by setting it to [`Msaa::Off`].
111
///
112
/// [Currently](https://github.com/bevyengine/bevy/issues/8423), TAA cannot be used with [`bevy_camera::OrthographicProjection`].
113
///
114
/// TAA also does not work well with alpha-blended meshes, as it requires depth writing to determine motion.
115
///
116
/// It is very important that correct motion vectors are written for everything on screen.
117
/// Failure to do so will lead to ghosting artifacts. For instance, if particle effects
118
/// are added using a third party library, the library must either:
119
///
120
/// 1. Write particle motion vectors to the motion vectors prepass texture
121
/// 2. Render particles after TAA
122
#[derive(Component, Reflect, Clone)]
123
#[reflect(Component, Default, Clone)]
124
#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass)]
125
#[doc(alias = "Taa")]
126
pub struct TemporalAntiAliasing {
127
/// Set to true to delete the saved temporal history (past frames).
128
///
129
/// Useful for preventing ghosting when the history is no longer
130
/// representative of the current frame, such as in sudden camera cuts.
131
///
132
/// After setting this to true, it will automatically be toggled
133
/// back to false at the end of the frame.
134
pub reset: bool,
135
}
136
137
impl Default for TemporalAntiAliasing {
138
fn default() -> Self {
139
Self { reset: true }
140
}
141
}
142
143
/// Render [`bevy_render::render_graph::Node`] used by temporal anti-aliasing.
144
#[derive(Default)]
145
pub struct TemporalAntiAliasNode;
146
147
impl ViewNode for TemporalAntiAliasNode {
148
type ViewQuery = (
149
&'static ExtractedCamera,
150
&'static ViewTarget,
151
&'static TemporalAntiAliasHistoryTextures,
152
&'static ViewPrepassTextures,
153
&'static TemporalAntiAliasPipelineId,
154
&'static Msaa,
155
);
156
157
fn run(
158
&self,
159
_graph: &mut RenderGraphContext,
160
render_context: &mut RenderContext,
161
(camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa): QueryItem<
162
Self::ViewQuery,
163
>,
164
world: &World,
165
) -> Result<(), NodeRunError> {
166
if *msaa != Msaa::Off {
167
warn!("Temporal anti-aliasing requires MSAA to be disabled");
168
return Ok(());
169
}
170
171
let (Some(pipelines), Some(pipeline_cache)) = (
172
world.get_resource::<TaaPipeline>(),
173
world.get_resource::<PipelineCache>(),
174
) else {
175
return Ok(());
176
};
177
let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = (
178
pipeline_cache.get_render_pipeline(taa_pipeline_id.0),
179
&prepass_textures.motion_vectors,
180
&prepass_textures.depth,
181
) else {
182
return Ok(());
183
};
184
185
let diagnostics = render_context.diagnostic_recorder();
186
187
let view_target = view_target.post_process_write();
188
189
let taa_bind_group = render_context.render_device().create_bind_group(
190
"taa_bind_group",
191
&pipelines.taa_bind_group_layout,
192
&BindGroupEntries::sequential((
193
view_target.source,
194
&taa_history_textures.read.default_view,
195
&prepass_motion_vectors_texture.texture.default_view,
196
&prepass_depth_texture.texture.default_view,
197
&pipelines.nearest_sampler,
198
&pipelines.linear_sampler,
199
)),
200
);
201
202
{
203
let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
204
label: Some("taa"),
205
color_attachments: &[
206
Some(RenderPassColorAttachment {
207
view: view_target.destination,
208
depth_slice: None,
209
resolve_target: None,
210
ops: Operations::default(),
211
}),
212
Some(RenderPassColorAttachment {
213
view: &taa_history_textures.write.default_view,
214
depth_slice: None,
215
resolve_target: None,
216
ops: Operations::default(),
217
}),
218
],
219
depth_stencil_attachment: None,
220
timestamp_writes: None,
221
occlusion_query_set: None,
222
});
223
let pass_span = diagnostics.pass_span(&mut taa_pass, "taa");
224
225
taa_pass.set_render_pipeline(taa_pipeline);
226
taa_pass.set_bind_group(0, &taa_bind_group, &[]);
227
if let Some(viewport) = camera.viewport.as_ref() {
228
taa_pass.set_camera_viewport(viewport);
229
}
230
taa_pass.draw(0..3, 0..1);
231
232
pass_span.end(&mut taa_pass);
233
}
234
235
Ok(())
236
}
237
}
238
239
#[derive(Resource)]
240
struct TaaPipeline {
241
taa_bind_group_layout: BindGroupLayout,
242
nearest_sampler: Sampler,
243
linear_sampler: Sampler,
244
fullscreen_shader: FullscreenShader,
245
fragment_shader: Handle<Shader>,
246
}
247
248
fn init_taa_pipeline(
249
mut commands: Commands,
250
render_device: Res<RenderDevice>,
251
fullscreen_shader: Res<FullscreenShader>,
252
asset_server: Res<AssetServer>,
253
) {
254
let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
255
label: Some("taa_nearest_sampler"),
256
mag_filter: FilterMode::Nearest,
257
min_filter: FilterMode::Nearest,
258
..SamplerDescriptor::default()
259
});
260
let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
261
label: Some("taa_linear_sampler"),
262
mag_filter: FilterMode::Linear,
263
min_filter: FilterMode::Linear,
264
..SamplerDescriptor::default()
265
});
266
267
let taa_bind_group_layout = render_device.create_bind_group_layout(
268
"taa_bind_group_layout",
269
&BindGroupLayoutEntries::sequential(
270
ShaderStages::FRAGMENT,
271
(
272
// View target (read)
273
texture_2d(TextureSampleType::Float { filterable: true }),
274
// TAA History (read)
275
texture_2d(TextureSampleType::Float { filterable: true }),
276
// Motion Vectors
277
texture_2d(TextureSampleType::Float { filterable: true }),
278
// Depth
279
texture_depth_2d(),
280
// Nearest sampler
281
sampler(SamplerBindingType::NonFiltering),
282
// Linear sampler
283
sampler(SamplerBindingType::Filtering),
284
),
285
),
286
);
287
288
commands.insert_resource(TaaPipeline {
289
taa_bind_group_layout,
290
nearest_sampler,
291
linear_sampler,
292
fullscreen_shader: fullscreen_shader.clone(),
293
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "taa.wgsl"),
294
});
295
}
296
297
#[derive(PartialEq, Eq, Hash, Clone)]
298
struct TaaPipelineKey {
299
hdr: bool,
300
reset: bool,
301
}
302
303
impl SpecializedRenderPipeline for TaaPipeline {
304
type Key = TaaPipelineKey;
305
306
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
307
let mut shader_defs = vec![];
308
309
let format = if key.hdr {
310
shader_defs.push("TONEMAP".into());
311
ViewTarget::TEXTURE_FORMAT_HDR
312
} else {
313
TextureFormat::bevy_default()
314
};
315
316
if key.reset {
317
shader_defs.push("RESET".into());
318
}
319
320
RenderPipelineDescriptor {
321
label: Some("taa_pipeline".into()),
322
layout: vec![self.taa_bind_group_layout.clone()],
323
vertex: self.fullscreen_shader.to_vertex_state(),
324
fragment: Some(FragmentState {
325
shader: self.fragment_shader.clone(),
326
shader_defs,
327
targets: vec![
328
Some(ColorTargetState {
329
format,
330
blend: None,
331
write_mask: ColorWrites::ALL,
332
}),
333
Some(ColorTargetState {
334
format,
335
blend: None,
336
write_mask: ColorWrites::ALL,
337
}),
338
],
339
..default()
340
}),
341
..default()
342
}
343
}
344
}
345
346
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
347
let mut cameras_3d = main_world.query::<(
348
RenderEntity,
349
&Camera,
350
&Projection,
351
Option<&mut TemporalAntiAliasing>,
352
)>();
353
354
for (entity, camera, camera_projection, taa_settings) in cameras_3d.iter_mut(&mut main_world) {
355
let mut entity_commands = commands
356
.get_entity(entity)
357
.expect("Camera entity wasn't synced.");
358
if let Some(mut taa_settings) = taa_settings
359
&& camera.is_active
360
&& camera_projection.is_perspective()
361
{
362
entity_commands.insert(taa_settings.clone());
363
taa_settings.reset = false;
364
} else {
365
entity_commands.remove::<(
366
TemporalAntiAliasing,
367
TemporalAntiAliasHistoryTextures,
368
TemporalAntiAliasPipelineId,
369
)>();
370
}
371
}
372
}
373
374
fn prepare_taa_jitter(
375
frame_count: Res<FrameCount>,
376
mut query: Query<
377
&mut TemporalJitter,
378
(
379
With<TemporalAntiAliasing>,
380
With<Camera3d>,
381
With<TemporalJitter>,
382
With<DepthPrepass>,
383
With<MotionVectorPrepass>,
384
),
385
>,
386
) {
387
// Halton sequence (2, 3) - 0.5
388
let halton_sequence = [
389
vec2(0.0, 0.0),
390
vec2(0.0, -0.16666666),
391
vec2(-0.25, 0.16666669),
392
vec2(0.25, -0.3888889),
393
vec2(-0.375, -0.055555552),
394
vec2(0.125, 0.2777778),
395
vec2(-0.125, -0.2777778),
396
vec2(0.375, 0.055555582),
397
];
398
399
let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
400
401
for mut jitter in &mut query {
402
jitter.offset = offset;
403
}
404
}
405
406
#[derive(Component)]
407
pub struct TemporalAntiAliasHistoryTextures {
408
write: CachedTexture,
409
read: CachedTexture,
410
}
411
412
fn prepare_taa_history_textures(
413
mut commands: Commands,
414
mut texture_cache: ResMut<TextureCache>,
415
render_device: Res<RenderDevice>,
416
frame_count: Res<FrameCount>,
417
views: Query<(Entity, &ExtractedCamera, &ExtractedView), With<TemporalAntiAliasing>>,
418
) {
419
for (entity, camera, view) in &views {
420
if let Some(physical_target_size) = camera.physical_target_size {
421
let mut texture_descriptor = TextureDescriptor {
422
label: None,
423
size: physical_target_size.to_extents(),
424
mip_level_count: 1,
425
sample_count: 1,
426
dimension: TextureDimension::D2,
427
format: if view.hdr {
428
ViewTarget::TEXTURE_FORMAT_HDR
429
} else {
430
TextureFormat::bevy_default()
431
},
432
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
433
view_formats: &[],
434
};
435
436
texture_descriptor.label = Some("taa_history_1_texture");
437
let history_1_texture = texture_cache.get(&render_device, texture_descriptor.clone());
438
439
texture_descriptor.label = Some("taa_history_2_texture");
440
let history_2_texture = texture_cache.get(&render_device, texture_descriptor);
441
442
let textures = if frame_count.0.is_multiple_of(2) {
443
TemporalAntiAliasHistoryTextures {
444
write: history_1_texture,
445
read: history_2_texture,
446
}
447
} else {
448
TemporalAntiAliasHistoryTextures {
449
write: history_2_texture,
450
read: history_1_texture,
451
}
452
};
453
454
commands.entity(entity).insert(textures);
455
}
456
}
457
}
458
459
#[derive(Component)]
460
pub struct TemporalAntiAliasPipelineId(CachedRenderPipelineId);
461
462
fn prepare_taa_pipelines(
463
mut commands: Commands,
464
pipeline_cache: Res<PipelineCache>,
465
mut pipelines: ResMut<SpecializedRenderPipelines<TaaPipeline>>,
466
pipeline: Res<TaaPipeline>,
467
views: Query<(Entity, &ExtractedView, &TemporalAntiAliasing)>,
468
) {
469
for (entity, view, taa_settings) in &views {
470
let mut pipeline_key = TaaPipelineKey {
471
hdr: view.hdr,
472
reset: taa_settings.reset,
473
};
474
let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key.clone());
475
476
// Prepare non-reset pipeline anyways - it will be necessary next frame
477
if pipeline_key.reset {
478
pipeline_key.reset = false;
479
pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key);
480
}
481
482
commands
483
.entity(entity)
484
.insert(TemporalAntiAliasPipelineId(pipeline_id));
485
}
486
}
487
488