diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 51e5ae36278e9..fd438abd9cba3 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -20,7 +20,10 @@ pub use skybox::Skybox; /// Experimental features that are not yet finished. Please report any issues you encounter! pub mod experimental { pub mod taa { - pub use crate::taa::*; + pub use crate::taa::{ + TemporalAntiAliasBundle, TemporalAntiAliasNode, TemporalAntiAliasPlugin, + TemporalAntiAliasSettings, + }; } } diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index e61dccc5d4bf5..1c75b12339707 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -18,24 +18,27 @@ use bevy_math::vec2; use bevy_reflect::Reflect; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, + extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, prelude::{Camera, Projection}, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, render_resource::{ BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, - ColorTargetState, ColorWrites, Extent3d, FilterMode, FragmentState, MultisampleState, - Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor, - RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader, - ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDescriptor, - TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDimension, + BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType::Uniform, + CachedRenderPipelineId, ColorTargetState, ColorWrites, Extent3d, FilterMode, FragmentState, + MultisampleState, Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, + RenderPassDescriptor, RenderPipelineDescriptor, Sampler, SamplerBindingType, + SamplerDescriptor, Shader, ShaderStages, ShaderType, SpecializedRenderPipeline, + SpecializedRenderPipelines, TextureDescriptor, TextureDimension, TextureFormat, + TextureSampleType, TextureUsages, TextureViewDimension, }, renderer::{RenderContext, RenderDevice}, texture::{BevyDefault, CachedTexture, TextureCache}, view::{ExtractedView, Msaa, ViewTarget}, ExtractSchedule, MainWorld, Render, RenderApp, RenderSet, }; +use std::num::NonZeroU64; -mod draw_3d_graph { +pub mod draw_3d_graph { pub mod node { /// Label for the TAA render node. pub const TAA: &str = "taa"; @@ -54,14 +57,15 @@ impl Plugin for TemporalAntiAliasPlugin { load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl); app.insert_resource(Msaa::Off) - .register_type::(); + .register_type::() + .add_plugins(UniformComponentPlugin::::default()); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app - .init_resource::>() + .init_resource::>() .add_systems(ExtractSchedule, extract_taa_settings) .add_systems( Render, @@ -71,7 +75,10 @@ impl Plugin for TemporalAntiAliasPlugin { prepare_taa_history_textures.in_set(RenderSet::PrepareResources), ), ) - .add_render_graph_node::>(CORE_3D, draw_3d_graph::node::TAA) + .add_render_graph_node::>( + CORE_3D, + draw_3d_graph::node::TAA, + ) .add_render_graph_edges( CORE_3D, &[ @@ -88,7 +95,7 @@ impl Plugin for TemporalAntiAliasPlugin { return; }; - render_app.init_resource::(); + render_app.init_resource::(); } } @@ -110,14 +117,13 @@ pub struct TemporalAntiAliasBundle { /// # Tradeoffs /// /// Pros: +/// * Filters more types of aliasing than MSAA, such as textures and singular bright pixels (specular aliasing) /// * Cost scales with screen/view resolution, unlike MSAA which scales with number of triangles -/// * Filters more types of aliasing than MSAA, such as textures and singular bright pixels -/// * Greatly increases the quality of stochastic rendering techniques such as SSAO, shadow mapping, etc +/// * Greatly increases the quality of stochastic rendering techniques such as SSAO, certain shadow map sampling methods, etc /// /// Cons: /// * Chance of "ghosting" - ghostly trails left behind moving objects -/// * Thin geometry, lighting detail, or texture lines may flicker or disappear -/// * Slightly blurs the image, leading to a softer look (using an additional sharpening pass can reduce this) +/// * Thin geometry, lighting detail, or texture lines may flicker noisily or disappear /// /// Because TAA blends past frames with the current frame, when the frames differ too much /// (such as with fast moving objects or camera cuts), ghosting artifacts may occur. @@ -130,7 +136,7 @@ pub struct TemporalAntiAliasBundle { /// and add the [`DepthPrepass`], [`MotionVectorPrepass`], and [`TemporalJitter`] /// components to your camera. /// -/// Cannot be used with [`bevy_render::camera::OrthographicProjection`]. +/// [Currently](https://github.com/bevyengine/bevy/issues/8423) cannot be used with [`bevy_render::camera::OrthographicProjection`]. /// /// Currently does not support skinned meshes and morph targets. /// There will probably be ghosting artifacts if used with them. @@ -151,7 +157,7 @@ pub struct TemporalAntiAliasSettings { /// representative of the current frame, such as in sudden camera cuts. /// /// After setting this to true, it will automatically be toggled - /// back to false after one frame. + /// back to false at the end of the frame. pub reset: bool, } @@ -161,81 +167,94 @@ impl Default for TemporalAntiAliasSettings { } } +/// Render [bevy_render::render_graph::Node] used by temporal anti-aliasing. #[derive(Default)] -struct TAANode; +pub struct TemporalAntiAliasNode; -impl ViewNode for TAANode { +impl ViewNode for TemporalAntiAliasNode { type ViewQuery = ( &'static ExtractedCamera, &'static ViewTarget, - &'static TAAHistoryTextures, + &'static TemporalAntiAliasHistoryTextures, &'static ViewPrepassTextures, - &'static TAAPipelineId, + &'static TemporalAntiAliasPipelineId, + &'static DynamicUniformIndex, ); fn run( &self, _graph: &mut RenderGraphContext, render_context: &mut RenderContext, - (camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id): QueryItem< - Self::ViewQuery, - >, + ( + camera, + view_target, + taa_history_textures, + prepass_textures, + taa_pipeline_id, + taa_uniforms_index, + ): QueryItem, world: &World, ) -> Result<(), NodeRunError> { - let (Some(pipelines), Some(pipeline_cache)) = ( - world.get_resource::(), + let (Some(pipelines), Some(pipeline_cache), Some(taa_uniforms)) = ( + world.get_resource::(), world.get_resource::(), + world.get_resource::>(), ) else { return Ok(()); }; - let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = ( + let ( + Some(taa_pipeline), + Some(prepass_motion_vectors_texture), + Some(prepass_depth_texture), + Some(taa_uniforms), + ) = ( pipeline_cache.get_render_pipeline(taa_pipeline_id.0), &prepass_textures.motion_vectors, &prepass_textures.depth, - ) else { + taa_uniforms.binding(), + ) + else { return Ok(()); }; let view_target = view_target.post_process_write(); - let taa_bind_group = - render_context - .render_device() - .create_bind_group(&BindGroupDescriptor { - label: Some("taa_bind_group"), - layout: &pipelines.taa_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(view_target.source), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::TextureView( - &taa_history_textures.read.default_view, - ), - }, - BindGroupEntry { - binding: 2, - resource: BindingResource::TextureView( - &prepass_motion_vectors_texture.default_view, - ), - }, - BindGroupEntry { - binding: 3, - resource: BindingResource::TextureView( - &prepass_depth_texture.default_view, - ), - }, - BindGroupEntry { - binding: 4, - resource: BindingResource::Sampler(&pipelines.nearest_sampler), - }, - BindGroupEntry { - binding: 5, - resource: BindingResource::Sampler(&pipelines.linear_sampler), - }, - ], - }); + let render_device = render_context.render_device(); + let taa_bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("taa_bind_group"), + layout: &pipelines.taa_bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(view_target.source), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(&taa_history_textures.read.default_view), + }, + BindGroupEntry { + binding: 2, + resource: BindingResource::TextureView( + &prepass_motion_vectors_texture.default_view, + ), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(&prepass_depth_texture.default_view), + }, + BindGroupEntry { + binding: 4, + resource: BindingResource::Sampler(&pipelines.nearest_sampler), + }, + BindGroupEntry { + binding: 5, + resource: BindingResource::Sampler(&pipelines.linear_sampler), + }, + BindGroupEntry { + binding: 6, + resource: taa_uniforms, + }, + ], + }); { let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { @@ -255,7 +274,7 @@ impl ViewNode for TAANode { depth_stencil_attachment: None, }); taa_pass.set_render_pipeline(taa_pipeline); - taa_pass.set_bind_group(0, &taa_bind_group, &[]); + taa_pass.set_bind_group(0, &taa_bind_group, &[taa_uniforms_index.index()]); if let Some(viewport) = camera.viewport.as_ref() { taa_pass.set_camera_viewport(viewport); } @@ -267,13 +286,13 @@ impl ViewNode for TAANode { } #[derive(Resource)] -struct TAAPipeline { +struct TaaPipeline { taa_bind_group_layout: BindGroupLayout, nearest_sampler: Sampler, linear_sampler: Sampler, } -impl FromWorld for TAAPipeline { +impl FromWorld for TaaPipeline { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); @@ -352,10 +371,21 @@ impl FromWorld for TAAPipeline { ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, }, + // TAA uniforms + BindGroupLayoutEntry { + binding: 6, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: Uniform, + has_dynamic_offset: true, + min_binding_size: NonZeroU64::new(4), + }, + count: None, + }, ], }); - TAAPipeline { + TaaPipeline { taa_bind_group_layout, nearest_sampler, linear_sampler, @@ -364,13 +394,12 @@ impl FromWorld for TAAPipeline { } #[derive(PartialEq, Eq, Hash, Clone)] -struct TAAPipelineKey { +struct TaaPipelineKey { hdr: bool, - reset: bool, } -impl SpecializedRenderPipeline for TAAPipeline { - type Key = TAAPipelineKey; +impl SpecializedRenderPipeline for TaaPipeline { + type Key = TaaPipelineKey; fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut shader_defs = vec![]; @@ -382,10 +411,6 @@ impl SpecializedRenderPipeline for TAAPipeline { TextureFormat::bevy_default() }; - if key.reset { - shader_defs.push("RESET".into()); - } - RenderPipelineDescriptor { label: Some("taa_pipeline".into()), layout: vec![self.taa_bind_group_layout.clone()], @@ -429,7 +454,9 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut, - mut query: Query< - (Entity, &mut TemporalJitter, Option<&MipBias>), - With, - >, + mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With>, mut commands: Commands, ) { // Halton sequence (2, 3) - 0.5, skipping i = 0 @@ -467,7 +491,7 @@ fn prepare_taa_jitter_and_mip_bias( } #[derive(Component)] -struct TAAHistoryTextures { +pub struct TemporalAntiAliasHistoryTextures { write: CachedTexture, read: CachedTexture, } @@ -477,7 +501,7 @@ fn prepare_taa_history_textures( mut texture_cache: ResMut, render_device: Res, frame_count: Res, - views: Query<(Entity, &ExtractedCamera, &ExtractedView), With>, + views: Query<(Entity, &ExtractedCamera, &ExtractedView), With>, ) { for (entity, camera, view) in &views { if let Some(physical_viewport_size) = camera.physical_viewport_size { @@ -507,12 +531,12 @@ fn prepare_taa_history_textures( let history_2_texture = texture_cache.get(&render_device, texture_descriptor); let textures = if frame_count.0 % 2 == 0 { - TAAHistoryTextures { + TemporalAntiAliasHistoryTextures { write: history_1_texture, read: history_2_texture, } } else { - TAAHistoryTextures { + TemporalAntiAliasHistoryTextures { write: history_2_texture, read: history_1_texture, } @@ -524,28 +548,26 @@ fn prepare_taa_history_textures( } #[derive(Component)] -struct TAAPipelineId(CachedRenderPipelineId); +pub struct TemporalAntiAliasPipelineId(CachedRenderPipelineId); fn prepare_taa_pipelines( mut commands: Commands, pipeline_cache: Res, - mut pipelines: ResMut>, - pipeline: Res, - views: Query<(Entity, &ExtractedView, &TemporalAntiAliasSettings)>, + mut pipelines: ResMut>, + pipeline: Res, + views: Query<(Entity, &ExtractedView), With>, ) { - for (entity, view, taa_settings) in &views { - let mut pipeline_key = TAAPipelineKey { - hdr: view.hdr, - reset: taa_settings.reset, - }; - let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key.clone()); + for (entity, view) in &views { + let pipeline_id = + pipelines.specialize(&pipeline_cache, &pipeline, TaaPipelineKey { hdr: view.hdr }); - // Prepare non-reset pipeline anyways - it will be necessary next frame - if pipeline_key.reset { - pipeline_key.reset = false; - pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key); - } - - commands.entity(entity).insert(TAAPipelineId(pipeline_id)); + commands + .entity(entity) + .insert(TemporalAntiAliasPipelineId(pipeline_id)); } } + +#[derive(Component, ShaderType, Clone)] +pub struct TaaUniforms { + user_reset: f32, +} diff --git a/crates/bevy_core_pipeline/src/taa/taa.wgsl b/crates/bevy_core_pipeline/src/taa/taa.wgsl index 53be6b02f0ecf..b2d84ba618f9c 100644 --- a/crates/bevy_core_pipeline/src/taa/taa.wgsl +++ b/crates/bevy_core_pipeline/src/taa/taa.wgsl @@ -1,30 +1,24 @@ // References: // https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail +// https://alextardif.com/TAA.html // http://behindthepixels.io/assets/files/TemporalAA.pdf // http://leiy.cc/publications/TAA/TAA_EG2020_Talk.pdf // https://advances.realtimerendering.com/s2014/index.html#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING -// Controls how much to blend between the current and past samples -// Lower numbers = less of the current sample and more of the past sample = more smoothing -// Values chosen empirically -const DEFAULT_HISTORY_BLEND_RATE: f32 = 0.1; // Default blend rate to use when no confidence in history -const MIN_HISTORY_BLEND_RATE: f32 = 0.015; // Minimum blend rate allowed, to ensure at least some of the current sample is used - -#import bevy_core_pipeline::fullscreen_vertex_shader - @group(0) @binding(0) var view_target: texture_2d; @group(0) @binding(1) var history: texture_2d; @group(0) @binding(2) var motion_vectors: texture_2d; @group(0) @binding(3) var depth: texture_depth_2d; @group(0) @binding(4) var nearest_sampler: sampler; @group(0) @binding(5) var linear_sampler: sampler; +@group(0) @binding(6) var user_reset: f32; struct Output { @location(0) view_target: vec4, @location(1) history: vec4, }; -// TAA is ideally applied after tonemapping, but before post processing +// TAA is ideally applied after tonemapping (if not tonemapping in the main pass), but before post processing // Post processing wants to go before tonemapping, which conflicts // Solution: Put TAA before tonemapping, tonemap TAA input, apply TAA, invert-tonemap TAA output // https://advances.realtimerendering.com/s2014/index.html#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING, slide 20 @@ -65,11 +59,11 @@ fn clip_towards_aabb_center(history_color: vec3, current_color: vec3, } fn sample_history(u: f32, v: f32) -> vec3 { - return textureSample(history, linear_sampler, vec2(u, v)).rgb; + return textureSampleLevel(history, linear_sampler, vec2(u, v), 0.0).rgb; } fn sample_view_target(uv: vec2) -> vec3 { - var sample = textureSample(view_target, nearest_sampler, uv).rgb; + var sample = textureSampleLevel(view_target, nearest_sampler, uv, 0.0).rgb; #ifdef TONEMAP sample = tonemap(sample); #endif @@ -81,50 +75,42 @@ fn taa(@location(0) uv: vec2) -> Output { let texture_size = vec2(textureDimensions(view_target)); let texel_size = 1.0 / texture_size; - // Fetch the current sample - let original_color = textureSample(view_target, nearest_sampler, uv); - var current_color = original_color.rgb; -#ifdef TONEMAP - current_color = tonemap(current_color); -#endif - -#ifndef RESET - // Pick the closest motion_vector from 5 samples (reduces aliasing on the edges of moving entities) - // https://advances.realtimerendering.com/s2014/index.html#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING, slide 27 - let offset = texel_size * 2.0; - let d_uv_tl = uv + vec2(-offset.x, offset.y); - let d_uv_tr = uv + vec2(offset.x, offset.y); - let d_uv_bl = uv + vec2(-offset.x, -offset.y); - let d_uv_br = uv + vec2(offset.x, -offset.y); + // Loop over 3x3 neighborhood of the pre-TAA rendered texture + // https://alextardif.com/TAA.html + var current_color = vec3(0.0); + var moment_1 = vec3(0.0); + var moment_2 = vec3(0.0); + var closest_depth = 0.0; var closest_uv = uv; - let d_tl = textureSample(depth, nearest_sampler, d_uv_tl); - let d_tr = textureSample(depth, nearest_sampler, d_uv_tr); - var closest_depth = textureSample(depth, nearest_sampler, uv); - let d_bl = textureSample(depth, nearest_sampler, d_uv_bl); - let d_br = textureSample(depth, nearest_sampler, d_uv_br); - if d_tl > closest_depth { - closest_uv = d_uv_tl; - closest_depth = d_tl; - } - if d_tr > closest_depth { - closest_uv = d_uv_tr; - closest_depth = d_tr; - } - if d_bl > closest_depth { - closest_uv = d_uv_bl; - closest_depth = d_bl; + var weights = array(0.05556, 0.88889, 0.05556); + for (var x = -1.0; x <= 1.0; x += 1.0) { + for (var y = -1.0; y <= 1.0; y += 1.0) { + let sample_uv = uv + (vec2(x, y) * texel_size); + let sample = sample_view_target(sample_uv); + + // Apply Mitchell-Netravali kernel over the jittered 3x3 neighborhood to reduce softness + let weight = weights[u32(x + 1.0)] * weights[u32(y + 1.0)]; + current_color += sample * weight; + + // Calculate first and second color moments for use with variance clipping + moment_1 += sample; + moment_2 += sample * sample; + + // Find closest pixel to take motion vectors from (reduces aliasing on the edges of moving entities) + let sample_depth = textureSampleLevel(depth, nearest_sampler, sample_uv, 0.0); + if sample_depth > closest_depth { + closest_depth = sample_depth; + closest_uv = sample_uv; + } + } } - if d_br > closest_depth { - closest_uv = d_uv_br; - } - let closest_motion_vector = textureSample(motion_vectors, nearest_sampler, closest_uv).rg; // Reproject to find the equivalent sample from the past // Uses 5-sample Catmull-Rom filtering (reduces blurriness) // Catmull-Rom filtering: https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1 // Ignoring corners: https://www.activision.com/cdn/research/Dynamic_Temporal_Antialiasing_and_Upsampling_in_Call_of_Duty_v4.pdf#page=68 // Technically we should renormalize the weights since we're skipping the corners, but it's basically the same result - let history_uv = uv - closest_motion_vector; + let history_uv = uv - textureSampleLevel(motion_vectors, nearest_sampler, closest_uv, 0.0).rg; let sample_position = history_uv * texture_size; let texel_center = floor(sample_position - 0.5) + 0.5; let f = sample_position - texel_center; @@ -145,52 +131,28 @@ fn taa(@location(0) uv: vec2) -> Output { // Constrain past sample with 3x3 YCoCg variance clipping (reduces ghosting) // YCoCg: https://advances.realtimerendering.com/s2014/index.html#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING, slide 33 // Variance clipping: https://developer.download.nvidia.com/gameworks/events/GDC2016/msalvi_temporal_supersampling.pdf - let s_tl = sample_view_target(uv + vec2(-texel_size.x, texel_size.y)); - let s_tm = sample_view_target(uv + vec2( 0.0, texel_size.y)); - let s_tr = sample_view_target(uv + vec2( texel_size.x, texel_size.y)); - let s_ml = sample_view_target(uv + vec2(-texel_size.x, 0.0)); - let s_mm = RGB_to_YCoCg(current_color); - let s_mr = sample_view_target(uv + vec2( texel_size.x, 0.0)); - let s_bl = sample_view_target(uv + vec2(-texel_size.x, -texel_size.y)); - let s_bm = sample_view_target(uv + vec2( 0.0, -texel_size.y)); - let s_br = sample_view_target(uv + vec2( texel_size.x, -texel_size.y)); - let moment_1 = s_tl + s_tm + s_tr + s_ml + s_mm + s_mr + s_bl + s_bm + s_br; - let moment_2 = (s_tl * s_tl) + (s_tm * s_tm) + (s_tr * s_tr) + (s_ml * s_ml) + (s_mm * s_mm) + (s_mr * s_mr) + (s_bl * s_bl) + (s_bm * s_bm) + (s_br * s_br); let mean = moment_1 / 9.0; let variance = (moment_2 / 9.0) - (mean * mean); let std_deviation = sqrt(max(variance, vec3(0.0))); - history_color = RGB_to_YCoCg(history_color); - history_color = clip_towards_aabb_center(history_color, s_mm, mean - std_deviation, mean + std_deviation); - history_color = YCoCg_to_RGB(history_color); - - // How confident we are that the history is representative of the current frame - var history_confidence = textureSample(history, nearest_sampler, uv).a; - let pixel_motion_vector = abs(closest_motion_vector) * texture_size; - if pixel_motion_vector.x < 0.01 && pixel_motion_vector.y < 0.01 { - // Increment when pixels are not moving - history_confidence += 10.0; - } else { - // Else reset - history_confidence = 1.0; - } + history_color = clip_towards_aabb_center(history_color, current_color, mean - std_deviation, mean + std_deviation); - // Blend current and past sample - // Use more of the history if we're confident in it (reduces noise when there is no motion) - // https://hhoppe.com/supersample.pdf, section 4.1 - let current_color_factor = clamp(1.0 / history_confidence, MIN_HISTORY_BLEND_RATE, DEFAULT_HISTORY_BLEND_RATE); - current_color = mix(history_color, current_color, current_color_factor); -#endif // #ifndef RESET + // Use more of the history if it's been visible for a few frames (reduces noise) + var accumulated_samples = textureSampleLevel(history, nearest_sampler, history_uv, 0.0).a; + // If the history_uv is pointing off-screen, reset accumulated sample count + accumulated_samples *= f32(all(saturate(history_uv) == history_uv)); + accumulated_samples *= user_reset; + accumulated_samples = max(accumulated_samples + 1.0, 8.0); + // Blend current and past sample + current_color = mix(history_color, current_color, 1.0 / accumulated_samples); // Write output to history and view target var out: Output; -#ifdef RESET - let history_confidence = 1.0 / MIN_HISTORY_BLEND_RATE; -#endif - out.history = vec4(current_color, history_confidence); + out.history = vec4(current_color, accumulated_samples); + current_color = YCoCg_to_RGB(current_color); #ifdef TONEMAP current_color = reverse_tonemap(current_color); #endif - out.view_target = vec4(current_color, original_color.a); + out.view_target = vec4(current_color, 1.0); return out; } diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 372757f935148..964b177875050 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -194,7 +194,7 @@ pub fn update_previous_view_projections( query: Query<(Entity, &Camera, &GlobalTransform), (With, With)>, ) { for (entity, camera, camera_transform) in &query { - commands.entity(entity).insert(PreviousViewProjection { + commands.entity(entity).try_insert(PreviousViewProjection { view_proj: camera.projection_matrix() * camera_transform.compute_matrix().inverse(), }); } @@ -214,7 +214,7 @@ pub fn update_mesh_previous_global_transforms( for (entity, transform) in &meshes { commands .entity(entity) - .insert(PreviousGlobalTransform(transform.affine())); + .try_insert(PreviousGlobalTransform(transform.affine())); } } } diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index bf3677918d851..40c4f95af8c16 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -1,10 +1,7 @@ //! This example compares MSAA (Multi-Sample Anti-aliasing), FXAA (Fast Approximate Anti-aliasing), and TAA (Temporal Anti-aliasing). - -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. #![allow(clippy::type_complexity)] -use std::f32::consts::PI; +use std::f32::consts::{FRAC_PI_2, PI, TAU}; use bevy::{ core_pipeline::{ @@ -14,9 +11,11 @@ use bevy::{ }, fxaa::{Fxaa, Sensitivity}, }, + math::vec3, pbr::CascadeShadowConfigBuilder, prelude::*, render::{ + mesh::VertexAttributeValues, render_resource::{Extent3d, SamplerDescriptor, TextureDimension, TextureFormat}, texture::ImageSampler, }, @@ -25,12 +24,19 @@ use bevy::{ fn main() { App::new() .insert_resource(Msaa::Off) + .insert_resource(CameraMovementSettings::default()) .add_plugins((DefaultPlugins, TemporalAntiAliasPlugin)) .add_systems(Startup, setup) .add_systems(Update, (modify_aa, modify_sharpening, update_ui)) .run(); } +#[derive(Resource, Default)] +struct CameraMovementSettings { + rotate_camera: bool, + circle_look_camera: bool, +} + fn modify_aa( keys: Res>, mut camera: Query< @@ -43,6 +49,7 @@ fn modify_aa( >, mut msaa: ResMut, mut commands: Commands, + mut camera_movement_settings: ResMut, ) { let (camera_entity, fxaa, taa) = camera.single_mut(); let mut camera = commands.entity(camera_entity); @@ -114,6 +121,16 @@ fn modify_aa( camera.insert(TemporalAntiAliasBundle::default()); } + + // Rotate Camera + if keys.just_pressed(KeyCode::K) { + camera_movement_settings.rotate_camera = !camera_movement_settings.rotate_camera; + } + + // Circle look camera + if keys.just_pressed(KeyCode::L) { + camera_movement_settings.circle_look_camera = !camera_movement_settings.circle_look_camera; + } } fn modify_sharpening( @@ -141,8 +158,10 @@ fn modify_sharpening( } fn update_ui( - camera: Query< + time: Res