https://bevyengine.org/news/bevy-0-9/ [ ] [icon-hambu] Bevy Engine Bevy Engine News Bevy Engine [icon-times] * Learn * News * Community * Assets * Examples Donate heart icon Get Started Bevy 0.9 Posted on November 12, 2022 by Carter Anderson ( [github_gre] @cart [twitter_gr] @cart_cart [youtube_gr] cartdev ) [bloom_lion] Thanks to 159 contributors, 430 pull requests, community reviewers, and our generous sponsors, I'm happy to announce the Bevy 0.9 release on crates.io! For those who don't know, Bevy is a refreshingly simple data-driven game engine built in Rust. You can check out our Quick Start Guide to try it today. It's free and open source forever! You can grab the full source code on GitHub. Check out Bevy Assets for a collection of community-developed plugins, games, and learning resources. To update an existing Bevy App or Plugin to Bevy 0.9, check out our 0.8 to 0.9 Migration Guide. Since our last release a few months ago we've added a ton of new features, bug fixes, and quality of life tweaks, but here are some of the highlights: * HDR Post Processing, Tonemapping, and Bloom: Bevy has a new HDR post processing and tonemapping pipeline, which we used to implement the "bloom" post processing effect! * FXAA: Fast approximate anti-aliasing was added, which gives users a new cheap option for screen space anti-aliasing. * Deband Dithering: Hide gradient precision errors with this new post processing effect! * Other Post Processing Improvements: View target double buffering and automatic render target format handling. * New Scene Format: Bevy's new scene format is smaller, simpler to compose manually, and easier to read. Comes in both "human readable" and "binary" variants! * Code Driven Scene Construction: Build scenes dynamically from an existing app using queries and specific entity references. * Improved Entity/Component APIs: Spawning entities with components is now simpler and more ergonomic than ever! * Exclusive System Rework: Exclusive systems (systems with unique ECS World access) are now just "normal" systems with significantly improved usability. * Enum Reflection: Bevy Reflect can now reflect enum types, which exposes them to Bevy's scene system and opens doors to editor tooling for enums. * Time Shader Globals: Time is now passed to shaders as a global, making time-driven animation in custom shaders easy! * Plugin Settings: Plugins can now have settings, which can be overridden in plugin groups, simplifying the plugin configuration story. * Bevy UI Z-Indices: Control how UI elements stack on top of each other using local and global z-indices HDR Post Processing, Tonemapping, and Bloom # authors: @ChangeCaps, @jakobhellermann, @cart, @JMS55 Bevy now supports the "bloom" post processing effect, backed by a ton of internal improvements to our HDR (High Dynamic Range) render pipeline. bloom Bloom creates a "blurred" effect around bright lights, which emulates how cameras (and our eyes) often perceive light in the real world. High quality bloom builds on top of HDR render pipelines, which represents light and color using more than the standard 8 bits per channel (rgba) used elsewhere. In previous releases Bevy already did HDR lighting internally in its PBR shader, but because we were rendering to a "normal" (low dynamic range) texture, we had to lose the extra high dynamic range information when we mapped the HDR lighting to the LDR texture (using a process called tonemapping). In Bevy 0.9, you can now configure cameras to render to HDR textures, which will preserve the high dynamic range information after the "main pass" is finished rendering: Camera { // Currently this defaults to false, but we will likely // switch this to true by default in future releases hdr: true, ..default() } This enables post processing effects, such as bloom, to have access to the raw HDR information. When HDR textures are enabled, we delay "tonemapping" until after "HDR post processing effects" have run in our Render Graph. Bloom is enabled by adding a BloomSettings component to a camera with HDR textures enabled: commands.spawn(( Camera3dBundle { camera: Camera { hdr: true, ..default() }, ..default() }, BloomSettings::default(), )); The bloom effect can be overbearing if misconfigured. BloomSettings has a number of options to tune it, but the most relevant is intensity, which can (and should) be used to adjust how much the effect is applied. Seriously ... this effect can be obnoxious: too much bloom In most cases, it is best to err on the side of subtlety. HDR rendering is also available in 2D, which means you can also use bloom effects in 2D! 2D bloom FXAA: Fast Approximate Anti-Aliasing # authors: @DGriffin91, @cart Bevy 0.9 adds support for FXAA (fast approximate anti-aliasing). FXAA is a popular (and cheap!) anti-aliasing approach that uses luminance data contrast to identify edges and blur them: no_aa fxaa Bevy already has support for MSAA (multisample anti-aliasing), which does multiple samples when rendering geometry edges, which makes those edges crisper: msaa Picking an anti-aliasing implementation is all about tradeoffs: * MSAA: Crisp, high quality geometry edges. Leaves other parts of the image (such as textures and shadows) untouched, which can be a pro (crisper outputs) or a con (more aliasing). More expensive than FXAA. * FXAA: Considers the entire image when blurring, including textures, which can be a pro (textures and shadows get anti-aliased) or a con (the image gets blurrier as a whole). Cheap to run (a good choice for mobile or web AA). Now that our post processing pipeline is maturing, we plan on adding even more anti-aliasing options in future Bevy releases. We already have TAA (temporal anti-aliasing) and SMAA (subpixel morphological anti-aliasing) implementations in the works! Deband Dithering # authors: @aevyrie "Color banding" is a known limitation when using 8 bit color channels (which are required by pretty much every device / screen). This is most visible when trying to render smooth gradients for low noise textures (ex: the lighting on a "pure green" material): banding If you look closely at the green plane or the tan cube, you will notice distinct bands for each shade of color. A popular solution to this problem is to "dither" the final image. Bevy 0.9 now performs "deband dithering" by default in the tonemapping stage: debanding You can enable and disable this per-camera: commands.spawn(Camera3dBundle { tonemapping: Tonemapping::Enabled { deband_dither: true, }, ..default() }); Post Processing: View Target Double Buffering # authors: @cart Rendering post processing effects requires both an input texture (containing the "current" render) and an output texture (the "new" render with the effect applied). Previous versions of Bevy only had one main "view target" image. This meant that naively, post processing effects would need to manage and render to their own "intermediate" texture, then write it back to the main target. This is clearly inefficient, as we have a new texture allocation for each effect and we have the extra work of copying the intermediate texture back to the main texture. To solve this, in Bevy 0.9 we now "double buffer" our view target textures, which mean we have two copies of them that we flip between. At a given moment in time, one is the current "main" texture and the other is the "next" main texture. Post processing effect developers can now trigger a "post process write", which returns a source and destination texture. It assumes that an effect will write source to destination (with or without modifications). destination will then become the new "main" texture. let post_process = view_target.post_process_write(); render_some_effect(render_context, post_process.source, post_process.destination); This reduces the complexity burden on post processing effect developers and keeps our pipeline nice and efficient. The new FXAA effect was implemented using this new system. Post processing plugin developers can use that implementation as a reference. Improved Render Target Texture Format Handling # authors: @VitalyAnkh, @cart Bevy 0.9 now detects and uses each window's / surface's preferred TextureFormat, rather than using hard-coded compile-time-selected per-platform formats. This means that we automatically support uncommon platforms and configurations. Additionally, Bevy's main passes and post processing passes now render to stable / consistent TextureFormats (ex: Rgba16Float for HDR). We do a final blit from these "standard" textures to the final render target's preferred format. This simplifies render pipeline construction, allows for render pipeline re-use across render targets (even if their formats don't match), and provides consistent and predictable render pipeline behaviors. This also means that when rendering to a texture, the texture format no longer needs to match the surface's texture format. For example, you can now render to a texture that only has a red channel: render to texture red New Scene Format # authors: @MrGVSV Bevy 0.9 introduces a much improved scene format, which makes scenes smaller, simpler to compose manually, and easier to read. This is backed by a ton of improvements to Bevy Reflect (Bevy's Rust runtime reflection system). Most of the improvements to the Bevy Scene Format are actually generic improvements to all Bevy Reflect serialization! // The New Bevy Scene Format ( entities: { 0: ( components: { "game::Player": ( name: "Reyna", position: ( x: 0.0, y: 0.0, ), ), "game::Health": ( current: 5, max: 10, ), "game::Team": A, }, ), 1: ( components: { "game::Player": ( name: "Sova", position: ( x: 10.0, y: 0.0, ), ), "game::Health": ( current: 10, max: 10, ), "game::Team": B, }, ), }, ) Compare that to the old format: // The Old Bevy Scene Format [ ( entity: 0, components: [ { "type": "game::Player", "struct": { "name": { "type": "alloc::string::String", "value": "Reyna", }, "position": { "type": "glam::f32::vec2::Vec2", "struct": { "x": { "type": "f32", "value": 0.0, }, "y": { "type": "f32", "value": 0.0, }, }, }, }, }, { "type": "game::Health", "struct": { "current": { "type": "usize", "value": 5, }, "max": { "type": "usize", "value": 10, }, }, }, { "type": "game::Team", "value": A, }, ], ), ( entity: 1, components: [ { "type": "game::Player", "struct": { "name": { "type": "alloc::string::String", "value": "Sova", }, "position": { "type": "glam::f32::vec2::Vec2", "struct": { "x": { "type": "f32", "value": 10.0, }, "y": { "type": "f32", "value": 0.0, }, }, }, }, }, { "type": "game::Health", "struct": { "current": { "type": "usize", "value": 10, }, "max": { "type": "usize", "value": 10, }, }, }, { "type": "game::Team", "value": B, }, ], ), ] There are so many improvements that it might be hard to pick them all out! Simpler Struct Syntax # Structs now use struct-style formatting instead of complicated map-based representations. // Old { "type": "game::Health", "struct": { "current": { "type": "usize", "value": 5, }, "max": { "type": "usize", "value": 10, }, }, }, // New "game::Health": ( current: 5, max: 10, ), Simpler Primitive Serialization # Types can now opt in to direct serde serialization, which makes primitive values much nicer to work with: // Old "name": { "type": "alloc::string::String", "value": "Reyna", }, // New name: "Reyna", Nicer Enum Syntax # Consider the enum: pub enum Team { A, B, } Lets compare how it is serialized: // Old { "type": "game::Team", "value": A, }, // New "game::Team": A, Also note that Bevy Reflect didn't even directly support enums until Bevy 0.9. Older versions of Bevy required using #[reflect_value] in combination with normal serde for enums, which was much more complicated. See the Enum Reflection section of this blog post for details! Nicer Tuples # // Old { "type": "(f32, f32)", "tuple": [ { "type": "f32", "value": 1.0 }, { "type": "f32", "value": 2.0 } ] } // New { "(f32, f32)": (1.0, 2.0) } Top Level Struct # Bevy Scenes now have a top level struct, which allows us to add additional values and metadata to the Bevy Scene format in the future (such as version numbers, ECS Resources, assets, etc). // Old [ /* entities here */ ] // New ( entities: ( /* entities here */ ) ) Use Maps Where Appropriate # Entity IDs and Component values must be unique in Bevy ECS. To better represent that, we now use map syntax instead of a list. // Old [ ( entity: 0, components: [ ], ), ( entity: 1, components: [ ], ), ] // New ( entities: { 0: ( components: { }, ), 1: ( components: { }, ), }, ) Binary Scene Formats # authors: @MrGVSV Bevy Scenes can be serialized and deserialized to/from binary formats, such as bincode, postcard, and rmp_serde. This required adding support for "non-self-describing" formats to the new scene format. In the case of postcard, this can be almost 5x smaller (4.53x for the scene above)! Very useful if you are trying to keep the size of the scene small on disk, or send the scene over the network. Dynamic Scene Builder # authors: @mockersf Bevy Scenes can now be constructed dynamically using the new DynamicSceneBuilder. Previous versions of Bevy already supported writing "whole worlds" to scenes, but in some cases, users might only want to write specific entities to a scene. Bevy 0.9's DynamicSceneBuilder makes this possible: // Write players to a scene fn system(world: &World, players: Query>) { let builder = DynamicSceneBuilder::from_world(world); builder.extract_entities(players.iter()); let dynamic_scene = builder.build(); } extract_entities accepts any Entity iterator. You can also pass in specific entities: builder.extract_entity(entity); More Scene Construction Tools # authors: @mockersf Scenes can now be cloned: let scene = scene.clone_with(type_registry).unwrap(); DynamicScenes can now be converted to Scenes: let scene = Scene::from_dynamic_scene(dynamic_scene, type_registry).unwrap(); Improved Entity / Component APIs # authors: @DJMcNab, @cart Spawning entities with components and adding / removing them from entities just got even easier! First some quick fundamentals: Bevy ECS uses Components to add data and logic to entities. To make entity composition easier, Bevy ECS also has Bundles, which define groups of components to be added together. Just like in previous versions of Bevy, Bundles can be tuples of components: (Player { name: "Sova" }, Health::new(10), Team::A) The Bundle trait can also be derived: #[derive(Bundle)] struct PlayerBundle { player: Player, health: Health, team: Team, } In Bevy 0.9, Component types now also automatically implement the Bundle trait, which allows us to consolidate all entity component operations under new spawn, insert, and remove apis. Previously, we had separate variants for Bundle (ex: insert_bundle(SomeBundle)) and Component (ex: .insert(SomeComponent)). The Bundle trait is now also implemented for tuples of Bundles instead of just tuples of Components. The value of this will be made clear in a moment. First, spawn now takes a bundle: // Old (variant 1) commands.spawn().insert_bundle(SpriteBundle::default()); // Old (variant 2) commands.spawn_bundle(SpriteBundle::default()); // New commands.spawn(SpriteBundle::default()); Already we've saved some characters, but we're just getting started! Because Component implements Bundle, we can now also pass in single components into spawn: // Old commands.spawn().insert(Player { name: "Sova" }); // New commands.spawn(Player { name: "Sova" }); Things get even more interesting when we introduce Bundle tuples into the mix, which allow us to combine many operations (covering both components and bundles) into a single spawn call: // Old commands .spawn_bundle(PlayerBundle::default()) .insert_bundle(TransformBundle::default()) .insert(ActivePlayer); // New commands.spawn(( PlayerBundle::default(), TransformBundle::default(), ActivePlayer, )); This is much easier to type and read. And on top of that, from the perspective of Bevy ECS this is a single "bundle spawn" instead of multiple operations, which cuts down on "archetype moves". This makes this single spawn operation much more efficient! These principles apply to the insert apis as well: // Old commands .insert_bundle(PlayerBundle::default()) .insert(ActivePlayer); // New commands.insert((PlayerBundle::default(), ActivePlayer)); They also apply to the remove apis: // Old commands .remove_bundle::() .remove::(); // New commands.remove::<(PlayerBundle, ActivePlayer)>(); Exclusive System Rework # authors: @cart, @maniwani In preparation for the larger scheduler changes outlined in the newly-merged (but not yet implemented) Stageless RFC, we've started blurring the lines between "exclusive systems" (systems with "exclusive" full mutable access to the ECS World) and normal systems, which historically have been separate types with strict lines between them. In Bevy 0.9, exclusive systems now implement the normal System trait! This will ultimately have even larger implications, but in Bevy 0.9 this means that you no longer need to call .exclusive_system() when adding exclusive systems to your schedule: fn some_exclusive_system(world: &mut World) { } // Old app.add_system(some_exclusive_system.exclusive_system()) // New app.add_system(some_exclusive_system) We've also expanded exclusive systems to support more system parameters, which vastly improves the user experience of writing exclusive systems and makes them more efficient by caching state across executions. SystemState enables using "normal" system parameters from inside an exclusive system: // Old fn some_system(world: &mut World) { let mut state: SystemState<(Res