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, 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)]
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 /// UV offset for bloom shader. Ideally close to 2.0 / `max_mip_dimension`.
117 /// Only tweak if you are seeing visual artifacts.
118 pub uv_offset: f32,
119}
120
121#[deprecated(since = "0.15.0", note = "Renamed to `Bloom`")]
122pub type BloomSettings = Bloom;
123
124impl Bloom {
125 const DEFAULT_MAX_MIP_DIMENSION: u32 = 512;
126 const DEFAULT_UV_OFFSET: f32 = 0.004;
127
128 /// The default bloom preset.
129 ///
130 /// This uses the [`EnergyConserving`](BloomCompositeMode::EnergyConserving) composite mode.
131 pub const NATURAL: Self = Self {
132 intensity: 0.15,
133 low_frequency_boost: 0.7,
134 low_frequency_boost_curvature: 0.95,
135 high_pass_frequency: 1.0,
136 prefilter: BloomPrefilter {
137 threshold: 0.0,
138 threshold_softness: 0.0,
139 },
140 composite_mode: BloomCompositeMode::EnergyConserving,
141 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
142 uv_offset: Self::DEFAULT_UV_OFFSET,
143 };
144
145 /// A preset that's similar to how older games did bloom.
146 pub const OLD_SCHOOL: Self = Self {
147 intensity: 0.05,
148 low_frequency_boost: 0.7,
149 low_frequency_boost_curvature: 0.95,
150 high_pass_frequency: 1.0,
151 prefilter: BloomPrefilter {
152 threshold: 0.6,
153 threshold_softness: 0.2,
154 },
155 composite_mode: BloomCompositeMode::Additive,
156 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
157 uv_offset: Self::DEFAULT_UV_OFFSET,
158 };
159
160 /// A preset that applies a very strong bloom, and blurs the whole screen.
161 pub const SCREEN_BLUR: Self = Self {
162 intensity: 1.0,
163 low_frequency_boost: 0.0,
164 low_frequency_boost_curvature: 0.0,
165 high_pass_frequency: 1.0 / 3.0,
166 prefilter: BloomPrefilter {
167 threshold: 0.0,
168 threshold_softness: 0.0,
169 },
170 composite_mode: BloomCompositeMode::EnergyConserving,
171 max_mip_dimension: Self::DEFAULT_MAX_MIP_DIMENSION,
172 uv_offset: Self::DEFAULT_UV_OFFSET,
173 };
174}
175
176impl Default for Bloom {
177 fn default() -> Self {
178 Self::NATURAL
179 }
180}
181
182/// Applies a threshold filter to the input image to extract the brightest
183/// regions before blurring them and compositing back onto the original image.
184/// These settings are useful when emulating the 1990s-2000s game look.
185///
186/// # Considerations
187/// * Changing these settings creates a physically inaccurate image
188/// * Changing these settings makes it easy to make the final result look worse
189/// * Non-default prefilter settings should be used in conjunction with [`BloomCompositeMode::Additive`]
190#[derive(Default, Clone, Reflect)]
191pub struct BloomPrefilter {
192 /// Baseline of the quadratic threshold curve (default: 0.0).
193 ///
194 /// RGB values under the threshold curve will not contribute to the effect.
195 pub threshold: f32,
196
197 /// Controls how much to blend between the thresholded and non-thresholded colors (default: 0.0).
198 ///
199 /// 0.0 = Abrupt threshold, no blending
200 /// 1.0 = Fully soft threshold
201 ///
202 /// Values outside of the range [0.0, 1.0] will be clamped.
203 pub threshold_softness: f32,
204}
205
206#[deprecated(since = "0.15.0", note = "Renamed to `BloomPrefilter`")]
207pub type BloomPrefilterSettings = BloomPrefilter;
208
209#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)]
210pub enum BloomCompositeMode {
211 EnergyConserving,
212 Additive,
213}
214
215impl ExtractComponent for Bloom {
216 type QueryData = (&'static Self, &'static Camera);
217
218 type QueryFilter = ();
219 type Out = (Self, BloomUniforms);
220
221 fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
222 match (
223 camera.physical_viewport_rect(),
224 camera.physical_viewport_size(),
225 camera.physical_target_size(),
226 camera.is_active,
227 camera.hdr,
228 ) {
229 (Some(URect { min: origin, .. }), Some(size), Some(target_size), true, true)
230 if size.x != 0 && size.y != 0 =>
231 {
232 let threshold = bloom.prefilter.threshold;
233 let threshold_softness = bloom.prefilter.threshold_softness;
234 let knee = threshold * threshold_softness.clamp(0.0, 1.0);
235
236 let uniform = BloomUniforms {
237 threshold_precomputations: Vec4::new(
238 threshold,
239 threshold - knee,
240 2.0 * knee,
241 0.25 / (knee + 0.00001),
242 ),
243 viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4()
244 / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y)
245 .as_vec4(),
246 aspect: AspectRatio::try_from_pixels(size.x, size.y)
247 .expect("Valid screen size values for Bloom settings")
248 .ratio(),
249 uv_offset: bloom.uv_offset,
250 };
251
252 Some((bloom.clone(), uniform))
253 }
254 _ => None,
255 }
256 }
257}