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