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