Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs
9424 views
1
use crate::fxaa::fxaa;
2
use bevy_app::prelude::*;
3
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer};
4
use bevy_camera::Camera;
5
use bevy_core_pipeline::{
6
schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems},
7
FullscreenShader,
8
};
9
use bevy_ecs::{prelude::*, query::QueryItem};
10
use bevy_image::BevyDefault as _;
11
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12
use bevy_render::{
13
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
14
render_resource::{
15
binding_types::{sampler, texture_2d, uniform_buffer},
16
*,
17
},
18
renderer::RenderDevice,
19
sync_component::SyncComponent,
20
view::{ExtractedView, ViewTarget},
21
Render, RenderApp, RenderStartup, RenderSystems,
22
};
23
24
mod node;
25
26
pub(crate) use node::cas;
27
28
/// Applies a contrast adaptive sharpening (CAS) filter to the camera.
29
///
30
/// CAS is usually used in combination with shader based anti-aliasing methods
31
/// such as FXAA or TAA to regain some of the lost detail from the blurring that they introduce.
32
///
33
/// CAS is designed to adjust the amount of sharpening applied to different areas of an image
34
/// based on the local contrast. This can help avoid over-sharpening areas with high contrast
35
/// and under-sharpening areas with low contrast.
36
///
37
/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera.
38
#[derive(Component, Reflect, Clone)]
39
#[reflect(Component, Default, Clone)]
40
pub struct ContrastAdaptiveSharpening {
41
/// Enable or disable sharpening.
42
pub enabled: bool,
43
/// Adjusts sharpening strength. Higher values increase the amount of sharpening.
44
///
45
/// Clamped between 0.0 and 1.0.
46
///
47
/// The default value is 0.6.
48
pub sharpening_strength: f32,
49
/// Whether to try and avoid sharpening areas that are already noisy.
50
///
51
/// You probably shouldn't use this, and just leave it set to false.
52
/// You should generally apply any sort of film grain or similar effects after CAS
53
/// and upscaling to avoid artifacts.
54
pub denoise: bool,
55
}
56
57
impl Default for ContrastAdaptiveSharpening {
58
fn default() -> Self {
59
ContrastAdaptiveSharpening {
60
enabled: true,
61
sharpening_strength: 0.6,
62
denoise: false,
63
}
64
}
65
}
66
67
#[derive(Component, Default, Reflect, Clone)]
68
#[reflect(Component, Default, Clone)]
69
pub struct DenoiseCas(bool);
70
71
/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`].
72
/// Will be available for use in the CAS shader.
73
#[doc(hidden)]
74
#[derive(Component, ShaderType, Clone)]
75
pub struct CasUniform {
76
sharpness: f32,
77
}
78
79
impl SyncComponent for ContrastAdaptiveSharpening {
80
type Out = (DenoiseCas, CasUniform);
81
}
82
83
impl ExtractComponent for ContrastAdaptiveSharpening {
84
type QueryData = &'static Self;
85
type QueryFilter = With<Camera>;
86
87
fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
88
if !item.enabled || item.sharpening_strength == 0.0 {
89
return None;
90
}
91
Some((
92
DenoiseCas(item.denoise),
93
CasUniform {
94
// above 1.0 causes extreme artifacts and fireflies
95
sharpness: item.sharpening_strength.clamp(0.0, 1.0),
96
},
97
))
98
}
99
}
100
101
/// Adds Support for Contrast Adaptive Sharpening (CAS).
102
#[derive(Default)]
103
pub struct CasPlugin;
104
105
impl Plugin for CasPlugin {
106
fn build(&self, app: &mut App) {
107
embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl");
108
109
app.add_plugins((
110
ExtractComponentPlugin::<ContrastAdaptiveSharpening>::default(),
111
UniformComponentPlugin::<CasUniform>::default(),
112
));
113
114
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
115
return;
116
};
117
render_app
118
.add_systems(RenderStartup, init_cas_pipeline)
119
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare))
120
.add_systems(Core3d, cas.after(fxaa).in_set(Core3dSystems::PostProcess))
121
.add_systems(Core2d, cas.after(fxaa).in_set(Core2dSystems::PostProcess));
122
}
123
}
124
125
#[derive(Resource)]
126
pub struct CasPipeline {
127
layout: BindGroupLayoutDescriptor,
128
sampler: Sampler,
129
variants: Variants<RenderPipeline, CasPipelineSpecializer>,
130
}
131
132
pub fn init_cas_pipeline(
133
mut commands: Commands,
134
render_device: Res<RenderDevice>,
135
fullscreen_shader: Res<FullscreenShader>,
136
asset_server: Res<AssetServer>,
137
) {
138
let layout = BindGroupLayoutDescriptor::new(
139
"sharpening_texture_bind_group_layout",
140
&BindGroupLayoutEntries::sequential(
141
ShaderStages::FRAGMENT,
142
(
143
texture_2d(TextureSampleType::Float { filterable: true }),
144
sampler(SamplerBindingType::Filtering),
145
// CAS Settings
146
uniform_buffer::<CasUniform>(true),
147
),
148
),
149
);
150
151
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
152
153
let fragment_shader = load_embedded_asset!(
154
asset_server.as_ref(),
155
"robust_contrast_adaptive_sharpening.wgsl"
156
);
157
158
let variants = Variants::new(
159
CasPipelineSpecializer,
160
RenderPipelineDescriptor {
161
label: Some("contrast_adaptive_sharpening".into()),
162
layout: vec![layout.clone()],
163
vertex: fullscreen_shader.to_vertex_state(),
164
fragment: Some(FragmentState {
165
shader: fragment_shader,
166
..Default::default()
167
}),
168
..Default::default()
169
},
170
);
171
172
commands.insert_resource(CasPipeline {
173
layout,
174
sampler,
175
variants,
176
});
177
}
178
179
#[derive(PartialEq, Eq, Hash, Clone, Copy, SpecializerKey)]
180
pub struct CasPipelineKey {
181
texture_format: TextureFormat,
182
denoise: bool,
183
}
184
185
pub struct CasPipelineSpecializer;
186
187
impl Specializer<RenderPipeline> for CasPipelineSpecializer {
188
type Key = CasPipelineKey;
189
190
fn specialize(
191
&self,
192
key: Self::Key,
193
descriptor: &mut <RenderPipeline as Specializable>::Descriptor,
194
) -> Result<Canonical<Self::Key>, BevyError> {
195
let fragment = descriptor.fragment_mut()?;
196
197
if key.denoise {
198
fragment.shader_defs.push("RCAS_DENOISE".into());
199
}
200
201
fragment.set_target(
202
0,
203
ColorTargetState {
204
format: key.texture_format,
205
blend: None,
206
write_mask: ColorWrites::ALL,
207
},
208
);
209
210
Ok(key)
211
}
212
}
213
214
fn prepare_cas_pipelines(
215
mut commands: Commands,
216
pipeline_cache: Res<PipelineCache>,
217
mut sharpening_pipeline: ResMut<CasPipeline>,
218
views: Query<
219
(Entity, &ExtractedView, &DenoiseCas),
220
Or<(Added<CasUniform>, Changed<DenoiseCas>)>,
221
>,
222
mut removals: RemovedComponents<CasUniform>,
223
) -> Result<(), BevyError> {
224
for entity in removals.read() {
225
commands.entity(entity).remove::<ViewCasPipeline>();
226
}
227
228
for (entity, view, denoise_cas) in &views {
229
let pipeline_id = sharpening_pipeline.variants.specialize(
230
&pipeline_cache,
231
CasPipelineKey {
232
denoise: denoise_cas.0,
233
texture_format: if view.hdr {
234
ViewTarget::TEXTURE_FORMAT_HDR
235
} else {
236
TextureFormat::bevy_default()
237
},
238
},
239
)?;
240
241
commands.entity(entity).insert(ViewCasPipeline(pipeline_id));
242
}
243
244
Ok(())
245
}
246
247
#[derive(Component)]
248
pub struct ViewCasPipeline(CachedRenderPipelineId);
249
250