render: bright-pass material + camera (bloom stage [2])
Adds BrightPassMaterial + brightpass.wgsl: extracts luminance above a soft-knee threshold from the HDR offscreen into a half-res Rgba16Float target (bloom_0). Camera order -19 (after offscreen at -20). The brightpass output is not yet composited (Task 6); this commit only adds the pass and confirms it runs without crashing.
This commit is contained in:
parent
0f33d80fd3
commit
6ccd0a173b
23
assets/shaders/brightpass.wgsl
Normal file
23
assets/shaders/brightpass.wgsl
Normal file
@ -0,0 +1,23 @@
|
||||
#import bevy_sprite::mesh2d_vertex_output::VertexOutput
|
||||
|
||||
struct BrightPassUniform {
|
||||
threshold: f32,
|
||||
_pad0: f32,
|
||||
_pad1: f32,
|
||||
_pad2: f32,
|
||||
};
|
||||
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(0) var<uniform> u: BrightPassUniform;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(1) var tex: texture_2d<f32>;
|
||||
@group(#{MATERIAL_BIND_GROUP}) @binding(2) var samp: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let hdr = textureSample(tex, samp, in.uv).rgb;
|
||||
let lum = dot(hdr, vec3<f32>(0.2126, 0.7152, 0.0722));
|
||||
// Soft knee: smooth roll-off instead of a hard threshold cut.
|
||||
// Fully-bright passes through; near-threshold tapers to zero.
|
||||
let soft = max(lum - u.threshold, 0.0) / (lum + 0.0001);
|
||||
let contribution = hdr * soft;
|
||||
return vec4<f32>(contribution, 1.0);
|
||||
}
|
||||
@ -138,4 +138,20 @@ impl Material2d for UpscaleMaterial {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts luminance above a threshold from the HDR offscreen into a
|
||||
/// half-res float texture (bloom stage [2]). Soft-knee, not hard cut.
|
||||
#[derive(Asset, TypePath, AsBindGroup, Clone)]
|
||||
pub struct BrightPassMaterial {
|
||||
#[uniform(0)]
|
||||
pub threshold: f32,
|
||||
#[texture(1)]
|
||||
#[sampler(2)]
|
||||
pub source: Handle<Image>,
|
||||
}
|
||||
|
||||
impl Material2d for BrightPassMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/brightpass.wgsl".into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -49,6 +49,22 @@ pub struct QuadScaleFactor(pub f32, pub f32);
|
||||
#[derive(Component)]
|
||||
pub struct CompositeQuad;
|
||||
|
||||
/// The half-res Image the bright-pass writes into (bloom stage [2]).
|
||||
#[derive(Component)]
|
||||
pub struct BloomTarget0(pub Handle<Image>);
|
||||
|
||||
/// Camera + quad markers for the bright-pass (for rebuild queries).
|
||||
#[derive(Component)]
|
||||
pub struct BrightPassCamera;
|
||||
#[derive(Component)]
|
||||
pub struct BrightPassQuad;
|
||||
|
||||
/// Camera + quad markers for the blur passes (for rebuild queries).
|
||||
#[derive(Component)]
|
||||
pub struct BlurCamera;
|
||||
#[derive(Component)]
|
||||
pub struct BlurQuad;
|
||||
|
||||
pub struct BlackHolePlugin;
|
||||
|
||||
impl Plugin for BlackHolePlugin {
|
||||
@ -58,6 +74,7 @@ impl Plugin for BlackHolePlugin {
|
||||
.init_resource::<crate::params::BlackHoleParams>()
|
||||
.add_plugins(Material2dPlugin::<BlackHoleMaterial>::default())
|
||||
.add_plugins(Material2dPlugin::<crate::render::material::UpscaleMaterial>::default())
|
||||
.add_plugins(Material2dPlugin::<crate::render::material::BrightPassMaterial>::default())
|
||||
.add_plugins(bevy_egui::EguiPlugin::default())
|
||||
.add_systems(Startup, spawn_fullscreen_quad)
|
||||
.add_systems(Startup, crate::scene::planets::spawn_default_planet)
|
||||
@ -83,6 +100,7 @@ fn spawn_fullscreen_quad(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<BlackHoleMaterial>>,
|
||||
mut upscale_materials: ResMut<Assets<crate::render::material::UpscaleMaterial>>,
|
||||
mut bp_materials: ResMut<Assets<crate::render::material::BrightPassMaterial>>,
|
||||
mut buffers: ResMut<Assets<ShaderBuffer>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
window: Query<&Window>,
|
||||
@ -147,6 +165,38 @@ fn spawn_fullscreen_quad(
|
||||
RenderLayers::layer(0),
|
||||
));
|
||||
|
||||
// --- Bright-pass (bloom stage [2]): half-res float target ---
|
||||
let bw = ((w as f32 * 0.5) as u32).max(1);
|
||||
let bh = ((h as f32 * 0.5) as u32).max(1);
|
||||
let bloom0 = images.add(Image::new_target_texture(
|
||||
bw, bh, TextureFormat::Rgba16Float, None,
|
||||
));
|
||||
commands.spawn(BloomTarget0(bloom0.clone()));
|
||||
commands.spawn((
|
||||
Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))),
|
||||
MeshMaterial2d(bp_materials.add(crate::render::material::BrightPassMaterial {
|
||||
threshold: 1.0,
|
||||
source: offscreen.clone(),
|
||||
})),
|
||||
Transform::default().with_scale(Vec3::new(bw as f32 / 2.0, bh as f32 / 2.0, 1.0)),
|
||||
BrightPassQuad,
|
||||
QuadScaleFactor(0.5, 0.5),
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
commands.spawn((
|
||||
Camera2d,
|
||||
Camera {
|
||||
order: -19,
|
||||
clear_color: ClearColorConfig::Custom(Color::srgb(0.0, 0.0, 0.0)),
|
||||
..default()
|
||||
},
|
||||
RenderTarget::Image(bloom0.clone().into()),
|
||||
Msaa::Off,
|
||||
BrightPassCamera,
|
||||
Nudgable,
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
|
||||
// --- Upscale quad (draws offscreen Image to the window) ---
|
||||
commands.spawn((
|
||||
Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user