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
6849 views
1
use bevy_app::{App, Plugin};
2
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
3
use bevy_camera::{Camera, Camera3d};
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::StartMainPassPostProcessing,
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 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
/// TAA also does not work well with alpha-blended meshes, as it requires depth writing to determine motion.
113
///
114
/// It is very important that correct motion vectors are written for everything on screen.
115
/// Failure to do so will lead to ghosting artifacts. For instance, if particle effects
116
/// are added using a third party library, the library must either:
117
///
118
/// 1. Write particle motion vectors to the motion vectors prepass texture
119
/// 2. Render particles after TAA
120
#[derive(Component, Reflect, Clone)]
121
#[reflect(Component, Default, Clone)]
122
#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass)]
123
#[doc(alias = "Taa")]
124
pub struct TemporalAntiAliasing {
125
/// Set to true to delete the saved temporal history (past frames).
126
///
127
/// Useful for preventing ghosting when the history is no longer
128
/// representative of the current frame, such as in sudden camera cuts.
129
///
130
/// After setting this to true, it will automatically be toggled
131
/// back to false at the end of the frame.
132
pub reset: bool,
133
}
134
135
impl Default for TemporalAntiAliasing {
136
fn default() -> Self {
137
Self { reset: true }
138
}
139
}
140
141
/// Render [`bevy_render::render_graph::Node`] used by temporal anti-aliasing.
142
#[derive(Default)]
143
pub struct TemporalAntiAliasNode;
144
145
impl ViewNode for TemporalAntiAliasNode {
146
type ViewQuery = (
147
&'static ExtractedCamera,
148
&'static ViewTarget,
149
&'static TemporalAntiAliasHistoryTextures,
150
&'static ViewPrepassTextures,
151
&'static TemporalAntiAliasPipelineId,
152
&'static Msaa,
153
);
154
155
fn run(
156
&self,
157
_graph: &mut RenderGraphContext,
158
render_context: &mut RenderContext,
159
(camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa): QueryItem<
160
Self::ViewQuery,
161
>,
162
world: &World,
163
) -> Result<(), NodeRunError> {
164
if *msaa != Msaa::Off {
165
warn!("Temporal anti-aliasing requires MSAA to be disabled");
166
return Ok(());
167
}
168
169
let (Some(pipelines), Some(pipeline_cache)) = (
170
world.get_resource::<TaaPipeline>(),
171
world.get_resource::<PipelineCache>(),
172
) else {
173
return Ok(());
174
};
175
let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = (
176
pipeline_cache.get_render_pipeline(taa_pipeline_id.0),
177
&prepass_textures.motion_vectors,
178
&prepass_textures.depth,
179
) else {
180
return Ok(());
181
};
182
183
let diagnostics = render_context.diagnostic_recorder();
184
185
let view_target = view_target.post_process_write();
186
187
let taa_bind_group = render_context.render_device().create_bind_group(
188
"taa_bind_group",
189
&pipelines.taa_bind_group_layout,
190
&BindGroupEntries::sequential((
191
view_target.source,
192
&taa_history_textures.read.default_view,
193
&prepass_motion_vectors_texture.texture.default_view,
194
&prepass_depth_texture.texture.default_view,
195
&pipelines.nearest_sampler,
196
&pipelines.linear_sampler,
197
)),
198
);
199
200
{
201
let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
202
label: Some("taa"),
203
color_attachments: &[
204
Some(RenderPassColorAttachment {
205
view: view_target.destination,
206
depth_slice: None,
207
resolve_target: None,
208
ops: Operations::default(),
209
}),
210
Some(RenderPassColorAttachment {
211
view: &taa_history_textures.write.default_view,
212
depth_slice: None,
213
resolve_target: None,
214
ops: Operations::default(),
215
}),
216
],
217
depth_stencil_attachment: None,
218
timestamp_writes: None,
219
occlusion_query_set: None,
220
});
221
let pass_span = diagnostics.pass_span(&mut taa_pass, "taa");
222
223
taa_pass.set_render_pipeline(taa_pipeline);
224
taa_pass.set_bind_group(0, &taa_bind_group, &[]);
225
if let Some(viewport) = camera.viewport.as_ref() {
226
taa_pass.set_camera_viewport(viewport);
227
}
228
taa_pass.draw(0..3, 0..1);
229
230
pass_span.end(&mut taa_pass);
231
}
232
233
Ok(())
234
}
235
}
236
237
#[derive(Resource)]
238
struct TaaPipeline {
239
taa_bind_group_layout: BindGroupLayout,
240
nearest_sampler: Sampler,
241
linear_sampler: Sampler,
242
fullscreen_shader: FullscreenShader,
243
fragment_shader: Handle<Shader>,
244
}
245
246
fn init_taa_pipeline(
247
mut commands: Commands,
248
render_device: Res<RenderDevice>,
249
fullscreen_shader: Res<FullscreenShader>,
250
asset_server: Res<AssetServer>,
251
) {
252
let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
253
label: Some("taa_nearest_sampler"),
254
mag_filter: FilterMode::Nearest,
255
min_filter: FilterMode::Nearest,
256
..SamplerDescriptor::default()
257
});
258
let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
259
label: Some("taa_linear_sampler"),
260
mag_filter: FilterMode::Linear,
261
min_filter: FilterMode::Linear,
262
..SamplerDescriptor::default()
263
});
264
265
let taa_bind_group_layout = render_device.create_bind_group_layout(
266
"taa_bind_group_layout",
267
&BindGroupLayoutEntries::sequential(
268
ShaderStages::FRAGMENT,
269
(
270
// View target (read)
271
texture_2d(TextureSampleType::Float { filterable: true }),
272
// TAA History (read)
273
texture_2d(TextureSampleType::Float { filterable: true }),
274
// Motion Vectors
275
texture_2d(TextureSampleType::Float { filterable: true }),
276
// Depth
277
texture_depth_2d(),
278
// Nearest sampler
279
sampler(SamplerBindingType::NonFiltering),
280
// Linear sampler
281
sampler(SamplerBindingType::Filtering),
282
),
283
),
284
);
285
286
commands.insert_resource(TaaPipeline {
287
taa_bind_group_layout,
288
nearest_sampler,
289
linear_sampler,
290
fullscreen_shader: fullscreen_shader.clone(),
291
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "taa.wgsl"),
292
});
293
}
294
295
#[derive(PartialEq, Eq, Hash, Clone)]
296
struct TaaPipelineKey {
297
hdr: bool,
298
reset: bool,
299
}
300
301
impl SpecializedRenderPipeline for TaaPipeline {
302
type Key = TaaPipelineKey;
303
304
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
305
let mut shader_defs = vec![];
306
307
let format = if key.hdr {
308
shader_defs.push("TONEMAP".into());
309
ViewTarget::TEXTURE_FORMAT_HDR
310
} else {
311
TextureFormat::bevy_default()
312
};
313
314
if key.reset {
315
shader_defs.push("RESET".into());
316
}
317
318
RenderPipelineDescriptor {
319
label: Some("taa_pipeline".into()),
320
layout: vec![self.taa_bind_group_layout.clone()],
321
vertex: self.fullscreen_shader.to_vertex_state(),
322
fragment: Some(FragmentState {
323
shader: self.fragment_shader.clone(),
324
shader_defs,
325
targets: vec![
326
Some(ColorTargetState {
327
format,
328
blend: None,
329
write_mask: ColorWrites::ALL,
330
}),
331
Some(ColorTargetState {
332
format,
333
blend: None,
334
write_mask: ColorWrites::ALL,
335
}),
336
],
337
..default()
338
}),
339
..default()
340
}
341
}
342
}
343
344
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
345
let mut cameras_3d =
346
main_world.query::<(RenderEntity, &Camera, Option<&mut TemporalAntiAliasing>)>();
347
348
for (entity, camera, taa_settings) in cameras_3d.iter_mut(&mut main_world) {
349
let mut entity_commands = commands
350
.get_entity(entity)
351
.expect("Camera entity wasn't synced.");
352
if let Some(mut taa_settings) = taa_settings
353
&& camera.is_active
354
{
355
entity_commands.insert(taa_settings.clone());
356
taa_settings.reset = false;
357
} else {
358
entity_commands.remove::<(
359
TemporalAntiAliasing,
360
TemporalAntiAliasHistoryTextures,
361
TemporalAntiAliasPipelineId,
362
)>();
363
}
364
}
365
}
366
367
fn prepare_taa_jitter(
368
frame_count: Res<FrameCount>,
369
mut query: Query<
370
&mut TemporalJitter,
371
(
372
With<TemporalAntiAliasing>,
373
With<Camera3d>,
374
With<TemporalJitter>,
375
With<DepthPrepass>,
376
With<MotionVectorPrepass>,
377
),
378
>,
379
) {
380
// Halton sequence (2, 3) - 0.5
381
let halton_sequence = [
382
vec2(0.0, 0.0),
383
vec2(0.0, -0.16666666),
384
vec2(-0.25, 0.16666669),
385
vec2(0.25, -0.3888889),
386
vec2(-0.375, -0.055555552),
387
vec2(0.125, 0.2777778),
388
vec2(-0.125, -0.2777778),
389
vec2(0.375, 0.055555582),
390
];
391
392
let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
393
394
for mut jitter in &mut query {
395
jitter.offset = offset;
396
}
397
}
398
399
#[derive(Component)]
400
pub struct TemporalAntiAliasHistoryTextures {
401
write: CachedTexture,
402
read: CachedTexture,
403
}
404
405
fn prepare_taa_history_textures(
406
mut commands: Commands,
407
mut texture_cache: ResMut<TextureCache>,
408
render_device: Res<RenderDevice>,
409
frame_count: Res<FrameCount>,
410
views: Query<(Entity, &ExtractedCamera, &ExtractedView), With<TemporalAntiAliasing>>,
411
) {
412
for (entity, camera, view) in &views {
413
if let Some(physical_target_size) = camera.physical_target_size {
414
let mut texture_descriptor = TextureDescriptor {
415
label: None,
416
size: physical_target_size.to_extents(),
417
mip_level_count: 1,
418
sample_count: 1,
419
dimension: TextureDimension::D2,
420
format: if view.hdr {
421
ViewTarget::TEXTURE_FORMAT_HDR
422
} else {
423
TextureFormat::bevy_default()
424
},
425
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
426
view_formats: &[],
427
};
428
429
texture_descriptor.label = Some("taa_history_1_texture");
430
let history_1_texture = texture_cache.get(&render_device, texture_descriptor.clone());
431
432
texture_descriptor.label = Some("taa_history_2_texture");
433
let history_2_texture = texture_cache.get(&render_device, texture_descriptor);
434
435
let textures = if frame_count.0.is_multiple_of(2) {
436
TemporalAntiAliasHistoryTextures {
437
write: history_1_texture,
438
read: history_2_texture,
439
}
440
} else {
441
TemporalAntiAliasHistoryTextures {
442
write: history_2_texture,
443
read: history_1_texture,
444
}
445
};
446
447
commands.entity(entity).insert(textures);
448
}
449
}
450
}
451
452
#[derive(Component)]
453
pub struct TemporalAntiAliasPipelineId(CachedRenderPipelineId);
454
455
fn prepare_taa_pipelines(
456
mut commands: Commands,
457
pipeline_cache: Res<PipelineCache>,
458
mut pipelines: ResMut<SpecializedRenderPipelines<TaaPipeline>>,
459
pipeline: Res<TaaPipeline>,
460
views: Query<(Entity, &ExtractedView, &TemporalAntiAliasing)>,
461
) {
462
for (entity, view, taa_settings) in &views {
463
let mut pipeline_key = TaaPipelineKey {
464
hdr: view.hdr,
465
reset: taa_settings.reset,
466
};
467
let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key.clone());
468
469
// Prepare non-reset pipeline anyways - it will be necessary next frame
470
if pipeline_key.reset {
471
pipeline_key.reset = false;
472
pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key);
473
}
474
475
commands
476
.entity(entity)
477
.insert(TemporalAntiAliasPipelineId(pipeline_id));
478
}
479
}
480
481