1mod downsampling_pipeline;
2mod settings;
3mod upsampling_pipeline;
4
5use bevy_image::ToExtents;
6pub use settings::{Bloom, BloomCompositeMode, BloomPrefilter};
7
8use crate::bloom::{
9 downsampling_pipeline::init_bloom_downsampling_pipeline,
10 upsampling_pipeline::init_bloom_upscaling_pipeline,
11};
12use bevy_app::{App, Plugin};
13use bevy_asset::embedded_asset;
14use bevy_color::{Gray, LinearRgba};
15use bevy_core_pipeline::{
16 schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems},
17 tonemapping::tonemapping,
18};
19use bevy_ecs::prelude::*;
20use bevy_math::{ops, UVec2};
21use bevy_render::{
22 camera::ExtractedCamera,
23 diagnostic::RecordDiagnostics,
24 extract_component::{
25 ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin,
26 },
27 render_resource::*,
28 renderer::{RenderContext, RenderDevice, ViewQuery},
29 texture::{CachedTexture, TextureCache},
30 view::ViewTarget,
31 GpuResourceAppExt, Render, RenderApp, RenderStartup, RenderSystems,
32};
33use downsampling_pipeline::{
34 prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds,
35 BloomUniforms,
36};
37use upsampling_pipeline::{
38 prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds,
39};
40
41const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Ufloat;
42
43#[derive(#[automatically_derived]
impl ::core::default::Default for BloomPlugin {
#[inline]
fn default() -> Self { Self }
}Default)]
44pub struct BloomPlugin;
45
46impl Plugin for BloomPlugin {
47 fn build(&self, app: &mut App) {
48 {
{
let mut embedded =
app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
let path =
{
let crate_name =
"bevy_post_process::bloom".split(':').next().unwrap();
::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
"src".as_ref(), "src/bloom/mod.rs".as_ref(),
"bloom.wesl".as_ref())
};
let watched_path =
::bevy_asset::io::embedded::watched_path("src/bloom/mod.rs",
"bloom.wesl");
embedded.insert_asset(watched_path, &path,
b"//! Bloom works by creating an intermediate texture with a bunch of mip levels, each half the size of the previous.\n//! You then downsample each mip (starting with the original texture) to the lower resolution mip under it, going in order.\n//! You then upsample each mip (starting from the smallest mip) and blend with the higher resolution mip above it (ending on the original texture).\n//!\n//! References:\n//! * [COD] - Next Generation Post Processing in Call of Duty - http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare\n//! * [PBB] - Physically Based Bloom - https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom\n\nstruct BloomUniforms {\n threshold_precomputations: vec4<f32>,\n viewport: vec4<f32>,\n scale: vec2<f32>,\n aspect: f32,\n};\n\n@group(0) @binding(0) var input_texture: texture_2d<f32>;\n@group(0) @binding(1) var s: sampler;\n\n@group(0) @binding(2) var<uniform> uniforms: BloomUniforms;\n\n@if(FIRST_DOWNSAMPLE)\n/// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4\nfn soft_threshold(color: vec3<f32>) -> vec3<f32> {\n let brightness = max(color.r, max(color.g, color.b));\n var softness = brightness - uniforms.threshold_precomputations.y;\n softness = clamp(softness, 0.0, uniforms.threshold_precomputations.z);\n softness = softness * softness * uniforms.threshold_precomputations.w;\n var contribution = max(brightness - uniforms.threshold_precomputations.x, softness);\n contribution /= max(brightness, 0.00001); // Prevent division by 0\n return color * contribution;\n}\n\n/// luminance coefficients from Rec. 709.\n/// https://en.wikipedia.org/wiki/Rec._709\nfn tonemapping_luminance(v: vec3<f32>) -> f32 {\n return dot(v, vec3<f32>(0.2126, 0.7152, 0.0722));\n}\n\n/// http://graphicrants.blogspot.com/2013/12/tone-mapping.html\nfn karis_average(color: vec3<f32>) -> f32 {\n // Luminance calculated based on Rec. 709 color primaries.\n // This must be done in *linear* color space.\n let luma = tonemapping_luminance(color) / 4.0;\n return 1.0 / (1.0 + luma);\n}\n\n/// [COD] slide 153\nfn sample_input_13_tap(uv: vec2<f32>) -> vec3<f32> {\n var a: vec3<f32>;\n var b: vec3<f32>;\n var c: vec3<f32>;\n var d: vec3<f32>;\n var e: vec3<f32>;\n var f: vec3<f32>;\n var g: vec3<f32>;\n var h: vec3<f32>;\n var i: vec3<f32>;\n var j: vec3<f32>;\n var k: vec3<f32>;\n var l: vec3<f32>;\n var m: vec3<f32>;\n@if(UNIFORM_SCALE) {\n // This is the fast path. When the bloom scale is uniform, the 13 tap sampling kernel can be\n // expressed with constant offsets.\n //\n // It\'s possible that this isn\'t meaningfully faster than the \"slow\" path. However, because it\n // is hard to test performance on all platforms, and uniform bloom is the most common case, this\n // path was retained when adding non-uniform (anamorphic) bloom. This adds a small, but nonzero,\n // cost to maintainability, but it does help me sleep at night.\n a = textureSample(input_texture, s, uv, vec2<i32>(-2, 2)).rgb;\n b = textureSample(input_texture, s, uv, vec2<i32>(0, 2)).rgb;\n c = textureSample(input_texture, s, uv, vec2<i32>(2, 2)).rgb;\n d = textureSample(input_texture, s, uv, vec2<i32>(-2, 0)).rgb;\n e = textureSample(input_texture, s, uv).rgb;\n f = textureSample(input_texture, s, uv, vec2<i32>(2, 0)).rgb;\n g = textureSample(input_texture, s, uv, vec2<i32>(-2, -2)).rgb;\n h = textureSample(input_texture, s, uv, vec2<i32>(0, -2)).rgb;\n i = textureSample(input_texture, s, uv, vec2<i32>(2, -2)).rgb;\n j = textureSample(input_texture, s, uv, vec2<i32>(-1, 1)).rgb;\n k = textureSample(input_texture, s, uv, vec2<i32>(1, 1)).rgb;\n l = textureSample(input_texture, s, uv, vec2<i32>(-1, -1)).rgb;\n m = textureSample(input_texture, s, uv, vec2<i32>(1, -1)).rgb;\n} @else {\n // This is the flexible, but potentially slower, path for non-uniform sampling. Because the\n // sample is not a constant, and it can fall outside of the limits imposed on constant sample\n // offsets (-8..8), we have to compute the pixel offset in uv coordinates using the size of the\n // texture.\n //\n // It isn\'t clear if this is meaningfully slower than using the offset syntax, the spec doesn\'t\n // mention it anywhere: https://www.w3.org/TR/WGSL/#texturesample, but the fact that the offset\n // syntax uses a const-expr implies that it allows some compiler optimizations - maybe more\n // impactful on mobile?\n let scale = uniforms.scale;\n let ps = scale / vec2<f32>(textureDimensions(input_texture));\n let pl = 2.0 * ps;\n let ns = -1.0 * ps;\n let nl = -2.0 * ps;\n a = textureSample(input_texture, s, uv + vec2<f32>(nl.x, pl.y)).rgb;\n b = textureSample(input_texture, s, uv + vec2<f32>(0.00, pl.y)).rgb;\n c = textureSample(input_texture, s, uv + vec2<f32>(pl.x, pl.y)).rgb;\n d = textureSample(input_texture, s, uv + vec2<f32>(nl.x, 0.00)).rgb;\n e = textureSample(input_texture, s, uv).rgb;\n f = textureSample(input_texture, s, uv + vec2<f32>(pl.x, 0.00)).rgb;\n g = textureSample(input_texture, s, uv + vec2<f32>(nl.x, nl.y)).rgb;\n h = textureSample(input_texture, s, uv + vec2<f32>(0.00, nl.y)).rgb;\n i = textureSample(input_texture, s, uv + vec2<f32>(pl.x, nl.y)).rgb;\n j = textureSample(input_texture, s, uv + vec2<f32>(ns.x, ps.y)).rgb;\n k = textureSample(input_texture, s, uv + vec2<f32>(ps.x, ps.y)).rgb;\n l = textureSample(input_texture, s, uv + vec2<f32>(ns.x, ns.y)).rgb;\n m = textureSample(input_texture, s, uv + vec2<f32>(ps.x, ns.y)).rgb;\n}\n\n@if(FIRST_DOWNSAMPLE) {\n // [COD] slide 168\n //\n // The first downsample pass reads from the rendered frame which may exhibit\n // \'fireflies\' (individual very bright pixels) that should not cause the bloom effect.\n //\n // The first downsample uses a firefly-reduction method proposed by Brian Karis\n // which takes a weighted-average of the samples to limit their luma range to [0, 1].\n // This implementation matches the LearnOpenGL article [PBB].\n var group0 = (a + b + d + e) * (0.125f / 4.0f);\n var group1 = (b + c + e + f) * (0.125f / 4.0f);\n var group2 = (d + e + g + h) * (0.125f / 4.0f);\n var group3 = (e + f + h + i) * (0.125f / 4.0f);\n var group4 = (j + k + l + m) * (0.5f / 4.0f);\n group0 *= karis_average(group0);\n group1 *= karis_average(group1);\n group2 *= karis_average(group2);\n group3 *= karis_average(group3);\n group4 *= karis_average(group4);\n return group0 + group1 + group2 + group3 + group4;\n} @else {\n var sample = (a + c + g + i) * 0.03125;\n sample += (b + d + f + h) * 0.0625;\n sample += (e + j + k + l + m) * 0.125;\n return sample;\n}\n}\n\n/// [COD] slide 162\nfn sample_input_3x3_tent(uv: vec2<f32>) -> vec3<f32> {\n // While this is probably technically incorrect, it makes nonuniform bloom smoother, without\n // having any impact on uniform bloom, which simply evaluates to 1.0 here.\n let frag_size = uniforms.scale / vec2<f32>(textureDimensions(input_texture));\n let x = frag_size.x;\n let y = frag_size.y;\n\n let a = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y + y)).rgb;\n let b = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y + y)).rgb;\n let c = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y + y)).rgb;\n\n let d = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y)).rgb;\n let e = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y)).rgb;\n let f = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y)).rgb;\n\n let g = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y - y)).rgb;\n let h = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y - y)).rgb;\n let i = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y - y)).rgb;\n\n var sample = e * 0.25;\n sample += (b + d + f + h) * 0.125;\n sample += (a + c + g + i) * 0.0625;\n\n return sample;\n}\n\n@if(FIRST_DOWNSAMPLE)\n@fragment\nfn downsample_first(@location(0) output_uv: vec2<f32>) -> @location(0) vec4<f32> {\n let sample_uv = uniforms.viewport.xy + output_uv * uniforms.viewport.zw;\n var sample = sample_input_13_tap(sample_uv);\n // Lower bound of 0.0001 is to avoid propagating multiplying by 0.0 through the\n // downscaling and upscaling which would result in black boxes.\n // The upper bound is to prevent NaNs.\n // with f32::MAX (E+38) Chrome fails with \":value 340282346999999984391321947108527833088.0 cannot be represented as \'f32\'\"\n sample = clamp(sample, vec3<f32>(0.0001), vec3<f32>(3.40282347E+37));\n\n@if(USE_THRESHOLD)\n sample = soft_threshold(sample);\n\n return vec4<f32>(sample, 1.0);\n}\n\n@fragment\nfn downsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {\n return vec4<f32>(sample_input_13_tap(uv), 1.0);\n}\n\n@fragment\nfn upsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {\n return vec4<f32>(sample_input_3x3_tent(uv), 1.0);\n}\n");
}
};embedded_asset!(app, "bloom.wesl");
49
50 app.add_plugins((
51 ExtractComponentPlugin::<Bloom>::default(),
52 UniformComponentPlugin::<BloomUniforms>::default(),
53 ));
54
55 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
56 return;
57 };
58 render_app
59 .init_gpu_resource::<SpecializedRenderPipelines<BloomDownsamplingPipeline>>()
60 .init_gpu_resource::<SpecializedRenderPipelines<BloomUpsamplingPipeline>>()
61 .add_systems(
62 RenderStartup,
63 (
64 init_bloom_downsampling_pipeline,
65 init_bloom_upscaling_pipeline,
66 ),
67 )
68 .add_systems(
69 Render,
70 (
71 prepare_downsampling_pipeline.in_set(RenderSystems::Prepare),
72 prepare_upsampling_pipeline.in_set(RenderSystems::Prepare),
73 prepare_bloom_textures.in_set(RenderSystems::PrepareResources),
74 prepare_bloom_bind_groups.in_set(RenderSystems::PrepareBindGroups),
75 ),
76 )
77 .add_systems(
78 Core3d,
79 bloom.before(tonemapping).in_set(Core3dSystems::PostProcess),
80 )
81 .add_systems(
82 Core2d,
83 bloom.before(tonemapping).in_set(Core2dSystems::PostProcess),
84 );
85 }
86}
87
88pub fn bloom(
89 view: ViewQuery<(
90 &ExtractedCamera,
91 &ViewTarget,
92 &BloomTexture,
93 &BloomBindGroups,
94 &DynamicUniformIndex<BloomUniforms>,
95 &Bloom,
96 &UpsamplingPipelineIds,
97 &BloomDownsamplingPipelineIds,
98 )>,
99 downsampling_pipeline_res: Res<BloomDownsamplingPipeline>,
100 pipeline_cache: Res<PipelineCache>,
101 uniforms: Res<ComponentUniforms<BloomUniforms>>,
102 mut ctx: RenderContext,
103) {
104 let (
105 camera,
106 view_target,
107 bloom_texture,
108 bind_groups,
109 uniform_index,
110 bloom_settings,
111 upsampling_pipeline_ids,
112 downsampling_pipeline_ids,
113 ) = view.into_inner();
114
115 if bloom_settings.intensity == 0.0 || !camera.hdr {
116 return;
117 }
118
119 let (
120 Some(uniforms_binding),
121 Some(downsampling_first_pipeline),
122 Some(downsampling_pipeline),
123 Some(upsampling_pipeline),
124 Some(upsampling_final_pipeline),
125 ) = (
126 uniforms.binding(),
127 pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.first),
128 pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.main),
129 pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_main),
130 pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_final),
131 )
132 else {
133 return;
134 };
135
136 let view_texture = view_target.main_texture_view();
137 let view_texture_unsampled = view_target.get_unsampled_color_attachment();
138
139 let downsampling_first_bind_group = ctx.render_device().create_bind_group(
141 "bloom_downsampling_first_bind_group",
142 &pipeline_cache.get_bind_group_layout(&downsampling_pipeline_res.bind_group_layout),
143 &BindGroupEntries::sequential((
144 view_texture,
145 &bind_groups.sampler,
146 uniforms_binding.clone(),
147 )),
148 );
149
150 let diagnostics = ctx.diagnostic_recorder();
151 let diagnostics = diagnostics.as_deref();
152 let time_span = diagnostics.time_span(ctx.command_encoder(), "bloom");
153
154 let command_encoder = ctx.command_encoder();
155 command_encoder.push_debug_group("bloom");
156
157 {
159 let view = &bloom_texture.view(0);
160 let mut downsampling_first_pass =
161 command_encoder.begin_render_pass(&RenderPassDescriptor {
162 label: Some("bloom_downsampling_first_pass"),
163 color_attachments: &[Some(RenderPassColorAttachment {
164 view,
165 depth_slice: None,
166 resolve_target: None,
167 ops: Operations::default(),
168 })],
169 depth_stencil_attachment: None,
170 timestamp_writes: None,
171 occlusion_query_set: None,
172 multiview_mask: None,
173 });
174 downsampling_first_pass.set_pipeline(downsampling_first_pipeline);
175 downsampling_first_pass.set_bind_group(
176 0,
177 &downsampling_first_bind_group,
178 &[uniform_index.index()],
179 );
180 downsampling_first_pass.draw(0..3, 0..1);
181 }
182
183 for mip in 1..bloom_texture.mip_count {
185 let view = &bloom_texture.view(mip);
186 let mut downsampling_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
187 label: Some("bloom_downsampling_pass"),
188 color_attachments: &[Some(RenderPassColorAttachment {
189 view,
190 depth_slice: None,
191 resolve_target: None,
192 ops: Operations::default(),
193 })],
194 depth_stencil_attachment: None,
195 timestamp_writes: None,
196 occlusion_query_set: None,
197 multiview_mask: None,
198 });
199 downsampling_pass.set_pipeline(downsampling_pipeline);
200 downsampling_pass.set_bind_group(
201 0,
202 &bind_groups.downsampling_bind_groups[mip as usize - 1],
203 &[uniform_index.index()],
204 );
205 downsampling_pass.draw(0..3, 0..1);
206 }
207
208 for mip in (1..bloom_texture.mip_count).rev() {
210 let view = &bloom_texture.view(mip - 1);
211 let mut upsampling_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
212 label: Some("bloom_upsampling_pass"),
213 color_attachments: &[Some(RenderPassColorAttachment {
214 view,
215 depth_slice: None,
216 resolve_target: None,
217 ops: Operations {
218 load: LoadOp::Load,
219 store: StoreOp::Store,
220 },
221 })],
222 depth_stencil_attachment: None,
223 timestamp_writes: None,
224 occlusion_query_set: None,
225 multiview_mask: None,
226 });
227 upsampling_pass.set_pipeline(upsampling_pipeline);
228 upsampling_pass.set_bind_group(
229 0,
230 &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize],
231 &[uniform_index.index()],
232 );
233 let blend = compute_blend_factor(
234 bloom_settings,
235 mip as f32,
236 (bloom_texture.mip_count - 1) as f32,
237 );
238 upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into());
239 upsampling_pass.draw(0..3, 0..1);
240 }
241
242 {
244 let mut upsampling_final_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
245 label: Some("bloom_upsampling_final_pass"),
246 color_attachments: &[Some(view_texture_unsampled)],
247 depth_stencil_attachment: None,
248 timestamp_writes: None,
249 occlusion_query_set: None,
250 multiview_mask: None,
251 });
252 upsampling_final_pass.set_pipeline(upsampling_final_pipeline);
253 upsampling_final_pass.set_bind_group(
254 0,
255 &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize],
256 &[uniform_index.index()],
257 );
258 if let Some(viewport) = camera.viewport.as_ref() {
259 upsampling_final_pass.set_viewport(
260 viewport.physical_position.x as f32,
261 viewport.physical_position.y as f32,
262 viewport.physical_size.x as f32,
263 viewport.physical_size.y as f32,
264 viewport.depth.start,
265 viewport.depth.end,
266 );
267 }
268 let blend = compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32);
269 upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into());
270 upsampling_final_pass.draw(0..3, 0..1);
271 }
272
273 command_encoder.pop_debug_group();
274 time_span.end(ctx.command_encoder());
275}
276
277#[derive(impl bevy_ecs::component::Component for BloomTexture where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::Table;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}Component)]
278pub struct BloomTexture {
279 #[cfg(any(
281 not(feature = "webgl"),
282 not(target_arch = "wasm32"),
283 feature = "webgpu"
284 ))]
285 texture: CachedTexture,
286 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
288 texture: Vec<CachedTexture>,
289 mip_count: u32,
290}
291
292impl BloomTexture {
293 #[cfg(any(
294 not(feature = "webgl"),
295 not(target_arch = "wasm32"),
296 feature = "webgpu"
297 ))]
298 fn view(&self, base_mip_level: u32) -> TextureView {
299 self.texture.texture.create_view(&TextureViewDescriptor {
300 base_mip_level,
301 mip_level_count: Some(1u32),
302 ..Default::default()
303 })
304 }
305 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
306 fn view(&self, base_mip_level: u32) -> TextureView {
307 self.texture[base_mip_level as usize]
308 .texture
309 .create_view(&TextureViewDescriptor {
310 base_mip_level: 0,
311 mip_level_count: Some(1u32),
312 ..Default::default()
313 })
314 }
315}
316
317fn prepare_bloom_textures(
318 mut commands: Commands,
319 mut texture_cache: ResMut<TextureCache>,
320 render_device: Res<RenderDevice>,
321 views: Query<(Entity, &ExtractedCamera, &Bloom)>,
322) {
323 for (entity, camera, bloom) in &views {
324 if let Some(viewport) = camera.physical_viewport_size {
325 let mip_count = bloom.max_mip_dimension.ilog2().max(2) - 1;
327 let mip_height_ratio = if viewport.y != 0 {
328 bloom.max_mip_dimension as f32 / viewport.y as f32
329 } else {
330 0.
331 };
332
333 let texture_descriptor = TextureDescriptor {
334 label: Some("bloom_texture"),
335 size: (viewport.as_vec2() * mip_height_ratio)
336 .round()
337 .as_uvec2()
338 .max(UVec2::ONE)
339 .to_extents(),
340 mip_level_count: mip_count,
341 sample_count: 1,
342 dimension: TextureDimension::D2,
343 format: BLOOM_TEXTURE_FORMAT,
344 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
345 view_formats: &[],
346 };
347
348 #[cfg(any(
349 not(feature = "webgl"),
350 not(target_arch = "wasm32"),
351 feature = "webgpu"
352 ))]
353 let texture = texture_cache.get(&render_device, texture_descriptor);
354 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
355 let texture: Vec<CachedTexture> = (0..mip_count)
356 .map(|mip| {
357 texture_cache.get(
358 &render_device,
359 TextureDescriptor {
360 size: Extent3d {
361 width: (texture_descriptor.size.width >> mip).max(1),
362 height: (texture_descriptor.size.height >> mip).max(1),
363 depth_or_array_layers: 1,
364 },
365 mip_level_count: 1,
366 ..texture_descriptor.clone()
367 },
368 )
369 })
370 .collect();
371
372 commands
373 .entity(entity)
374 .insert(BloomTexture { texture, mip_count });
375 }
376 }
377}
378
379#[derive(impl bevy_ecs::component::Component for BloomBindGroups where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::Table;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}Component)]
380pub struct BloomBindGroups {
381 #[cfg(any(
382 not(feature = "webgl"),
383 not(target_arch = "wasm32"),
384 feature = "webgpu"
385 ))]
386 cache_key: (TextureId, BufferId),
387 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
388 cache_key: (Vec<TextureId>, BufferId),
389 downsampling_bind_groups: Box<[BindGroup]>,
390 upsampling_bind_groups: Box<[BindGroup]>,
391 sampler: Sampler,
392}
393
394fn prepare_bloom_bind_groups(
395 mut commands: Commands,
396 render_device: Res<RenderDevice>,
397 downsampling_pipeline: Res<BloomDownsamplingPipeline>,
398 upsampling_pipeline: Res<BloomUpsamplingPipeline>,
399 views: Query<(Entity, &BloomTexture, Option<&BloomBindGroups>)>,
400 uniforms: Res<ComponentUniforms<BloomUniforms>>,
401 pipeline_cache: Res<PipelineCache>,
402) {
403 let sampler = &downsampling_pipeline.sampler;
404
405 for (entity, bloom_texture, bloom_bind_groups) in &views {
406 #[cfg(any(
407 not(feature = "webgl"),
408 not(target_arch = "wasm32"),
409 feature = "webgpu"
410 ))]
411 let cache_key = (
412 bloom_texture.texture.texture.id(),
413 uniforms.buffer().unwrap().id(),
414 );
415 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
416 let cache_key = (
417 bloom_texture
418 .texture
419 .iter()
420 .map(|tex| tex.texture.id())
421 .collect(),
422 uniforms.buffer().unwrap().id(),
423 );
424
425 if let Some(b) = bloom_bind_groups
426 && b.cache_key == cache_key
427 {
428 continue;
429 }
430
431 let bind_group_count = bloom_texture.mip_count as usize - 1;
432
433 let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count);
434 for mip in 1..bloom_texture.mip_count {
435 downsampling_bind_groups.push(render_device.create_bind_group(
436 "bloom_downsampling_bind_group",
437 &pipeline_cache.get_bind_group_layout(&downsampling_pipeline.bind_group_layout),
438 &BindGroupEntries::sequential((
439 &bloom_texture.view(mip - 1),
440 sampler,
441 uniforms.binding().unwrap(),
442 )),
443 ));
444 }
445
446 let mut upsampling_bind_groups = Vec::with_capacity(bind_group_count);
447 for mip in (0..bloom_texture.mip_count).rev() {
448 upsampling_bind_groups.push(render_device.create_bind_group(
449 "bloom_upsampling_bind_group",
450 &pipeline_cache.get_bind_group_layout(&upsampling_pipeline.bind_group_layout),
451 &BindGroupEntries::sequential((
452 &bloom_texture.view(mip),
453 sampler,
454 uniforms.binding().unwrap(),
455 )),
456 ));
457 }
458
459 commands.entity(entity).insert(BloomBindGroups {
460 cache_key,
461 downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(),
462 upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(),
463 sampler: sampler.clone(),
464 });
465 }
466}
467
468fn compute_blend_factor(bloom: &Bloom, mip: f32, max_mip: f32) -> f32 {
487 let mut lf_boost =
488 (1.0 - ops::powf(
489 1.0 - (mip / max_mip),
490 1.0 / (1.0 - bloom.low_frequency_boost_curvature),
491 )) * bloom.low_frequency_boost;
492 let high_pass_lq = 1.0
493 - (((mip / max_mip) - bloom.high_pass_frequency) / bloom.high_pass_frequency)
494 .clamp(0.0, 1.0);
495 lf_boost *= match bloom.composite_mode {
496 BloomCompositeMode::EnergyConserving => 1.0 - bloom.intensity,
497 BloomCompositeMode::Additive => 1.0,
498 };
499
500 (bloom.intensity + lf_boost) * high_pass_lq
501}