Path: blob/main/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs
9424 views
use crate::fxaa::fxaa;1use bevy_app::prelude::*;2use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer};3use bevy_camera::Camera;4use bevy_core_pipeline::{5schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems},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_resource::{14binding_types::{sampler, texture_2d, uniform_buffer},15*,16},17renderer::RenderDevice,18sync_component::SyncComponent,19view::{ExtractedView, ViewTarget},20Render, RenderApp, RenderStartup, RenderSystems,21};2223mod node;2425pub(crate) use node::cas;2627/// Applies a contrast adaptive sharpening (CAS) filter to the camera.28///29/// CAS is usually used in combination with shader based anti-aliasing methods30/// such as FXAA or TAA to regain some of the lost detail from the blurring that they introduce.31///32/// CAS is designed to adjust the amount of sharpening applied to different areas of an image33/// based on the local contrast. This can help avoid over-sharpening areas with high contrast34/// and under-sharpening areas with low contrast.35///36/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera.37#[derive(Component, Reflect, Clone)]38#[reflect(Component, Default, Clone)]39pub struct ContrastAdaptiveSharpening {40/// Enable or disable sharpening.41pub enabled: bool,42/// Adjusts sharpening strength. Higher values increase the amount of sharpening.43///44/// Clamped between 0.0 and 1.0.45///46/// The default value is 0.6.47pub sharpening_strength: f32,48/// Whether to try and avoid sharpening areas that are already noisy.49///50/// You probably shouldn't use this, and just leave it set to false.51/// You should generally apply any sort of film grain or similar effects after CAS52/// and upscaling to avoid artifacts.53pub denoise: bool,54}5556impl Default for ContrastAdaptiveSharpening {57fn default() -> Self {58ContrastAdaptiveSharpening {59enabled: true,60sharpening_strength: 0.6,61denoise: false,62}63}64}6566#[derive(Component, Default, Reflect, Clone)]67#[reflect(Component, Default, Clone)]68pub struct DenoiseCas(bool);6970/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`].71/// Will be available for use in the CAS shader.72#[doc(hidden)]73#[derive(Component, ShaderType, Clone)]74pub struct CasUniform {75sharpness: f32,76}7778impl SyncComponent for ContrastAdaptiveSharpening {79type Out = (DenoiseCas, CasUniform);80}8182impl ExtractComponent for ContrastAdaptiveSharpening {83type QueryData = &'static Self;84type QueryFilter = With<Camera>;8586fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {87if !item.enabled || item.sharpening_strength == 0.0 {88return None;89}90Some((91DenoiseCas(item.denoise),92CasUniform {93// above 1.0 causes extreme artifacts and fireflies94sharpness: item.sharpening_strength.clamp(0.0, 1.0),95},96))97}98}99100/// Adds Support for Contrast Adaptive Sharpening (CAS).101#[derive(Default)]102pub struct CasPlugin;103104impl Plugin for CasPlugin {105fn build(&self, app: &mut App) {106embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl");107108app.add_plugins((109ExtractComponentPlugin::<ContrastAdaptiveSharpening>::default(),110UniformComponentPlugin::<CasUniform>::default(),111));112113let Some(render_app) = app.get_sub_app_mut(RenderApp) else {114return;115};116render_app117.add_systems(RenderStartup, init_cas_pipeline)118.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare))119.add_systems(Core3d, cas.after(fxaa).in_set(Core3dSystems::PostProcess))120.add_systems(Core2d, cas.after(fxaa).in_set(Core2dSystems::PostProcess));121}122}123124#[derive(Resource)]125pub struct CasPipeline {126layout: BindGroupLayoutDescriptor,127sampler: Sampler,128variants: Variants<RenderPipeline, CasPipelineSpecializer>,129}130131pub fn init_cas_pipeline(132mut commands: Commands,133render_device: Res<RenderDevice>,134fullscreen_shader: Res<FullscreenShader>,135asset_server: Res<AssetServer>,136) {137let layout = BindGroupLayoutDescriptor::new(138"sharpening_texture_bind_group_layout",139&BindGroupLayoutEntries::sequential(140ShaderStages::FRAGMENT,141(142texture_2d(TextureSampleType::Float { filterable: true }),143sampler(SamplerBindingType::Filtering),144// CAS Settings145uniform_buffer::<CasUniform>(true),146),147),148);149150let sampler = render_device.create_sampler(&SamplerDescriptor::default());151152let fragment_shader = load_embedded_asset!(153asset_server.as_ref(),154"robust_contrast_adaptive_sharpening.wgsl"155);156157let variants = Variants::new(158CasPipelineSpecializer,159RenderPipelineDescriptor {160label: Some("contrast_adaptive_sharpening".into()),161layout: vec![layout.clone()],162vertex: fullscreen_shader.to_vertex_state(),163fragment: Some(FragmentState {164shader: fragment_shader,165..Default::default()166}),167..Default::default()168},169);170171commands.insert_resource(CasPipeline {172layout,173sampler,174variants,175});176}177178#[derive(PartialEq, Eq, Hash, Clone, Copy, SpecializerKey)]179pub struct CasPipelineKey {180texture_format: TextureFormat,181denoise: bool,182}183184pub struct CasPipelineSpecializer;185186impl Specializer<RenderPipeline> for CasPipelineSpecializer {187type Key = CasPipelineKey;188189fn specialize(190&self,191key: Self::Key,192descriptor: &mut <RenderPipeline as Specializable>::Descriptor,193) -> Result<Canonical<Self::Key>, BevyError> {194let fragment = descriptor.fragment_mut()?;195196if key.denoise {197fragment.shader_defs.push("RCAS_DENOISE".into());198}199200fragment.set_target(2010,202ColorTargetState {203format: key.texture_format,204blend: None,205write_mask: ColorWrites::ALL,206},207);208209Ok(key)210}211}212213fn prepare_cas_pipelines(214mut commands: Commands,215pipeline_cache: Res<PipelineCache>,216mut sharpening_pipeline: ResMut<CasPipeline>,217views: Query<218(Entity, &ExtractedView, &DenoiseCas),219Or<(Added<CasUniform>, Changed<DenoiseCas>)>,220>,221mut removals: RemovedComponents<CasUniform>,222) -> Result<(), BevyError> {223for entity in removals.read() {224commands.entity(entity).remove::<ViewCasPipeline>();225}226227for (entity, view, denoise_cas) in &views {228let pipeline_id = sharpening_pipeline.variants.specialize(229&pipeline_cache,230CasPipelineKey {231denoise: denoise_cas.0,232texture_format: if view.hdr {233ViewTarget::TEXTURE_FORMAT_HDR234} else {235TextureFormat::bevy_default()236},237},238)?;239240commands.entity(entity).insert(ViewCasPipeline(pipeline_id));241}242243Ok(())244}245246#[derive(Component)]247pub struct ViewCasPipeline(CachedRenderPipelineId);248249250