Add port of AMD's Robust Contrast Adaptive Sharpening (#7422)
# Objective
TAA, FXAA, and some other post processing effects can cause the image to
become blurry. Sharpening helps to counteract that.
## Solution
~~This is a port of AMD's Contrast Adaptive Sharpening (I ported it from
the
[SweetFX](https://github.com/CeeJayDK/SweetFX/blob/master/Shaders/CAS.fx)
version, which is still MIT licensed). CAS is a good sharpening
algorithm that is better at avoiding the full screen oversharpening
artifacts that simpler algorithms tend to create.~~
This is a port of AMD's Robust Contrast Adaptive Sharpening (RCAS) which
they developed for FSR 1 ([and continue to use in FSR
2](149cf26e12/src/ffx-fsr2-api/shaders/ffx_fsr1.h (L599))).
RCAS is a good sharpening algorithm that is better at avoiding the full
screen oversharpening artifacts that simpler algorithms tend to create.
---
## Future Work
- Consider porting this to a compute shader for potentially better
performance. (In my testing it is currently ridiculously cheap (0.01ms
in Bistro at 1440p where I'm GPU bound), so this wasn't a priority,
especially since it would increase complexity due to still needing the
non-compute version for webgl2 support).
---
## Changelog
- Added Contrast Adaptive Sharpening.
---------
Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com>
			
			
This commit is contained in:
		
							parent
							
								
									f0f5d79917
								
							
						
					
					
						commit
						09f1bd0be7
					
				| @ -0,0 +1,275 @@ | ||||
| use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state}; | ||||
| use bevy_app::prelude::*; | ||||
| use bevy_asset::{load_internal_asset, HandleUntyped}; | ||||
| use bevy_ecs::{prelude::*, query::QueryItem}; | ||||
| use bevy_reflect::{Reflect, TypeUuid}; | ||||
| use bevy_render::{ | ||||
|     extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, | ||||
|     prelude::Camera, | ||||
|     render_graph::RenderGraph, | ||||
|     render_resource::*, | ||||
|     renderer::RenderDevice, | ||||
|     texture::BevyDefault, | ||||
|     view::{ExtractedView, ViewTarget}, | ||||
|     Render, RenderApp, RenderSet, | ||||
| }; | ||||
| 
 | ||||
| mod node; | ||||
| 
 | ||||
| pub use node::CASNode; | ||||
| 
 | ||||
| /// Applies a contrast adaptive sharpening (CAS) filter to the camera.
 | ||||
| ///
 | ||||
| /// CAS is usually used in combination with shader based anti-aliasing methods
 | ||||
| /// such as FXAA or TAA to regain some of the lost detail from the blurring that they introduce.
 | ||||
| ///
 | ||||
| /// CAS is designed to adjust the amount of sharpening applied to different areas of an image
 | ||||
| /// based on the local contrast. This can help avoid over-sharpening areas with high contrast
 | ||||
| /// and under-sharpening areas with low contrast.
 | ||||
| ///
 | ||||
| /// To use this, add the [`ContrastAdaptiveSharpeningSettings`] component to a 2D or 3D camera.
 | ||||
| #[derive(Component, Reflect, Clone)] | ||||
| #[reflect(Component)] | ||||
| pub struct ContrastAdaptiveSharpeningSettings { | ||||
|     /// Enable or disable sharpening.
 | ||||
|     pub enabled: bool, | ||||
|     /// Adjusts sharpening strength. Higher values increase the amount of sharpening.
 | ||||
|     ///
 | ||||
|     /// Clamped between 0.0 and 1.0.
 | ||||
|     ///
 | ||||
|     /// The default value is 0.6.
 | ||||
|     pub sharpening_strength: f32, | ||||
|     /// Whether to try and avoid sharpening areas that are already noisy.
 | ||||
|     ///
 | ||||
|     /// You probably shouldn't use this, and just leave it set to false.
 | ||||
|     /// You should generally apply any sort of film grain or similar effects after CAS
 | ||||
|     /// and upscaling to avoid artifacts.
 | ||||
|     pub denoise: bool, | ||||
| } | ||||
| 
 | ||||
| impl Default for ContrastAdaptiveSharpeningSettings { | ||||
|     fn default() -> Self { | ||||
|         ContrastAdaptiveSharpeningSettings { | ||||
|             enabled: true, | ||||
|             sharpening_strength: 0.6, | ||||
|             denoise: false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Component, Default, Reflect, Clone)] | ||||
| #[reflect(Component)] | ||||
| pub struct DenoiseCAS(bool); | ||||
| 
 | ||||
| /// The uniform struct extracted from [`ContrastAdaptiveSharpeningSettings`] attached to a [`Camera`].
 | ||||
| /// Will be available for use in the CAS shader.
 | ||||
| #[doc(hidden)] | ||||
| #[derive(Component, ShaderType, Clone)] | ||||
| pub struct CASUniform { | ||||
|     sharpness: f32, | ||||
| } | ||||
| 
 | ||||
| impl ExtractComponent for ContrastAdaptiveSharpeningSettings { | ||||
|     type Query = &'static Self; | ||||
|     type Filter = With<Camera>; | ||||
|     type Out = (DenoiseCAS, CASUniform); | ||||
| 
 | ||||
|     fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> { | ||||
|         if !item.enabled || item.sharpening_strength == 0.0 { | ||||
|             return None; | ||||
|         } | ||||
|         Some(( | ||||
|             DenoiseCAS(item.denoise), | ||||
|             CASUniform { | ||||
|                 // above 1.0 causes extreme artifacts and fireflies
 | ||||
|                 sharpness: item.sharpening_strength.clamp(0.0, 1.0), | ||||
|             }, | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE: HandleUntyped = | ||||
|     HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 6925381244141981602); | ||||
| 
 | ||||
| /// Adds Support for Contrast Adaptive Sharpening (CAS).
 | ||||
| pub struct CASPlugin; | ||||
| 
 | ||||
| impl Plugin for CASPlugin { | ||||
|     fn build(&self, app: &mut App) { | ||||
|         load_internal_asset!( | ||||
|             app, | ||||
|             CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE, | ||||
|             "robust_contrast_adaptive_sharpening.wgsl", | ||||
|             Shader::from_wgsl | ||||
|         ); | ||||
| 
 | ||||
|         app.register_type::<ContrastAdaptiveSharpeningSettings>(); | ||||
|         app.add_plugin(ExtractComponentPlugin::<ContrastAdaptiveSharpeningSettings>::default()); | ||||
|         app.add_plugin(UniformComponentPlugin::<CASUniform>::default()); | ||||
| 
 | ||||
|         let render_app = match app.get_sub_app_mut(RenderApp) { | ||||
|             Ok(render_app) => render_app, | ||||
|             Err(_) => return, | ||||
|         }; | ||||
|         render_app | ||||
|             .init_resource::<CASPipeline>() | ||||
|             .init_resource::<SpecializedRenderPipelines<CASPipeline>>() | ||||
|             .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare)); | ||||
|         { | ||||
|             let cas_node = CASNode::new(&mut render_app.world); | ||||
|             let mut binding = render_app.world.resource_mut::<RenderGraph>(); | ||||
|             let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); | ||||
| 
 | ||||
|             graph.add_node(core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); | ||||
| 
 | ||||
|             graph.add_node_edge( | ||||
|                 core_3d::graph::node::TONEMAPPING, | ||||
|                 core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|             ); | ||||
|             graph.add_node_edge( | ||||
|                 core_3d::graph::node::FXAA, | ||||
|                 core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|             ); | ||||
|             graph.add_node_edge( | ||||
|                 core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|                 core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, | ||||
|             ); | ||||
|         } | ||||
|         { | ||||
|             let cas_node = CASNode::new(&mut render_app.world); | ||||
|             let mut binding = render_app.world.resource_mut::<RenderGraph>(); | ||||
|             let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap(); | ||||
| 
 | ||||
|             graph.add_node(core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); | ||||
| 
 | ||||
|             graph.add_node_edge( | ||||
|                 core_2d::graph::node::TONEMAPPING, | ||||
|                 core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|             ); | ||||
|             graph.add_node_edge( | ||||
|                 core_2d::graph::node::FXAA, | ||||
|                 core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|             ); | ||||
|             graph.add_node_edge( | ||||
|                 core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, | ||||
|                 core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Resource)] | ||||
| pub struct CASPipeline { | ||||
|     texture_bind_group: BindGroupLayout, | ||||
|     sampler: Sampler, | ||||
| } | ||||
| 
 | ||||
| impl FromWorld for CASPipeline { | ||||
|     fn from_world(render_world: &mut World) -> Self { | ||||
|         let render_device = render_world.resource::<RenderDevice>(); | ||||
|         let texture_bind_group = | ||||
|             render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { | ||||
|                 label: Some("sharpening_texture_bind_group_layout"), | ||||
|                 entries: &[ | ||||
|                     BindGroupLayoutEntry { | ||||
|                         binding: 0, | ||||
|                         visibility: ShaderStages::FRAGMENT, | ||||
|                         ty: BindingType::Texture { | ||||
|                             sample_type: TextureSampleType::Float { filterable: true }, | ||||
|                             view_dimension: TextureViewDimension::D2, | ||||
|                             multisampled: false, | ||||
|                         }, | ||||
|                         count: None, | ||||
|                     }, | ||||
|                     BindGroupLayoutEntry { | ||||
|                         binding: 1, | ||||
|                         visibility: ShaderStages::FRAGMENT, | ||||
|                         ty: BindingType::Sampler(SamplerBindingType::Filtering), | ||||
|                         count: None, | ||||
|                     }, | ||||
|                     // CAS Settings
 | ||||
|                     BindGroupLayoutEntry { | ||||
|                         binding: 2, | ||||
|                         ty: BindingType::Buffer { | ||||
|                             ty: BufferBindingType::Uniform, | ||||
|                             has_dynamic_offset: true, | ||||
|                             min_binding_size: Some(CASUniform::min_size()), | ||||
|                         }, | ||||
|                         visibility: ShaderStages::FRAGMENT, | ||||
|                         count: None, | ||||
|                     }, | ||||
|                 ], | ||||
|             }); | ||||
| 
 | ||||
|         let sampler = render_device.create_sampler(&SamplerDescriptor::default()); | ||||
| 
 | ||||
|         CASPipeline { | ||||
|             texture_bind_group, | ||||
|             sampler, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(PartialEq, Eq, Hash, Clone, Copy)] | ||||
| pub struct CASPipelineKey { | ||||
|     texture_format: TextureFormat, | ||||
|     denoise: bool, | ||||
| } | ||||
| 
 | ||||
| impl SpecializedRenderPipeline for CASPipeline { | ||||
|     type Key = CASPipelineKey; | ||||
| 
 | ||||
|     fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { | ||||
|         let mut shader_defs = vec![]; | ||||
|         if key.denoise { | ||||
|             shader_defs.push("RCAS_DENOISE".into()); | ||||
|         } | ||||
|         RenderPipelineDescriptor { | ||||
|             label: Some("contrast_adaptive_sharpening".into()), | ||||
|             layout: vec![self.texture_bind_group.clone()], | ||||
|             vertex: fullscreen_shader_vertex_state(), | ||||
|             fragment: Some(FragmentState { | ||||
|                 shader: CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE.typed(), | ||||
|                 shader_defs, | ||||
|                 entry_point: "fragment".into(), | ||||
|                 targets: vec![Some(ColorTargetState { | ||||
|                     format: key.texture_format, | ||||
|                     blend: None, | ||||
|                     write_mask: ColorWrites::ALL, | ||||
|                 })], | ||||
|             }), | ||||
|             primitive: PrimitiveState::default(), | ||||
|             depth_stencil: None, | ||||
|             multisample: MultisampleState::default(), | ||||
|             push_constant_ranges: Vec::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn prepare_cas_pipelines( | ||||
|     mut commands: Commands, | ||||
|     pipeline_cache: Res<PipelineCache>, | ||||
|     mut pipelines: ResMut<SpecializedRenderPipelines<CASPipeline>>, | ||||
|     sharpening_pipeline: Res<CASPipeline>, | ||||
|     views: Query<(Entity, &ExtractedView, &DenoiseCAS), With<CASUniform>>, | ||||
| ) { | ||||
|     for (entity, view, cas_settings) in &views { | ||||
|         let pipeline_id = pipelines.specialize( | ||||
|             &pipeline_cache, | ||||
|             &sharpening_pipeline, | ||||
|             CASPipelineKey { | ||||
|                 denoise: cas_settings.0, | ||||
|                 texture_format: if view.hdr { | ||||
|                     ViewTarget::TEXTURE_FORMAT_HDR | ||||
|                 } else { | ||||
|                     TextureFormat::bevy_default() | ||||
|                 }, | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         commands.entity(entity).insert(ViewCASPipeline(pipeline_id)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Component)] | ||||
| pub struct ViewCASPipeline(CachedRenderPipelineId); | ||||
| @ -0,0 +1,125 @@ | ||||
| use std::sync::Mutex; | ||||
| 
 | ||||
| use crate::contrast_adaptive_sharpening::ViewCASPipeline; | ||||
| use bevy_ecs::prelude::*; | ||||
| use bevy_ecs::query::QueryState; | ||||
| use bevy_render::{ | ||||
|     extract_component::{ComponentUniforms, DynamicUniformIndex}, | ||||
|     render_graph::{Node, NodeRunError, RenderGraphContext}, | ||||
|     render_resource::{ | ||||
|         BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, Operations, | ||||
|         PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, TextureViewId, | ||||
|     }, | ||||
|     renderer::RenderContext, | ||||
|     view::{ExtractedView, ViewTarget}, | ||||
| }; | ||||
| 
 | ||||
| use super::{CASPipeline, CASUniform}; | ||||
| 
 | ||||
| pub struct CASNode { | ||||
|     query: QueryState< | ||||
|         ( | ||||
|             &'static ViewTarget, | ||||
|             &'static ViewCASPipeline, | ||||
|             &'static DynamicUniformIndex<CASUniform>, | ||||
|         ), | ||||
|         With<ExtractedView>, | ||||
|     >, | ||||
|     cached_bind_group: Mutex<Option<(BufferId, TextureViewId, BindGroup)>>, | ||||
| } | ||||
| 
 | ||||
| impl CASNode { | ||||
|     pub fn new(world: &mut World) -> Self { | ||||
|         Self { | ||||
|             query: QueryState::new(world), | ||||
|             cached_bind_group: Mutex::new(None), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Node for CASNode { | ||||
|     fn update(&mut self, world: &mut World) { | ||||
|         self.query.update_archetypes(world); | ||||
|     } | ||||
| 
 | ||||
|     fn run( | ||||
|         &self, | ||||
|         graph: &mut RenderGraphContext, | ||||
|         render_context: &mut RenderContext, | ||||
|         world: &World, | ||||
|     ) -> Result<(), NodeRunError> { | ||||
|         let view_entity = graph.view_entity(); | ||||
|         let pipeline_cache = world.resource::<PipelineCache>(); | ||||
|         let sharpening_pipeline = world.resource::<CASPipeline>(); | ||||
|         let uniforms = world.resource::<ComponentUniforms<CASUniform>>(); | ||||
| 
 | ||||
|         let Ok((target, pipeline, uniform_index)) = self.query.get_manual(world, view_entity) else { return Ok(()) }; | ||||
| 
 | ||||
|         let uniforms_id = uniforms.buffer().unwrap().id(); | ||||
|         let Some(uniforms) = uniforms.binding() else { return Ok(()) }; | ||||
| 
 | ||||
|         let pipeline = pipeline_cache.get_render_pipeline(pipeline.0).unwrap(); | ||||
| 
 | ||||
|         let view_target = target.post_process_write(); | ||||
|         let source = view_target.source; | ||||
|         let destination = view_target.destination; | ||||
| 
 | ||||
|         let mut cached_bind_group = self.cached_bind_group.lock().unwrap(); | ||||
|         let bind_group = match &mut *cached_bind_group { | ||||
|             Some((buffer_id, texture_id, bind_group)) | ||||
|                 if source.id() == *texture_id && uniforms_id == *buffer_id => | ||||
|             { | ||||
|                 bind_group | ||||
|             } | ||||
|             cached_bind_group => { | ||||
|                 let bind_group = | ||||
|                     render_context | ||||
|                         .render_device() | ||||
|                         .create_bind_group(&BindGroupDescriptor { | ||||
|                             label: Some("cas_bind_group"), | ||||
|                             layout: &sharpening_pipeline.texture_bind_group, | ||||
|                             entries: &[ | ||||
|                                 BindGroupEntry { | ||||
|                                     binding: 0, | ||||
|                                     resource: BindingResource::TextureView(view_target.source), | ||||
|                                 }, | ||||
|                                 BindGroupEntry { | ||||
|                                     binding: 1, | ||||
|                                     resource: BindingResource::Sampler( | ||||
|                                         &sharpening_pipeline.sampler, | ||||
|                                     ), | ||||
|                                 }, | ||||
|                                 BindGroupEntry { | ||||
|                                     binding: 2, | ||||
|                                     resource: uniforms, | ||||
|                                 }, | ||||
|                             ], | ||||
|                         }); | ||||
| 
 | ||||
|                 let (_, _, bind_group) = | ||||
|                     cached_bind_group.insert((uniforms_id, source.id(), bind_group)); | ||||
|                 bind_group | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let pass_descriptor = RenderPassDescriptor { | ||||
|             label: Some("contrast_adaptive_sharpening"), | ||||
|             color_attachments: &[Some(RenderPassColorAttachment { | ||||
|                 view: destination, | ||||
|                 resolve_target: None, | ||||
|                 ops: Operations::default(), | ||||
|             })], | ||||
|             depth_stencil_attachment: None, | ||||
|         }; | ||||
| 
 | ||||
|         let mut render_pass = render_context | ||||
|             .command_encoder() | ||||
|             .begin_render_pass(&pass_descriptor); | ||||
| 
 | ||||
|         render_pass.set_pipeline(pipeline); | ||||
|         render_pass.set_bind_group(0, bind_group, &[uniform_index.index()]); | ||||
|         render_pass.draw(0..3, 0..1); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,98 @@ | ||||
| // Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved. | ||||
| // | ||||
| // Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| // of this software and associated documentation files (the "Software"), to deal | ||||
| // in the Software without restriction, including without limitation the rights | ||||
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| // copies of the Software, and to permit persons to whom the Software is | ||||
| // furnished to do so, subject to the following conditions: | ||||
| // The above copyright notice and this permission notice shall be included in | ||||
| // all copies or substantial portions of the Software. | ||||
| // | ||||
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| // THE SOFTWARE. | ||||
| 
 | ||||
| #import bevy_core_pipeline::fullscreen_vertex_shader | ||||
| 
 | ||||
| struct CASUniforms { | ||||
|     sharpness: f32, | ||||
| }; | ||||
| 
 | ||||
| @group(0) @binding(0) | ||||
| var screenTexture: texture_2d<f32>; | ||||
| @group(0) @binding(1) | ||||
| var samp: sampler; | ||||
| @group(0) @binding(2) | ||||
| var<uniform> uniforms: CASUniforms; | ||||
| 
 | ||||
| // This is set at the limit of providing unnatural results for sharpening. | ||||
| const FSR_RCAS_LIMIT = 0.1875; | ||||
| // -4.0 instead of -1.0 to avoid issues with MSAA. | ||||
| const peakC = vec2<f32>(10.0, -40.0); | ||||
| 
 | ||||
| // Robust Contrast Adaptive Sharpening (RCAS) | ||||
| // Based on the following implementation: | ||||
| // https://github.com/GPUOpen-Effects/FidelityFX-FSR2/blob/ea97a113b0f9cadf519fbcff315cc539915a3acd/src/ffx-fsr2-api/shaders/ffx_fsr1.h#L672 | ||||
| // RCAS is based on the following logic. | ||||
| // RCAS uses a 5 tap filter in a cross pattern (same as CAS), | ||||
| //    W                b | ||||
| //  W 1 W  for taps  d e f  | ||||
| //    W                h | ||||
| // Where 'W' is the negative lobe weight. | ||||
| //  output = (W*(b+d+f+h)+e)/(4*W+1) | ||||
| // RCAS solves for 'W' by seeing where the signal might clip out of the {0 to 1} input range, | ||||
| //  0 == (W*(b+d+f+h)+e)/(4*W+1) -> W = -e/(b+d+f+h) | ||||
| //  1 == (W*(b+d+f+h)+e)/(4*W+1) -> W = (1-e)/(b+d+f+h-4) | ||||
| // Then chooses the 'W' which results in no clipping, limits 'W', and multiplies by the 'sharp' amount. | ||||
| // This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. | ||||
| // So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. | ||||
| // As well as switching from 'e' to either the minimum or maximum (depending on side), to help in energy conservation. | ||||
| // This stabilizes RCAS. | ||||
| // RCAS does a simple highpass which is normalized against the local contrast then shaped, | ||||
| //       0.25 | ||||
| //  0.25  -1  0.25 | ||||
| //       0.25 | ||||
| // This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. | ||||
| // The CAS node runs after tonemapping, so the input will be in the range of 0 to 1. | ||||
| @fragment | ||||
| fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> { | ||||
|     // Algorithm uses minimal 3x3 pixel neighborhood. | ||||
|     //    b | ||||
|     //  d e f | ||||
|     //    h | ||||
|     let b = textureSample(screenTexture, samp, in.uv, vec2<i32>(0, -1)).rgb; | ||||
|     let d = textureSample(screenTexture, samp, in.uv, vec2<i32>(-1, 0)).rgb; | ||||
|     // We need the alpha value of the pixel we're working on for the output | ||||
|     let e = textureSample(screenTexture, samp, in.uv).rgbw; | ||||
|     let f = textureSample(screenTexture, samp, in.uv, vec2<i32>(1, 0)).rgb; | ||||
|     let h = textureSample(screenTexture, samp, in.uv, vec2<i32>(0, 1)).rgb; | ||||
|     // Min and max of ring. | ||||
|     let mn4 = min(min(b, d), min(f, h)); | ||||
|     let mx4 = max(max(b, d), max(f, h)); | ||||
|     // Limiters | ||||
|     // 4.0 to avoid issues with MSAA. | ||||
|     let hitMin = mn4 / (4.0 * mx4); | ||||
|     let hitMax = (peakC.x - mx4) / (peakC.y + 4.0 * mn4); | ||||
|     let lobeRGB = max(-hitMin, hitMax); | ||||
|     var lobe = max(-FSR_RCAS_LIMIT, min(0.0, max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)))) * uniforms.sharpness; | ||||
| #ifdef RCAS_DENOISE | ||||
|     // Luma times 2. | ||||
|     let bL = b.b * 0.5 + (b.r * 0.5 + b.g); | ||||
|     let dL = d.b * 0.5 + (d.r * 0.5 + d.g); | ||||
|     let eL = e.b * 0.5 + (e.r * 0.5 + e.g); | ||||
|     let fL = f.b * 0.5 + (f.r * 0.5 + f.g); | ||||
|     let hL = h.b * 0.5 + (h.r * 0.5 + h.g); | ||||
|     // Noise detection. | ||||
|     var noise = 0.25 * bL + 0.25 * dL + 0.25 * fL + 0.25 * hL - eL;; | ||||
|     noise = saturate(abs(noise) / (max(max(bL, dL), max(fL, hL)) - min(min(bL, dL), min(fL, hL)))); | ||||
|     noise = 1.0 - 0.5 * noise; | ||||
|     // Apply noise removal. | ||||
|     lobe *= noise; | ||||
| #endif | ||||
|     return vec4<f32>((lobe * b + lobe * d + lobe * f + lobe * h + e.rgb) / (4.0 * lobe + 1.0), e.w); | ||||
| } | ||||
| @ -13,6 +13,7 @@ pub mod graph { | ||||
|         pub const TONEMAPPING: &str = "tonemapping"; | ||||
|         pub const FXAA: &str = "fxaa"; | ||||
|         pub const UPSCALING: &str = "upscaling"; | ||||
|         pub const CONTRAST_ADAPTIVE_SHARPENING: &str = "contrast_adaptive_sharpening"; | ||||
|         pub const END_MAIN_PASS_POST_PROCESSING: &str = "end_main_pass_post_processing"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,7 @@ pub mod graph { | ||||
|         pub const TONEMAPPING: &str = "tonemapping"; | ||||
|         pub const FXAA: &str = "fxaa"; | ||||
|         pub const UPSCALING: &str = "upscaling"; | ||||
|         pub const CONTRAST_ADAPTIVE_SHARPENING: &str = "contrast_adaptive_sharpening"; | ||||
|         pub const END_MAIN_PASS_POST_PROCESSING: &str = "end_main_pass_post_processing"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| pub mod blit; | ||||
| pub mod bloom; | ||||
| pub mod clear_color; | ||||
| pub mod contrast_adaptive_sharpening; | ||||
| pub mod core_2d; | ||||
| pub mod core_3d; | ||||
| pub mod fullscreen_vertex_shader; | ||||
| @ -34,6 +35,7 @@ use crate::{ | ||||
|     blit::BlitPlugin, | ||||
|     bloom::BloomPlugin, | ||||
|     clear_color::{ClearColor, ClearColorConfig}, | ||||
|     contrast_adaptive_sharpening::CASPlugin, | ||||
|     core_2d::Core2dPlugin, | ||||
|     core_3d::Core3dPlugin, | ||||
|     fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, | ||||
| @ -72,6 +74,7 @@ impl Plugin for CorePipelinePlugin { | ||||
|             .add_plugin(TonemappingPlugin) | ||||
|             .add_plugin(UpscalingPlugin) | ||||
|             .add_plugin(BloomPlugin) | ||||
|             .add_plugin(FxaaPlugin); | ||||
|             .add_plugin(FxaaPlugin) | ||||
|             .add_plugin(CASPlugin); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ use std::f32::consts::PI; | ||||
| 
 | ||||
| use bevy::{ | ||||
|     core_pipeline::{ | ||||
|         contrast_adaptive_sharpening::ContrastAdaptiveSharpeningSettings, | ||||
|         experimental::taa::{ | ||||
|             TemporalAntiAliasBundle, TemporalAntiAliasPlugin, TemporalAntiAliasSettings, | ||||
|         }, | ||||
| @ -23,7 +24,7 @@ fn main() { | ||||
|         .add_plugins(DefaultPlugins) | ||||
|         .add_plugin(TemporalAntiAliasPlugin) | ||||
|         .add_systems(Startup, setup) | ||||
|         .add_systems(Update, (modify_aa, update_ui)) | ||||
|         .add_systems(Update, (modify_aa, modify_sharpening, update_ui)) | ||||
|         .run(); | ||||
| } | ||||
| 
 | ||||
| @ -112,12 +113,43 @@ fn modify_aa( | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn modify_sharpening( | ||||
|     keys: Res<Input<KeyCode>>, | ||||
|     mut query: Query<&mut ContrastAdaptiveSharpeningSettings>, | ||||
| ) { | ||||
|     for mut cas in &mut query { | ||||
|         if keys.just_pressed(KeyCode::Key0) { | ||||
|             cas.enabled = !cas.enabled; | ||||
|         } | ||||
|         if cas.enabled { | ||||
|             if keys.just_pressed(KeyCode::Minus) { | ||||
|                 cas.sharpening_strength -= 0.1; | ||||
|                 cas.sharpening_strength = cas.sharpening_strength.clamp(0.0, 1.0); | ||||
|             } | ||||
|             if keys.just_pressed(KeyCode::Equals) { | ||||
|                 cas.sharpening_strength += 0.1; | ||||
|                 cas.sharpening_strength = cas.sharpening_strength.clamp(0.0, 1.0); | ||||
|             } | ||||
|             if keys.just_pressed(KeyCode::D) { | ||||
|                 cas.denoise = !cas.denoise; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn update_ui( | ||||
|     camera: Query<(Option<&Fxaa>, Option<&TemporalAntiAliasSettings>), With<Camera>>, | ||||
|     camera: Query< | ||||
|         ( | ||||
|             Option<&Fxaa>, | ||||
|             Option<&TemporalAntiAliasSettings>, | ||||
|             &ContrastAdaptiveSharpeningSettings, | ||||
|         ), | ||||
|         With<Camera>, | ||||
|     >, | ||||
|     msaa: Res<Msaa>, | ||||
|     mut ui: Query<&mut Text>, | ||||
| ) { | ||||
|     let (fxaa, taa) = camera.single(); | ||||
|     let (fxaa, taa, cas_settings) = camera.single(); | ||||
| 
 | ||||
|     let mut ui = ui.single_mut(); | ||||
|     let ui = &mut ui.sections[0].value; | ||||
| @ -201,6 +233,21 @@ fn update_ui( | ||||
|             ui.push_str("(T) Extreme"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if cas_settings.enabled { | ||||
|         ui.push_str("\n\n----------\n\n(0) Sharpening (Enabled)\n"); | ||||
|         ui.push_str(&format!( | ||||
|             "(-/+) Strength: {:.1}\n", | ||||
|             cas_settings.sharpening_strength | ||||
|         )); | ||||
|         if cas_settings.denoise { | ||||
|             ui.push_str("(D) Denoising (Enabled)\n"); | ||||
|         } else { | ||||
|             ui.push_str("(D) Denoising (Disabled)\n"); | ||||
|         } | ||||
|     } else { | ||||
|         ui.push_str("\n\n----------\n\n(0) Sharpening (Disabled)\n"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Set up a simple 3D scene
 | ||||
| @ -261,14 +308,21 @@ fn setup( | ||||
|     }); | ||||
| 
 | ||||
|     // Camera
 | ||||
|     commands.spawn(Camera3dBundle { | ||||
|     commands.spawn(( | ||||
|         Camera3dBundle { | ||||
|             camera: Camera { | ||||
|                 hdr: true, | ||||
|                 ..default() | ||||
|             }, | ||||
|         transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), | ||||
|             transform: Transform::from_xyz(0.7, 0.7, 1.0) | ||||
|                 .looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), | ||||
|             ..default() | ||||
|     }); | ||||
|         }, | ||||
|         ContrastAdaptiveSharpeningSettings { | ||||
|             enabled: false, | ||||
|             ..default() | ||||
|         }, | ||||
|     )); | ||||
| 
 | ||||
|     // UI
 | ||||
|     commands.spawn( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Elabajaba
						Elabajaba