bevy_post_process/bloom/settings.rs
1use super::downsampling_pipeline::BloomUniforms;
2use bevy_camera::{Camera, Hdr};
3use bevy_ecs::{
4 prelude::Component,
5 query::{QueryItem, With},
6 reflect::ReflectComponent,
7};
8use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4};
9use bevy_reflect::{std_traits::ReflectDefault, Reflect};
10use bevy_render::{extract_component::ExtractComponent, sync_component::SyncComponent};
11
12/// Applies a bloom effect to an HDR-enabled 2d or 3d camera.
13///
14/// Bloom emulates an effect found in real cameras and the human eye,
15/// causing halos to appear around very bright parts of the scene.
16///
17/// See also <https://en.wikipedia.org/wiki/Bloom_(shader_effect)>.
18///
19/// # Usage Notes
20///
21/// Often used in conjunction with `bevy_pbr::StandardMaterial::emissive` for 3d meshes.
22///
23/// Bloom is best used alongside a tonemapping function that desaturates bright colors,
24/// such as [`bevy_core_pipeline::tonemapping::Tonemapping::TonyMcMapface`].
25///
26/// Bevy's implementation uses a parametric curve to blend between a set of
27/// blurred (lower frequency) images generated from the camera's view.
28/// See <https://starlederer.github.io/bloom/> for a visualization of the parametric curve
29/// used in Bevy as well as a visualization of the curve's respective scattering profile.
30#[derive(Component, Reflect, Clone)]
31#[reflect(Component, Default, Clone)]
32#[require(Hdr)]
33pub struct Bloom {
34 /// Controls the baseline of how much the image is scattered (default: 0.15).
35 ///
36 /// This parameter should be used only to control the strength of the bloom
37 /// for the scene as a whole. Increasing it too much will make the scene appear
38 /// blurry and over-exposed.
39 ///
40 /// To make a mesh glow brighter, rather than increase the bloom intensity,
41 /// you should increase the mesh's `emissive` value.
42 ///
43 /// # In energy-conserving mode
44 /// The value represents how likely the light is to scatter.
45 ///
46 /// The value should be between 0.0 and 1.0 where:
47 /// * 0.0 means no bloom
48 /// * 1.0 means the light is scattered as much as possible
49 ///
50 /// # In additive mode
51 /// The value represents how much scattered light is added to
52 /// the image to create the glow effect.
53 ///
54 /// In this configuration:
55 /// * 0.0 means no bloom
56 /// * Greater than 0.0 means a proportionate amount of scattered light is added
57 pub intensity: f32,
58
59 /// Low frequency contribution boost.
60 /// Controls how much more likely the light
61 /// is to scatter completely sideways (low frequency image).
62 ///
63 /// Comparable to a low shelf boost on an equalizer.
64 ///
65 /// # In energy-conserving mode
66 /// The value should be between 0.0 and 1.0 where:
67 /// * 0.0 means low frequency light uses base intensity for blend factor calculation
68 /// * 1.0 means low frequency light contributes at full power
69 ///
70 /// # In additive mode
71 /// The value represents how much scattered light is added to
72 /// the image to create the glow effect.
73 ///
74 /// In this configuration:
75 /// * 0.0 means no bloom
76 /// * Greater than 0.0 means a proportionate amount of scattered light is added
77 pub low_frequency_boost: f32,
78
79 /// Low frequency contribution boost curve.
80 /// Controls the curvature of the blend factor function
81 /// making frequencies next to the lowest ones contribute more.
82 ///
83 /// Somewhat comparable to the Q factor of an equalizer node.
84 ///
85 /// Valid range:
86 /// * 0.0 - base intensity and boosted intensity are linearly interpolated
87 /// * 1.0 - all frequencies below maximum are at boosted intensity level
88 pub low_frequency_boost_curvature: f32,
89
90 /// Tightens how much the light scatters (default: 1.0).
91 ///
92 /// Valid range:
93 /// * 0.0 - maximum scattering angle is 0 degrees (no scattering)
94 /// * 1.0 - maximum scattering angle is 90 degrees
95 pub high_pass_frequency: f32,
96
97 /// Controls the threshold filter used for extracting the brightest regions from the input image
98 /// before blurring them and compositing back onto the original image.
99 ///
100 /// Changing these settings creates a physically inaccurate image and makes it easy to make
101 /// the final result look worse. However, they can be useful when emulating the 1990s-2000s game look.
102 /// See [`BloomPrefilter`] for more information.
103 pub prefilter: BloomPrefilter,
104
105 /// Controls whether bloom textures
106 /// are blended between or added to each other. Useful
107 /// if image brightening is desired and a must-change
108 /// if `prefilter` is used.
109 ///
110 /// # Recommendation
111 /// Set to [`BloomCompositeMode::Additive`] if `prefilter` is
112 /// configured in a non-energy-conserving way,
113 /// otherwise set to [`BloomCompositeMode::EnergyConserving`].
114 pub composite_mode: BloomCompositeMode,
115
116 /// Maximum size of each dimension for the largest mipchain texture used in downscaling/upscaling.
117 /// Only tweak if you are seeing visual artifacts.
118 pub max_mip_dimension: u32,
119
120 /// Amount to stretch the bloom on each axis. Artistic control, can be used to emulate
121 /// anamorphic blur by using a large x-value. For large values, you may need to increase
122 /// [`Bloom::max_mip_dimension`] to reduce sampling artifacts.
123 pub scale: Vec2,
124}
125
126impl Bloom {
127 const DEFAULT_MAX_MIP_DIMENSION: u32 = 512;
128
129 /// The default bloom preset.
130 ///
131 /// This uses the [`EnergyConserving`](BloomCompositeMode::EnergyConserving) composite mode.
132 pub const NATURAL: Self = Self {
133 intensity: 0.15,
134 low_frequency_boost: 0.7,
135 low_frequency_boost_curvature: 0.95,
136 high_pass_frequency: 1.0,
137 prefilter: BloomPrefilter {
138 threshold: 0.0,
139 threshold_softness: 0.0,
140 },
141 composite_mode: BloomCompositeMode::EnergyConserving,
142 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
143 scale: Vec2::ONE,
144 };
145
146 /// Emulates the look of stylized anamorphic bloom, stretched horizontally.
147 pub const ANAMORPHIC: Self = Self {
148 // The larger scale necessitates a larger resolution to reduce artifacts:
149 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION * 2,
150 scale: Vec2::new(4.0, 1.0),
151 ..Self::NATURAL
152 };
153
154 /// A preset that's similar to how older games did bloom.
155 pub const OLD_SCHOOL: Self = Self {
156 intensity: 0.05,
157 low_frequency_boost: 0.7,
158 low_frequency_boost_curvature: 0.95,
159 high_pass_frequency: 1.0,
160 prefilter: BloomPrefilter {
161 threshold: 0.6,
162 threshold_softness: 0.2,
163 },
164 composite_mode: BloomCompositeMode::Additive,
165 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
166 scale: Vec2::ONE,
167 };
168
169 /// A preset that applies a very strong bloom, and blurs the whole screen.
170 pub const SCREEN_BLUR: Self = Self {
171 intensity: 1.0,
172 low_frequency_boost: 0.0,
173 low_frequency_boost_curvature: 0.0,
174 high_pass_frequency: 1.0 / 3.0,
175 prefilter: BloomPrefilter {
176 threshold: 0.0,
177 threshold_softness: 0.0,
178 },
179 composite_mode: BloomCompositeMode::EnergyConserving,
180 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
181 scale: Vec2::ONE,
182 };
183}
184
185impl Default for Bloom {
186 fn default() -> Self {
187 Self::NATURAL
188 }
189}
190
191/// Applies a threshold filter to the input image to extract the brightest
192/// regions before blurring them and compositing back onto the original image.
193/// These settings are useful when emulating the 1990s-2000s game look.
194///
195/// # Considerations
196/// * Changing these settings creates a physically inaccurate image
197/// * Changing these settings makes it easy to make the final result look worse
198/// * Non-default prefilter settings should be used in conjunction with [`BloomCompositeMode::Additive`]
199#[derive(Default, Clone, Reflect)]
200#[reflect(Clone, Default)]
201pub struct BloomPrefilter {
202 /// Baseline of the quadratic threshold curve (default: 0.0).
203 ///
204 /// RGB values under the threshold curve will not contribute to the effect.
205 pub threshold: f32,
206
207 /// Controls how much to blend between the thresholded and non-thresholded colors (default: 0.0).
208 ///
209 /// 0.0 = Abrupt threshold, no blending
210 /// 1.0 = Fully soft threshold
211 ///
212 /// Values outside of the range [0.0, 1.0] will be clamped.
213 pub threshold_softness: f32,
214}
215
216#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)]
217#[reflect(Clone, Hash, PartialEq)]
218pub enum BloomCompositeMode {
219 EnergyConserving,
220 Additive,
221}
222
223impl SyncComponent for Bloom {
224 type Target = (Self, BloomUniforms);
225}
226
227impl ExtractComponent for Bloom {
228 type QueryData = (&'static Self, &'static Camera);
229 type QueryFilter = With<Hdr>;
230 type Out = (Self, BloomUniforms);
231
232 fn extract_component((bloom, camera): QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
233 match (
234 camera.physical_viewport_rect(),
235 camera.physical_viewport_size(),
236 camera.physical_target_size(),
237 camera.is_active,
238 ) {
239 (Some(URect { min: origin, .. }), Some(size), Some(target_size), true)
240 if size.x != 0 && size.y != 0 =>
241 {
242 let threshold = bloom.prefilter.threshold;
243 let threshold_softness = bloom.prefilter.threshold_softness;
244 let knee = threshold * threshold_softness.clamp(0.0, 1.0);
245
246 let uniform = BloomUniforms {
247 threshold_precomputations: Vec4::new(
248 threshold,
249 threshold - knee,
250 2.0 * knee,
251 0.25 / (knee + 0.00001),
252 ),
253 viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4()
254 / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y)
255 .as_vec4(),
256 aspect: AspectRatio::try_from_pixels(size.x, size.y)
257 .expect("Valid screen size values for Bloom settings")
258 .ratio(),
259 scale: bloom.scale,
260 };
261
262 Some((bloom.clone(), uniform))
263 }
264 _ => None,
265 }
266 }
267}