bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.45k stars 3.6k forks source link

Bloom does not work right when combining a `Color::NONE` clear color with render-to-texture #8286

Open SludgePhD opened 1 year ago

SludgePhD commented 1 year ago

Bevy version

Reproduces on 0.10.0 and current master (d193d7f53734ca3d35e55e1d7fc0d4fd20cb97d6)

[Optional] Relevant system information

Tested on Arch Linux and this adapter:

`AdapterInfo { name: "AMD Radeon RX 6700 XT (RADV NAVI22)", vendor: 4098, device: 29663, device_type: DiscreteGpu, driver: "radv", driver_info: "Mesa 23.0.1", backend: Vulkan }`

What you did

I modified the bloom_3d.rs example to this:

Code

```rust //! Illustrates bloom post-processing using HDR and emissive materials. use bevy::{ core_pipeline::{ bloom::{BloomCompositeMode, BloomSettings}, clear_color::ClearColorConfig, tonemapping::Tonemapping, }, prelude::*, render::{ camera::RenderTarget, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, view::RenderLayers, }, }; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; fn main() { App::new() .insert_resource(ClearColor(Color::DARK_GRAY)) .add_plugins(DefaultPlugins) .add_systems(Startup, setup_scene) .add_systems(Update, (update_bloom_settings, bounce_spheres)) .run(); } fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut images: ResMut>, asset_server: Res, ) { let size = Extent3d { width: 1280, height: 720, ..Default::default() }; let mut image = Image { texture_descriptor: TextureDescriptor { label: None, size, dimension: TextureDimension::D2, format: TextureFormat::Bgra8UnormSrgb, mip_level_count: 1, sample_count: 1, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }, ..default() }; image.resize(size); let target = images.add(image); commands.spawn(( Camera3dBundle { camera: Camera { hdr: true, // 1. HDR is required for bloom target: RenderTarget::Image(target.clone()), ..default() }, camera_3d: Camera3d { clear_color: ClearColorConfig::Custom(Color::NONE), ..Default::default() }, tonemapping: Tonemapping::TonyMcMapface, // 2. Using a tonemapper that desaturates to white is recommended transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, BloomSettings::default(), // 3. Enable bloom for the camera )); let compose_layer = RenderLayers::layer(1); commands.spawn(( Camera2dBundle { camera: Camera { order: 1, // should be rendered last ..default() }, camera_2d: Camera2d { clear_color: ClearColorConfig::Custom(Color::NONE), ..Default::default() }, ..Default::default() }, compose_layer, )); commands.spawn(( SpriteBundle { texture: target, ..default() }, compose_layer, )); let material_emissive1 = materials.add(StandardMaterial { emissive: Color::rgb_linear(13.99, 5.32, 2.0), // 4. Put something bright in a dark environment to see the effect ..default() }); let material_emissive2 = materials.add(StandardMaterial { emissive: Color::rgb_linear(2.0, 13.99, 5.32), ..default() }); let material_emissive3 = materials.add(StandardMaterial { emissive: Color::rgb_linear(5.32, 2.0, 13.99), ..default() }); let material_non_emissive = materials.add(StandardMaterial { base_color: Color::GRAY, ..default() }); let mesh = meshes.add( shape::Icosphere { radius: 0.5, subdivisions: 5, } .try_into() .unwrap(), ); for x in -10..10 { for z in -10..10 { let mut hasher = DefaultHasher::new(); (x, z).hash(&mut hasher); let rand = (hasher.finish() - 2) % 6; let material = match rand { 0 => material_emissive1.clone(), 1 => material_emissive2.clone(), 2 => material_emissive3.clone(), 3 | 4 | 5 => material_non_emissive.clone(), _ => unreachable!(), }; commands.spawn(( PbrBundle { mesh: mesh.clone(), material, transform: Transform::from_xyz(x as f32 * 2.0, 0.0, z as f32 * 2.0), ..default() }, Bouncing, )); } } commands.spawn( TextBundle::from_section( "", TextStyle { font: asset_server.load("fonts/FiraMono-Medium.ttf"), font_size: 18.0, color: Color::BLACK, }, ) .with_style(Style { position_type: PositionType::Absolute, bottom: Val::Px(10.0), left: Val::Px(10.0), ..default() }), ); } // ------------------------------------------------------------------------------------------------ fn update_bloom_settings( mut camera: Query<(Entity, Option<&mut BloomSettings>), With>, mut text: Query<&mut Text>, mut commands: Commands, keycode: Res>, time: Res

What went wrong

I expected this to result in basically the same result you get when rendering to the window directly (with the clear color set to Color::NONE). Instead, the bloom effect only appears on the example geometry, not on the non-covered parts of the backing texture.

Additional information

Here's a screenshot of the original bloom_3d.rs example, modified to use a Color::NONE clear color:

screenshot_2023-04-01_23:33:39

Here's one from the modified example code provided above:

screenshot_2023-04-01_23:36:15

SludgePhD commented 1 year ago

Upon further investigation it looks like this is just a consequence of render-to-texture performing proper alpha blending on the texture, while the direct rendering ignores the alpha channel (or rather, the OS compositor ignores it). The alpha value for all pixels outside of geometry stays 0, even if the bloom effects the pixel.

It seems like this would be fixed if the bloom shader computed proper alpha values (and blended those to the target frame buffer) instead of using the blend constant like it does now, which is the same issue as pointed out by this comment:

https://github.com/bevyengine/bevy/blob/d193d7f53734ca3d35e55e1d7fc0d4fd20cb97d6/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs#L96-L102