Path: blob/main/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs
6596 views
use bevy_app::prelude::*;1use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};2use bevy_camera::Camera;3use bevy_core_pipeline::{4core_2d::graph::{Core2d, Node2d},5core_3d::graph::{Core3d, Node3d},6FullscreenShader,7};8use bevy_ecs::{prelude::*, query::QueryItem};9use bevy_image::BevyDefault as _;10use bevy_reflect::{std_traits::ReflectDefault, Reflect};11use bevy_render::{12extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},13render_graph::RenderGraphExt,14render_resource::{15binding_types::{sampler, texture_2d, uniform_buffer},16*,17},18renderer::RenderDevice,19view::{ExtractedView, ViewTarget},20Render, RenderApp, RenderStartup, RenderSystems,21};22use bevy_shader::Shader;23use bevy_utils::default;2425mod node;2627pub use node::CasNode;2829/// Applies a contrast adaptive sharpening (CAS) filter to the camera.30///31/// CAS is usually used in combination with shader based anti-aliasing methods32/// such as FXAA or TAA to regain some of the lost detail from the blurring that they introduce.33///34/// CAS is designed to adjust the amount of sharpening applied to different areas of an image35/// based on the local contrast. This can help avoid over-sharpening areas with high contrast36/// and under-sharpening areas with low contrast.37///38/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera.39#[derive(Component, Reflect, Clone)]40#[reflect(Component, Default, Clone)]41pub struct ContrastAdaptiveSharpening {42/// Enable or disable sharpening.43pub enabled: bool,44/// Adjusts sharpening strength. Higher values increase the amount of sharpening.45///46/// Clamped between 0.0 and 1.0.47///48/// The default value is 0.6.49pub sharpening_strength: f32,50/// Whether to try and avoid sharpening areas that are already noisy.51///52/// You probably shouldn't use this, and just leave it set to false.53/// You should generally apply any sort of film grain or similar effects after CAS54/// and upscaling to avoid artifacts.55pub denoise: bool,56}5758impl Default for ContrastAdaptiveSharpening {59fn default() -> Self {60ContrastAdaptiveSharpening {61enabled: true,62sharpening_strength: 0.6,63denoise: false,64}65}66}6768#[derive(Component, Default, Reflect, Clone)]69#[reflect(Component, Default, Clone)]70pub struct DenoiseCas(bool);7172/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`].73/// Will be available for use in the CAS shader.74#[doc(hidden)]75#[derive(Component, ShaderType, Clone)]76pub struct CasUniform {77sharpness: f32,78}7980impl ExtractComponent for ContrastAdaptiveSharpening {81type QueryData = &'static Self;82type QueryFilter = With<Camera>;83type Out = (DenoiseCas, CasUniform);8485fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {86if !item.enabled || item.sharpening_strength == 0.0 {87return None;88}89Some((90DenoiseCas(item.denoise),91CasUniform {92// above 1.0 causes extreme artifacts and fireflies93sharpness: item.sharpening_strength.clamp(0.0, 1.0),94},95))96}97}9899/// Adds Support for Contrast Adaptive Sharpening (CAS).100#[derive(Default)]101pub struct CasPlugin;102103impl Plugin for CasPlugin {104fn build(&self, app: &mut App) {105embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl");106107app.add_plugins((108ExtractComponentPlugin::<ContrastAdaptiveSharpening>::default(),109UniformComponentPlugin::<CasUniform>::default(),110));111112let Some(render_app) = app.get_sub_app_mut(RenderApp) else {113return;114};115render_app116.init_resource::<SpecializedRenderPipelines<CasPipeline>>()117.add_systems(RenderStartup, init_cas_pipeline)118.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare));119120{121render_app122.add_render_graph_node::<CasNode>(Core3d, Node3d::ContrastAdaptiveSharpening)123.add_render_graph_edge(124Core3d,125Node3d::Tonemapping,126Node3d::ContrastAdaptiveSharpening,127)128.add_render_graph_edges(129Core3d,130(131Node3d::Fxaa,132Node3d::ContrastAdaptiveSharpening,133Node3d::EndMainPassPostProcessing,134),135);136}137{138render_app139.add_render_graph_node::<CasNode>(Core2d, Node2d::ContrastAdaptiveSharpening)140.add_render_graph_edge(141Core2d,142Node2d::Tonemapping,143Node2d::ContrastAdaptiveSharpening,144)145.add_render_graph_edges(146Core2d,147(148Node2d::Fxaa,149Node2d::ContrastAdaptiveSharpening,150Node2d::EndMainPassPostProcessing,151),152);153}154}155}156157#[derive(Resource)]158pub struct CasPipeline {159texture_bind_group: BindGroupLayout,160sampler: Sampler,161fullscreen_shader: FullscreenShader,162fragment_shader: Handle<Shader>,163}164165pub fn init_cas_pipeline(166mut commands: Commands,167render_device: Res<RenderDevice>,168fullscreen_shader: Res<FullscreenShader>,169asset_server: Res<AssetServer>,170) {171let texture_bind_group = render_device.create_bind_group_layout(172"sharpening_texture_bind_group_layout",173&BindGroupLayoutEntries::sequential(174ShaderStages::FRAGMENT,175(176texture_2d(TextureSampleType::Float { filterable: true }),177sampler(SamplerBindingType::Filtering),178// CAS Settings179uniform_buffer::<CasUniform>(true),180),181),182);183184let sampler = render_device.create_sampler(&SamplerDescriptor::default());185186commands.insert_resource(CasPipeline {187texture_bind_group,188sampler,189fullscreen_shader: fullscreen_shader.clone(),190fragment_shader: load_embedded_asset!(191asset_server.as_ref(),192"robust_contrast_adaptive_sharpening.wgsl"193),194});195}196197#[derive(PartialEq, Eq, Hash, Clone, Copy)]198pub struct CasPipelineKey {199texture_format: TextureFormat,200denoise: bool,201}202203impl SpecializedRenderPipeline for CasPipeline {204type Key = CasPipelineKey;205206fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {207let mut shader_defs = vec![];208if key.denoise {209shader_defs.push("RCAS_DENOISE".into());210}211RenderPipelineDescriptor {212label: Some("contrast_adaptive_sharpening".into()),213layout: vec![self.texture_bind_group.clone()],214vertex: self.fullscreen_shader.to_vertex_state(),215fragment: Some(FragmentState {216shader: self.fragment_shader.clone(),217shader_defs,218targets: vec![Some(ColorTargetState {219format: key.texture_format,220blend: None,221write_mask: ColorWrites::ALL,222})],223..default()224}),225..default()226}227}228}229230fn prepare_cas_pipelines(231mut commands: Commands,232pipeline_cache: Res<PipelineCache>,233mut pipelines: ResMut<SpecializedRenderPipelines<CasPipeline>>,234sharpening_pipeline: Res<CasPipeline>,235views: Query<236(Entity, &ExtractedView, &DenoiseCas),237Or<(Added<CasUniform>, Changed<DenoiseCas>)>,238>,239mut removals: RemovedComponents<CasUniform>,240) {241for entity in removals.read() {242commands.entity(entity).remove::<ViewCasPipeline>();243}244245for (entity, view, denoise_cas) in &views {246let pipeline_id = pipelines.specialize(247&pipeline_cache,248&sharpening_pipeline,249CasPipelineKey {250denoise: denoise_cas.0,251texture_format: if view.hdr {252ViewTarget::TEXTURE_FORMAT_HDR253} else {254TextureFormat::bevy_default()255},256},257);258259commands.entity(entity).insert(ViewCasPipeline(pipeline_id));260}261}262263#[derive(Component)]264pub struct ViewCasPipeline(CachedRenderPipelineId);265266267