params: BloomQuality enum + tiered web/desktop defaults + Off fallback
BloomQuality { Off, Low, Medium, High } controls pyramid depth (0-3).
Web defaults to Low (1 level, minimal float textures), desktop to High
(3 levels, full cinematic). Off skips all bloom passes and composites
scene-only with ACES — the fallback path for WebGPU browsers without
float-filterable support. The composite samples a 1x1 black texture
when bloom is off.
Note: this task gates Off vs full-On (3-level pyramid always runs when
enabled). Partial pyramid for Low/Medium is deferred to the runtime
rebuild in Task 8.
This commit is contained in:
parent
a7490c0c2f
commit
c06f1543db
@ -1,5 +1,26 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
/// Bloom pyramid depth (number of bloom textures).
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum BloomQuality {
|
||||||
|
Off, // no bloom, scene-only ACES composite
|
||||||
|
Low, // 1 level: brightpass → composite (soft halo)
|
||||||
|
Medium, // 2 levels: brightpass → 1 down → 1 up → composite
|
||||||
|
#[default]
|
||||||
|
High, // 3 levels: brightpass → 2 down → 2 up → composite (full cinematic)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BloomQuality {
|
||||||
|
pub fn levels(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
BloomQuality::Off => 0,
|
||||||
|
BloomQuality::Low => 1,
|
||||||
|
BloomQuality::Medium => 2,
|
||||||
|
BloomQuality::High => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// All tunable black-hole parameters. Edited by the egui panel (Task 17),
|
/// All tunable black-hole parameters. Edited by the egui panel (Task 17),
|
||||||
/// mirrored into BlackHoleUniforms each frame (Task 7).
|
/// mirrored into BlackHoleUniforms each frame (Task 7).
|
||||||
#[derive(Resource, Clone)]
|
#[derive(Resource, Clone)]
|
||||||
@ -32,6 +53,7 @@ pub struct BlackHoleParams {
|
|||||||
pub bloom_threshold: f32,
|
pub bloom_threshold: f32,
|
||||||
pub bloom_strength: f32,
|
pub bloom_strength: f32,
|
||||||
pub exposure: f32,
|
pub exposure: f32,
|
||||||
|
pub bloom_quality: BloomQuality,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlackHoleParams {
|
impl Default for BlackHoleParams {
|
||||||
@ -57,6 +79,7 @@ impl Default for BlackHoleParams {
|
|||||||
bloom_threshold: 1.0,
|
bloom_threshold: 1.0,
|
||||||
bloom_strength: 0.8,
|
bloom_strength: 0.8,
|
||||||
exposure: 1.0,
|
exposure: 1.0,
|
||||||
|
bloom_quality: if cfg!(target_arch = "wasm32") { BloomQuality::Low } else { BloomQuality::High },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,6 +177,15 @@ fn spawn_fullscreen_quad(
|
|||||||
RenderLayers::layer(0),
|
RenderLayers::layer(0),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let levels = params.bloom_quality.levels();
|
||||||
|
let mut bloom_final_handle: Option<Handle<Image>> = None;
|
||||||
|
|
||||||
|
if levels > 0 {
|
||||||
|
// NOTE: when bloom is enabled the full 3-level pyramid (High) always
|
||||||
|
// runs here. Partial pyramid — Low = brightpass-only, Medium = 1 down +
|
||||||
|
// 1 up — is deferred to the runtime rebuild in Task 8, which can spawn
|
||||||
|
// fewer passes. For now this is a simple Off vs On gate.
|
||||||
|
//
|
||||||
// --- Bright-pass (bloom stage [2]): half-res float target ---
|
// --- Bright-pass (bloom stage [2]): half-res float target ---
|
||||||
let bw = ((w as f32 * 0.5) as u32).max(1);
|
let bw = ((w as f32 * 0.5) as u32).max(1);
|
||||||
let bh = ((h as f32 * 0.5) as u32).max(1);
|
let bh = ((h as f32 * 0.5) as u32).max(1);
|
||||||
@ -307,14 +316,30 @@ fn spawn_fullscreen_quad(
|
|||||||
RenderTarget::Image(bloom_final.clone().into()), Msaa::Off, BlurCamera, Nudgable, RenderLayers::layer(6),
|
RenderTarget::Image(bloom_final.clone().into()), Msaa::Off, BlurCamera, Nudgable, RenderLayers::layer(6),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
bloom_final_handle = Some(bloom_final.clone());
|
||||||
|
}
|
||||||
|
|
||||||
// --- Composite quad (draws HDR scene + bloom to the window, ACES tone-mapped) ---
|
// --- Composite quad (draws HDR scene + bloom to the window, ACES tone-mapped) ---
|
||||||
// bloom_final comes from the blur pyramid (Task 5).
|
// When bloom is off (BloomQuality::Off), bloom_final_handle is None and we
|
||||||
|
// fall back to a 1x1 black texture: the composite samples black, yielding a
|
||||||
|
// scene-only ACES composite (no bloom contribution).
|
||||||
|
let bloom_handle = bloom_final_handle.unwrap_or_else(|| {
|
||||||
|
// 1x1 black fallback — composite samples black, effectively scene-only ACES.
|
||||||
|
// 8 zero bytes = one Rgba16Float pixel (16 bits/channel × 4).
|
||||||
|
images.add(Image::new_fill(
|
||||||
|
bevy::render::render_resource::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 },
|
||||||
|
bevy::render::render_resource::TextureDimension::D2,
|
||||||
|
&[0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
TextureFormat::Rgba16Float,
|
||||||
|
bevy::asset::RenderAssetUsages::default(),
|
||||||
|
))
|
||||||
|
});
|
||||||
let composite_mat = composite_materials.add(crate::render::material::CompositeMaterial {
|
let composite_mat = composite_materials.add(crate::render::material::CompositeMaterial {
|
||||||
uniform: crate::render::material::CompositeUniform {
|
uniform: crate::render::material::CompositeUniform {
|
||||||
bloom_strength: 0.8, exposure: 1.0, _pad0: 0.0, _pad1: 0.0,
|
bloom_strength: 0.8, exposure: 1.0, _pad0: 0.0, _pad1: 0.0,
|
||||||
},
|
},
|
||||||
scene: offscreen.clone(),
|
scene: offscreen.clone(),
|
||||||
bloom: bloom_final.clone(),
|
bloom: bloom_handle,
|
||||||
});
|
});
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))),
|
Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user