-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic support for particle trails.
This commit implements simple fixed-length particle trails in Hanabi. They're stored in a ring buffer with a fixed capacity separate from the main particle buffer. Currently, for simplicity, trail particles are rendered as exact duplicates of the head particles. Nothing in this patch prevents this from being expanded further to support custom rendering for trail particles, including ribbons and trail-index-dependent rendering, in the future. The only reason why this wasn't implemented is to keep the size of this patch manageable, as it's quite large as it is. The size of the trail buffer is known as the `trail_capacity` and doesn't change over the lifetime of the effect. The length of each particle trail is known as the `trail_length` and can be altered at runtime. The interval at which new trail particles spawn is known as the `trail_period` and can likewise change at runtime. There are three primary reasons why particle trails are stored in a separate buffer from the head particles: 1. It's common to want a separate rendering for trail particles and head particles (e.g. the head particle may want to be some sort of particle with a short ribbon behind it), and so we need to separate the two so that they can be rendered in separate drawcalls. 2. Having a separate buffer allows us to skip the update phase for particle trails, enhancing performance. 3. Since trail particles are strictly LIFO, we can use a ring buffer instead of a freelist, which both saves memory (as no freelist needs to be maintained) and enhances performance (as an entire chunk of particles can be freed at once instead of having to do so one by one). The core of the implementation is the `render::effect_cache::TrailChunks` buffer. The long documentation comment attached to that structure explains the setup of the ring buffer and has a diagram. In summary, two parallel ring buffers are maintained on CPU and GPU. The GPU ring buffer has `trail_capacity` entries and stores the trail particles themselves, while the CPU one has `trail_length` entries and stores pointers to indices defining the boundaries of the chunks. A new example, `worms`, has been added in order to demonstrate simple use of trails. This example can be updated over time as new trail features are added.
- Loading branch information
Showing
19 changed files
with
1,537 additions
and
499 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
//! Worms | ||
//! | ||
//! Demonstrates simple use of particle trails. | ||
use std::f32::consts::{FRAC_PI_2, PI}; | ||
|
||
use bevy::{ | ||
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping}, | ||
log::LogPlugin, | ||
math::{vec3, vec4}, | ||
prelude::*, | ||
}; | ||
#[cfg(feature = "examples_world_inspector")] | ||
use bevy_inspector_egui::quick::WorldInspectorPlugin; | ||
|
||
use bevy_hanabi::prelude::*; | ||
|
||
fn main() { | ||
let mut app = App::default(); | ||
app.add_plugins( | ||
DefaultPlugins | ||
.set(LogPlugin { | ||
level: bevy::log::Level::WARN, | ||
filter: "bevy_hanabi=warn,worms=trace".to_string(), | ||
update_subscriber: None, | ||
}) | ||
.set(WindowPlugin { | ||
primary_window: Some(Window { | ||
title: "🎆 Hanabi — worms".to_string(), | ||
..default() | ||
}), | ||
..default() | ||
}), | ||
) | ||
.add_systems(Update, bevy::window::close_on_esc) | ||
.add_plugins(HanabiPlugin); | ||
|
||
#[cfg(feature = "examples_world_inspector")] | ||
app.add_plugins(WorldInspectorPlugin::default()); | ||
|
||
app.add_systems(Startup, setup).run(); | ||
} | ||
|
||
fn setup( | ||
mut commands: Commands, | ||
asset_server: ResMut<AssetServer>, | ||
mut effects: ResMut<Assets<EffectAsset>>, | ||
) { | ||
commands.spawn(( | ||
Camera3dBundle { | ||
transform: Transform::from_translation(Vec3::new(0., 0., 25.)), | ||
camera: Camera { | ||
hdr: true, | ||
clear_color: Color::BLACK.into(), | ||
..default() | ||
}, | ||
tonemapping: Tonemapping::None, | ||
..default() | ||
}, | ||
BloomSettings::default(), | ||
)); | ||
|
||
let circle: Handle<Image> = asset_server.load("circle.png"); | ||
|
||
let writer = ExprWriter::new(); | ||
|
||
// Init modifiers | ||
|
||
// Spawn the particles within a reasonably large box. | ||
let set_initial_position_modifier = SetAttributeModifier::new( | ||
Attribute::POSITION, | ||
((writer.rand(ValueType::Vector(VectorType::VEC3F)) + writer.lit(vec3(-0.5, -0.5, 0.0))) | ||
* writer.lit(vec3(16.0, 16.0, 0.0))) | ||
.expr(), | ||
); | ||
|
||
// Randomize the initial angle of the particle, storing it in the `F32_0` | ||
// scratch attribute.` | ||
let set_initial_angle_modifier = SetAttributeModifier::new( | ||
Attribute::F32_0, | ||
writer.lit(0.0).uniform(writer.lit(PI * 2.0)).expr(), | ||
); | ||
|
||
// Give each particle a random opaque color. | ||
let set_color_modifier = SetAttributeModifier::new( | ||
Attribute::COLOR, | ||
(writer.rand(ValueType::Vector(VectorType::VEC4F)) * writer.lit(vec4(1.0, 1.0, 1.0, 0.0)) | ||
+ writer.lit(Vec4::W)) | ||
.pack4x8unorm() | ||
.expr(), | ||
); | ||
|
||
// Give the particles a long lifetime. | ||
let set_lifetime_modifier = | ||
SetAttributeModifier::new(Attribute::LIFETIME, writer.lit(10.0).expr()); | ||
|
||
// Update modifiers | ||
|
||
// Make the particle wiggle, following a sine wave. | ||
let set_velocity_modifier = SetAttributeModifier::new( | ||
Attribute::VELOCITY, | ||
WriterExpr::sin( | ||
writer.lit(vec3(1.0, 1.0, 0.0)) | ||
* (writer.attr(Attribute::F32_0) | ||
+ (writer.time() * writer.lit(5.0)).sin() * writer.lit(1.0)) | ||
+ writer.lit(vec3(0.0, FRAC_PI_2, 0.0)), | ||
) | ||
.mul(writer.lit(5.0)) | ||
.expr(), | ||
); | ||
|
||
// Render modifiers | ||
|
||
// Set the particle size. | ||
let set_size_modifier = SetSizeModifier { | ||
size: Vec2::splat(0.4).into(), | ||
}; | ||
|
||
// Make each particle round. | ||
let particle_texture_modifier = ParticleTextureModifier { | ||
texture: circle, | ||
sample_mapping: ImageSampleMapping::Modulate, | ||
}; | ||
|
||
let module = writer.finish(); | ||
|
||
// Allocate room for 32,768 trail particles. Give each particle a 5-particle | ||
// trail, and spawn a new trail particle every ⅛ of a second. | ||
let effect = effects.add( | ||
EffectAsset::with_trails( | ||
32768, | ||
32768, | ||
Spawner::rate(4.0.into()) | ||
.with_trail_length(5) | ||
.with_trail_period(0.125.into()), | ||
module, | ||
) | ||
.with_name("worms") | ||
.init(set_initial_position_modifier) | ||
.init(set_initial_angle_modifier) | ||
.init(set_lifetime_modifier) | ||
.init(set_color_modifier) | ||
.update(set_velocity_modifier) | ||
.render(set_size_modifier) | ||
.render(particle_texture_modifier), | ||
); | ||
|
||
commands.spawn(( | ||
Name::new("worms"), | ||
ParticleEffectBundle { | ||
effect: ParticleEffect::new(effect), | ||
transform: Transform::IDENTITY, | ||
..default() | ||
}, | ||
)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.