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 {
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
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),
|
||||
..default()
|
||||
},
|
||||
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