bevy_core_pipeline/bloom/
settings.rs

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