Skip to content

Commit

Permalink
replace cas with rcas
Browse files Browse the repository at this point in the history
  • Loading branch information
Elabajaba committed Mar 29, 2023
1 parent 8751e56 commit afc3fe0
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 161 deletions.

This file was deleted.

86 changes: 55 additions & 31 deletions crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ mod node;

pub use node::CASNode;

const SHARPEN_MIN: f32 = 0.0;
// I didn't really notice a difference after 2.0, but it might just be the scene I tested it on.
const SHARPEN_MAX: f32 = 4.0;

/// Applies a contrast adaptive sharpening (CAS) filter to the camera.
///
/// CAS is usually used in combination with shader based anti-aliasing methods
Expand All @@ -33,49 +37,58 @@ pub use node::CASNode;
pub struct ContrastAdaptiveSharpeningSettings {
/// Enable or disable sharpening.
pub enabled: bool,
/// Adjusts how the shader adapts to high contrast.
/// Higher values = more high contrast sharpening.
/// Adjusts sharpening strength. Higher values reduce the amount of sharpening.
///
/// Range of 0.0 to 1.0, with 0.0 not being fully off.
pub contrast_adaption: f32,
/// Adjusts sharpening intensity by averaging original pixels to the sharpened result.
/// 0.0 is full strength, 1.0 is half strength, 2.0 is quarter strength, etc.
/// Clamped between 0.0 and 4.0.
///
/// Range of 0.0 to 1.0, with 0.0 being fully off.
pub sharpening_intensity: f32,
}

/// 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 {
contrast_adaption: f32,
sharpening_intensity: f32,
/// The default value is 0.2.
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,
contrast_adaption: 0.1,
sharpening_intensity: 1.0,
sharpening_strength: 0.2,
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 = CASUniform;
type Out = (DenoiseCAS, CASUniform);

fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> {
if !item.enabled {
return None;
}
Some(CASUniform {
contrast_adaption: item.contrast_adaption.clamp(0.0, 1.0),
sharpening_intensity: item.sharpening_intensity.clamp(0.0, 1.0),
})
Some((
DenoiseCAS(item.denoise),
CASUniform {
sharpness: (-(item.sharpening_strength.clamp(SHARPEN_MIN, SHARPEN_MAX))).exp2(),
},
))
}
}

Expand All @@ -90,7 +103,7 @@ impl Plugin for CASPlugin {
load_internal_asset!(
app,
CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
"contrast_adaptive_sharpening.wgsl",
"robust_contrast_adaptive_sharpening.wgsl",
Shader::from_wgsl
);

Expand All @@ -113,6 +126,10 @@ impl Plugin for CASPlugin {

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,
Expand All @@ -129,6 +146,10 @@ impl Plugin for CASPlugin {

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,
Expand Down Expand Up @@ -196,19 +217,24 @@ impl FromWorld for CASPipeline {
#[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: vec![],
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: key.texture_format,
Expand All @@ -224,21 +250,19 @@ impl SpecializedRenderPipeline for CASPipeline {
}
}

pub fn prepare_cas_pipelines(
fn prepare_cas_pipelines(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<CASPipeline>>,
sharpening_pipeline: Res<CASPipeline>,
views: Query<(Entity, &ExtractedView, &CASUniform)>,
views: Query<(Entity, &ExtractedView, &DenoiseCAS), With<CASUniform>>,
) {
for (entity, view, sharpening) in &views {
if sharpening.sharpening_intensity == 0.0 {
continue;
}
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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)
// 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 should 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.g + 0.5 * (b.b + b.r);
let dL = d.g + 0.5 * (d.b + d.r);
let eL = e.g + 0.5 * (e.b + e.r);
let fL = f.g + 0.5 * (f.b + f.r);
let hL = h.g + 0.5 * (h.b + h.r);
// Noise detection.
var noise = 0.25 * (bL + dL + fL + 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 + d + f + h) + e.rgb) / (4.0 * lobe + 1.0), e.w);
}
Loading

0 comments on commit afc3fe0

Please sign in to comment.