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 TONEMAPPING: &str = "tonemapping"; | ||||||
|         pub const FXAA: &str = "fxaa"; |         pub const FXAA: &str = "fxaa"; | ||||||
|         pub const UPSCALING: &str = "upscaling"; |         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"; |         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 TONEMAPPING: &str = "tonemapping"; | ||||||
|         pub const FXAA: &str = "fxaa"; |         pub const FXAA: &str = "fxaa"; | ||||||
|         pub const UPSCALING: &str = "upscaling"; |         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"; |         pub const END_MAIN_PASS_POST_PROCESSING: &str = "end_main_pass_post_processing"; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| pub mod blit; | pub mod blit; | ||||||
| pub mod bloom; | pub mod bloom; | ||||||
| pub mod clear_color; | pub mod clear_color; | ||||||
|  | pub mod contrast_adaptive_sharpening; | ||||||
| pub mod core_2d; | pub mod core_2d; | ||||||
| pub mod core_3d; | pub mod core_3d; | ||||||
| pub mod fullscreen_vertex_shader; | pub mod fullscreen_vertex_shader; | ||||||
| @ -34,6 +35,7 @@ use crate::{ | |||||||
|     blit::BlitPlugin, |     blit::BlitPlugin, | ||||||
|     bloom::BloomPlugin, |     bloom::BloomPlugin, | ||||||
|     clear_color::{ClearColor, ClearColorConfig}, |     clear_color::{ClearColor, ClearColorConfig}, | ||||||
|  |     contrast_adaptive_sharpening::CASPlugin, | ||||||
|     core_2d::Core2dPlugin, |     core_2d::Core2dPlugin, | ||||||
|     core_3d::Core3dPlugin, |     core_3d::Core3dPlugin, | ||||||
|     fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, |     fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, | ||||||
| @ -72,6 +74,7 @@ impl Plugin for CorePipelinePlugin { | |||||||
|             .add_plugin(TonemappingPlugin) |             .add_plugin(TonemappingPlugin) | ||||||
|             .add_plugin(UpscalingPlugin) |             .add_plugin(UpscalingPlugin) | ||||||
|             .add_plugin(BloomPlugin) |             .add_plugin(BloomPlugin) | ||||||
|             .add_plugin(FxaaPlugin); |             .add_plugin(FxaaPlugin) | ||||||
|  |             .add_plugin(CASPlugin); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ use std::f32::consts::PI; | |||||||
| 
 | 
 | ||||||
| use bevy::{ | use bevy::{ | ||||||
|     core_pipeline::{ |     core_pipeline::{ | ||||||
|  |         contrast_adaptive_sharpening::ContrastAdaptiveSharpeningSettings, | ||||||
|         experimental::taa::{ |         experimental::taa::{ | ||||||
|             TemporalAntiAliasBundle, TemporalAntiAliasPlugin, TemporalAntiAliasSettings, |             TemporalAntiAliasBundle, TemporalAntiAliasPlugin, TemporalAntiAliasSettings, | ||||||
|         }, |         }, | ||||||
| @ -23,7 +24,7 @@ fn main() { | |||||||
|         .add_plugins(DefaultPlugins) |         .add_plugins(DefaultPlugins) | ||||||
|         .add_plugin(TemporalAntiAliasPlugin) |         .add_plugin(TemporalAntiAliasPlugin) | ||||||
|         .add_systems(Startup, setup) |         .add_systems(Startup, setup) | ||||||
|         .add_systems(Update, (modify_aa, update_ui)) |         .add_systems(Update, (modify_aa, modify_sharpening, update_ui)) | ||||||
|         .run(); |         .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( | fn update_ui( | ||||||
|     camera: Query<(Option<&Fxaa>, Option<&TemporalAntiAliasSettings>), With<Camera>>, |     camera: Query< | ||||||
|  |         ( | ||||||
|  |             Option<&Fxaa>, | ||||||
|  |             Option<&TemporalAntiAliasSettings>, | ||||||
|  |             &ContrastAdaptiveSharpeningSettings, | ||||||
|  |         ), | ||||||
|  |         With<Camera>, | ||||||
|  |     >, | ||||||
|     msaa: Res<Msaa>, |     msaa: Res<Msaa>, | ||||||
|     mut ui: Query<&mut Text>, |     mut ui: Query<&mut Text>, | ||||||
| ) { | ) { | ||||||
|     let (fxaa, taa) = camera.single(); |     let (fxaa, taa, cas_settings) = camera.single(); | ||||||
| 
 | 
 | ||||||
|     let mut ui = ui.single_mut(); |     let mut ui = ui.single_mut(); | ||||||
|     let ui = &mut ui.sections[0].value; |     let ui = &mut ui.sections[0].value; | ||||||
| @ -201,6 +233,21 @@ fn update_ui( | |||||||
|             ui.push_str("(T) Extreme"); |             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
 | /// Set up a simple 3D scene
 | ||||||
| @ -261,14 +308,21 @@ fn setup( | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Camera
 |     // Camera
 | ||||||
|     commands.spawn(Camera3dBundle { |     commands.spawn(( | ||||||
|  |         Camera3dBundle { | ||||||
|             camera: Camera { |             camera: Camera { | ||||||
|                 hdr: true, |                 hdr: true, | ||||||
|                 ..default() |                 ..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() |             ..default() | ||||||
|     }); |         }, | ||||||
|  |         ContrastAdaptiveSharpeningSettings { | ||||||
|  |             enabled: false, | ||||||
|  |             ..default() | ||||||
|  |         }, | ||||||
|  |     )); | ||||||
| 
 | 
 | ||||||
|     // UI
 |     // UI
 | ||||||
|     commands.spawn( |     commands.spawn( | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Elabajaba
						Elabajaba