render: offscreen → Rgba16Float + Nudgable/QuadScaleFactor/CompositeQuad markers
Switch the offscreen target from Bgra8UnormSrgb to Rgba16Float for HDR headroom (disk brightness can exceed 1.0 without clamping). Set offscreen camera order to -20 (the bloom pipeline, added next, needs cameras ordered so offscreen renders first). Introduce three marker components: - Nudgable: all render cameras carry it; nudge_camera now queries this instead of a hardcoded Or<(OffscreenCamera, UpscaleCamera)>. - QuadScaleFactor(fx, fy): fraction of offscreen res each quad fills; resize_offscreen rescales each quad independently via this. - CompositeQuad: the final quad renders to the window (rescaled against window res, not offscreen). The upscale path still works (textureSample reads float textures); ACES tone-mapping is added in a later commit.
This commit is contained in:
parent
d32ea10aec
commit
d7fdef541e
@ -33,6 +33,22 @@ pub struct UpscaleCamera;
|
|||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct UpscaleQuad;
|
struct UpscaleQuad;
|
||||||
|
|
||||||
|
/// Marker for any camera that must be nudged each frame (Bevy 0.19 #24448
|
||||||
|
/// workaround). All render cameras carry this.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Nudgable;
|
||||||
|
|
||||||
|
/// Stores the fraction of the offscreen resolution that this quad's target
|
||||||
|
/// fills. Used by resize_offscreen to rescale each quad independently.
|
||||||
|
/// (1.0, 1.0) = full offscreen res; (0.5, 0.5) = half; etc.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct QuadScaleFactor(pub f32, pub f32);
|
||||||
|
|
||||||
|
/// Marks the composite quad (renders to the window, not an offscreen Image).
|
||||||
|
/// resize_offscreen rescales it against the window, not the offscreen res.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct CompositeQuad;
|
||||||
|
|
||||||
pub struct BlackHolePlugin;
|
pub struct BlackHolePlugin;
|
||||||
|
|
||||||
impl Plugin for BlackHolePlugin {
|
impl Plugin for BlackHolePlugin {
|
||||||
@ -84,7 +100,7 @@ fn spawn_fullscreen_quad(
|
|||||||
let offscreen = images.add(Image::new_target_texture(
|
let offscreen = images.add(Image::new_target_texture(
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
TextureFormat::Bgra8UnormSrgb,
|
TextureFormat::Rgba16Float,
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
commands.spawn(OffscreenTarget(offscreen.clone()));
|
commands.spawn(OffscreenTarget(offscreen.clone()));
|
||||||
@ -113,19 +129,21 @@ fn spawn_fullscreen_quad(
|
|||||||
MeshMaterial2d(materials.add(material)),
|
MeshMaterial2d(materials.add(material)),
|
||||||
Transform::default().with_scale(Vec3::new(half_w, half_h, 1.0)),
|
Transform::default().with_scale(Vec3::new(half_w, half_h, 1.0)),
|
||||||
FullscreenQuad,
|
FullscreenQuad,
|
||||||
|
QuadScaleFactor(1.0, 1.0),
|
||||||
RenderLayers::layer(0),
|
RenderLayers::layer(0),
|
||||||
));
|
));
|
||||||
// Offscreen camera: order -1 so it renders before the upscale camera.
|
// Offscreen camera: order -20 so it renders before the upscale camera.
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Camera2d,
|
Camera2d,
|
||||||
Camera {
|
Camera {
|
||||||
order: -1,
|
order: -20,
|
||||||
clear_color: ClearColorConfig::Custom(Color::srgb(0.1, 0.1, 0.1)),
|
clear_color: ClearColorConfig::Custom(Color::srgb(0.1, 0.1, 0.1)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
RenderTarget::Image(offscreen.clone().into()),
|
RenderTarget::Image(offscreen.clone().into()),
|
||||||
Msaa::Off,
|
Msaa::Off,
|
||||||
OffscreenCamera,
|
OffscreenCamera,
|
||||||
|
Nudgable,
|
||||||
RenderLayers::layer(0),
|
RenderLayers::layer(0),
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -137,9 +155,10 @@ fn spawn_fullscreen_quad(
|
|||||||
})),
|
})),
|
||||||
Transform::default().with_scale(Vec3::new(win.width() / 2.0, win.height() / 2.0, 1.0)),
|
Transform::default().with_scale(Vec3::new(win.width() / 2.0, win.height() / 2.0, 1.0)),
|
||||||
UpscaleQuad,
|
UpscaleQuad,
|
||||||
|
CompositeQuad,
|
||||||
RenderLayers::layer(1),
|
RenderLayers::layer(1),
|
||||||
));
|
));
|
||||||
commands.spawn((Camera2d, Msaa::Off, UpscaleCamera, RenderLayers::layer(1)));
|
commands.spawn((Camera2d, Msaa::Off, UpscaleCamera, Nudgable, RenderLayers::layer(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recreate the offscreen Image and rescale both quads on window resize,
|
/// Recreate the offscreen Image and rescale both quads on window resize,
|
||||||
@ -155,8 +174,10 @@ fn resize_offscreen(
|
|||||||
// treat `With<T>` filters as disjoint access, so two such Query params would
|
// treat `With<T>` filters as disjoint access, so two such Query params would
|
||||||
// trip B0001 — they must be grouped in a ParamSet (borrowed one at a time).
|
// trip B0001 — they must be grouped in a ParamSet (borrowed one at a time).
|
||||||
mut quads: ParamSet<(
|
mut quads: ParamSet<(
|
||||||
Query<&mut Transform, With<FullscreenQuad>>,
|
// p0: offscreen + bloom quads — rescaled against offscreen resolution.
|
||||||
Query<&mut Transform, With<UpscaleQuad>>,
|
Query<(&mut Transform, &QuadScaleFactor), Without<CompositeQuad>>,
|
||||||
|
// p1: composite quad — rescaled against window resolution.
|
||||||
|
Query<&mut Transform, With<CompositeQuad>>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
if resized.read().next().is_none() {
|
if resized.read().next().is_none() {
|
||||||
@ -167,15 +188,17 @@ fn resize_offscreen(
|
|||||||
let w = ((win.width() * scale) as u32).max(1);
|
let w = ((win.width() * scale) as u32).max(1);
|
||||||
let h = ((win.height() * scale) as u32).max(1);
|
let h = ((win.height() * scale) as u32).max(1);
|
||||||
if let Ok(handle) = target.single() {
|
if let Ok(handle) = target.single() {
|
||||||
let img = Image::new_target_texture(w, h, TextureFormat::Bgra8UnormSrgb, None);
|
let img = Image::new_target_texture(w, h, TextureFormat::Rgba16Float, None);
|
||||||
// insert returns Result in 0.19 (Err if the asset is locked this frame).
|
// insert returns Result in 0.19 (Err if the asset is locked this frame).
|
||||||
// Resize fires repeatedly while the user drags, so a dropped frame's
|
// Resize fires repeatedly while the user drags, so a dropped frame's
|
||||||
// insert is harmless — it'll succeed on the next WindowResized.
|
// insert is harmless — it'll succeed on the next WindowResized.
|
||||||
let _ = images.insert(handle.0.id(), img);
|
let _ = images.insert(handle.0.id(), img);
|
||||||
}
|
}
|
||||||
for mut t in &mut quads.p0() {
|
// Rescale offscreen + bloom quads against the offscreen resolution.
|
||||||
t.scale = Vec3::new(w as f32 / 2.0, h as f32 / 2.0, 1.0);
|
for (mut t, f) in &mut quads.p0() {
|
||||||
|
t.scale = Vec3::new(w as f32 * f.0 / 2.0, h as f32 * f.1 / 2.0, 1.0);
|
||||||
}
|
}
|
||||||
|
// Rescale the composite quad against the window resolution.
|
||||||
for mut t in &mut quads.p1() {
|
for mut t in &mut quads.p1() {
|
||||||
t.scale = Vec3::new(win.width() / 2.0, win.height() / 2.0, 1.0);
|
t.scale = Vec3::new(win.width() / 2.0, win.height() / 2.0, 1.0);
|
||||||
}
|
}
|
||||||
@ -192,7 +215,7 @@ fn resize_offscreen(
|
|||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn nudge_camera(
|
fn nudge_camera(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut camera: Query<&mut Transform, Or<(With<OffscreenCamera>, With<UpscaleCamera>)>>,
|
mut camera: Query<&mut Transform, With<Nudgable>>,
|
||||||
) {
|
) {
|
||||||
let nudge = (time.elapsed_secs() * 5.0).sin() * 1e-3;
|
let nudge = (time.elapsed_secs() * 5.0).sin() * 1e-3;
|
||||||
for mut t in &mut camera {
|
for mut t in &mut camera {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user