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