Skip to content

Commit

Permalink
Allow particles to be arbitrary meshes. (#377)
Browse files Browse the repository at this point in the history
Currently, Hanabi requires that particles be 2D quads. This is
sufficient for a good deal of VFX, but in many cases 3D objects are
required: smoke puffs, bullet casings, etc. This commit fixes this
deficiency by allowing particles to take on arbitrary meshes. To set the
mesh of a particle, use the new `EffectAsset::mesh` builder method. By
default, the mesh is a 2D quad.

The implementation is straightforward. The previously-hard-wired quad
vertices have been replaced with a `Handle<Mesh>`. The patch uses the
existing `bevy_render` infrastructure to upload the mesh to the GPU and
retrieve the vertices. Perhaps the most significant change is the
generalization of rendering to allow for indexed drawing in addition to
non-indexed. Because indexed drawing has a different on-GPU format for
indirect draw commands from that of non-indirect draw commands, some
additional bookkeeping is required.

This patch also adds support for a few features useful for 3D rendering:

* A `size3` attribute has been added, to allow the size to be controlled
  in 3D.

* The `SetSizeModifier` now takes a 3D size gradient instead of a 2D
  one.

* Vertex normals are available to modifiers via the `normal` shader
  variable, as long as they call the new
  `RenderContext::set_needs_normal` method.

A new example, `puffs`, has been added to demonstrate the use of 3D
meshes. It depicts the Bevy test fox running with cartoony smoke puffs
emitted at a constant rate behind it. Each puff consists of multiple
spherical mesh particles offset with some random jitter. A custom
Lambertian lighting modifier is supplied with the example, in order to
make the smoke puffs not appear solid white. (This modifier dramatically
improves the look of this example, but it's very limited, so I didn't
upstream it to Hanabi proper. A proper PBR lighting modifier would be
useful, but would be a significant amount of work, so I chose to defer
that to a follow-up.)
  • Loading branch information
pcwalton authored Nov 4, 2024
1 parent 687980e commit 1cd61b0
Show file tree
Hide file tree
Showing 33 changed files with 823 additions and 193 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- You can now call `EffectAsset::mesh` to change the mesh that a particle will
use from the default quad to a custom mesh of your choosing.
- A `size3` attribute has been added to allow controlling the size in 3D.
- Vertex normals are available to modifiers via the normal shader variable, as
long as they call the new RenderContext::set_needs_normal method.
- Added a new `serde` feature, and moved all `Serialization` and `Deserialization` derives under that feature.
This allows disabling support on wasm, where `typetag` is not available.
The feature is enabled by default, so the behavior doesn't change on non-wasm targets.
Expand All @@ -19,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- The `SetSizeModifier` now takes a 3D size gradient instead of a 2D one.
- The `ParticleTextureModifier` doesn't directly hold the texture handle to use anymore.
Instead, it holds an expression handle to the texture slot defined in the `Module` of the effect.
This allows dynamically changing the texture sampled.
Expand Down
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ bevy_egui = { version = "0.28", default-features = false, features = [
] }
egui = "0.28"

# For glTF animations (Fox.glb)
bevy_gltf = { version = "0.14", features = [ "bevy_animation" ] }

# For procedural texture generation in examples
noise = "0.9"

Expand Down Expand Up @@ -160,6 +163,18 @@ required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]
name = "ordering"
required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]

[[example]]
name = "puffs"
required-features = [
"bevy/bevy_winit",
"bevy/bevy_pbr",
"bevy/bevy_scene",
"bevy/bevy_gltf",
"bevy/bevy_animation",
"bevy/png",
"3d",
]

[[test]]
name = "empty_effect"
path = "gpu_tests/empty_effect.rs"
Expand Down
Binary file added assets/Fox.glb
Binary file not shown.
2 changes: 2 additions & 0 deletions build_examples_wasm.bat
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cargo b --release --example circle --target wasm32-unknown-unknown --no-default-
cargo b --release --example billboard --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example worms --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example instancing --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example puffs --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/bevy_scene bevy/bevy_gltf bevy/bevy_animation bevy/png 3d"
REM 2D
cargo b --release --example 2d --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_sprite 2d"

Expand All @@ -49,6 +50,7 @@ wasm-bindgen --out-name wasm_circle --no-typescript --out-dir examples/wasm/targ
wasm-bindgen --out-name wasm_billboard --no-typescript --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/billboard.wasm
wasm-bindgen --out-name wasm_worms --no-typescript --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/worms.wasm
wasm-bindgen --out-name wasm_instancing --no-typescript --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/instancing.wasm
wasm-bindgen --out-name wasm_puffs --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/puffs.wasm
wasm-bindgen --out-name wasm_2d --no-typescript --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/2d.wasm

echo Done. See docs/wasm.md for help on running the examples locally.
2 changes: 2 additions & 0 deletions build_examples_wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cargo b --release --example circle --target wasm32-unknown-unknown --no-default-
cargo b --release --example billboard --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example worms --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example instancing --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/png 3d"
cargo b --release --example puffs --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr bevy/bevy_scene bevy/bevy_gltf bevy/bevy_animation bevy/png 3d"
# 2D
cargo b --release --example 2d --target wasm32-unknown-unknown --no-default-features --features="bevy/bevy_winit bevy/bevy_sprite 2d"

Expand All @@ -48,6 +49,7 @@ wasm-bindgen --out-name wasm_circle --out-dir examples/wasm/target --target web
wasm-bindgen --out-name wasm_billboard --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/billboard.wasm
wasm-bindgen --out-name wasm_worms --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/worms.wasm
wasm-bindgen --out-name wasm_instancing --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/instancing.wasm
wasm-bindgen --out-name wasm_puffs --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/puffs.wasm
wasm-bindgen --out-name wasm_2d --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/release/examples/2d.wasm

echo Done. See docs/wasm.md for help on running the examples locally.
2 changes: 1 addition & 1 deletion examples/2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ fn setup(
.init(init_age)
.init(init_lifetime)
.render(SizeOverLifetimeModifier {
gradient: Gradient::constant(Vec2::splat(0.02)),
gradient: Gradient::constant(Vec3::splat(0.02)),
screen_space_size: false,
})
.render(ColorOverLifetimeModifier { gradient })
Expand Down
2 changes: 1 addition & 1 deletion examples/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fn setup(
.update(update_buoyancy)
.update(allow_zone)
.render(SetSizeModifier {
size: Vec2::splat(0.02).into(),
size: Vec3::splat(0.02).into(),
})
.render(ColorOverLifetimeModifier { gradient })
.render(round),
Expand Down
2 changes: 1 addition & 1 deletion examples/billboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fn setup(
rotation: Some(rotation_attr),
})
.render(SizeOverLifetimeModifier {
gradient: Gradient::constant([0.2; 2].into()),
gradient: Gradient::constant([0.2; 3].into()),
screen_space_size: false,
}),
);
Expand Down
2 changes: 1 addition & 1 deletion examples/circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn setup(
.render(FlipbookModifier { sprite_grid_size })
.render(ColorOverLifetimeModifier { gradient })
.render(SizeOverLifetimeModifier {
gradient: Gradient::constant([0.5; 2].into()),
gradient: Gradient::constant([0.5; 3].into()),
screen_space_size: false,
}),
);
Expand Down
4 changes: 2 additions & 2 deletions examples/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ fn setup(mut commands: Commands, mut effects: ResMut<Assets<EffectAsset>>) {
color_gradient.add_key(1.0, Vec4::new(0.0, 0.0, 0.0, 0.0));

let mut size_gradient = Gradient::new();
size_gradient.add_key(0.3, Vec2::new(0.2, 0.02));
size_gradient.add_key(1.0, Vec2::ZERO);
size_gradient.add_key(0.3, Vec3::new(0.2, 0.02, 1.0));
size_gradient.add_key(1.0, Vec3::ZERO);

let writer = ExprWriter::new();

Expand Down
6 changes: 3 additions & 3 deletions examples/firework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ fn setup(mut commands: Commands, mut effects: ResMut<Assets<EffectAsset>>) {
color_gradient1.add_key(1.0, Vec4::new(4.0, 0.0, 0.0, 0.0));

let mut size_gradient1 = Gradient::new();
size_gradient1.add_key(0.0, Vec2::splat(0.05));
size_gradient1.add_key(0.3, Vec2::splat(0.05));
size_gradient1.add_key(1.0, Vec2::splat(0.0));
size_gradient1.add_key(0.0, Vec3::splat(0.05));
size_gradient1.add_key(0.3, Vec3::splat(0.05));
size_gradient1.add_key(1.0, Vec3::splat(0.0));

let writer = ExprWriter::new();

Expand Down
2 changes: 1 addition & 1 deletion examples/force_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ fn setup(
.update(allow_zone)
.update(deny_zone)
.render(SizeOverLifetimeModifier {
gradient: Gradient::constant(Vec2::splat(0.05)),
gradient: Gradient::constant(Vec3::splat(0.05)),
screen_space_size: false,
})
.render(ColorOverLifetimeModifier { gradient }),
Expand Down
2 changes: 1 addition & 1 deletion examples/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

const COLOR: Vec4 = Vec4::new(0.7, 0.7, 1.0, 1.0);
const SIZE: Vec2 = Vec2::splat(0.1);
const SIZE: Vec3 = Vec3::splat(0.1);

fn base_effect<M, F>(name: impl Into<String>, mut make_modifier: F) -> EffectAsset
where
Expand Down
8 changes: 4 additions & 4 deletions examples/multicam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ struct SplitCamera {

fn make_effect(color: Color) -> EffectAsset {
let mut size_gradient = Gradient::new();
size_gradient.add_key(0.0, Vec2::splat(1.0));
size_gradient.add_key(0.5, Vec2::splat(5.0));
size_gradient.add_key(0.8, Vec2::splat(0.8));
size_gradient.add_key(1.0, Vec2::splat(0.0));
size_gradient.add_key(0.0, Vec3::splat(1.0));
size_gradient.add_key(0.5, Vec3::splat(5.0));
size_gradient.add_key(0.8, Vec3::splat(0.8));
size_gradient.add_key(1.0, Vec3::splat(0.0));

let mut color_gradient = Gradient::new();
color_gradient.add_key(0.0, Vec4::splat(1.0));
Expand Down
6 changes: 3 additions & 3 deletions examples/ordering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ fn make_firework() -> EffectAsset {
// Keep the size large so we can more visibly see the particles for longer, and
// see the effect of alpha blending.
let mut size_gradient1 = Gradient::new();
size_gradient1.add_key(0.0, Vec2::ONE);
size_gradient1.add_key(0.1, Vec2::ONE);
size_gradient1.add_key(1.0, Vec2::ZERO);
size_gradient1.add_key(0.0, Vec3::ONE);
size_gradient1.add_key(0.1, Vec3::ONE);
size_gradient1.add_key(1.0, Vec3::ZERO);

let writer = ExprWriter::new();

Expand Down
4 changes: 2 additions & 2 deletions examples/portal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ fn setup(mut commands: Commands, mut effects: ResMut<Assets<EffectAsset>>) {
color_gradient1.add_key(1.0, Vec4::new(4.0, 0.0, 0.0, 0.0));

let mut size_gradient1 = Gradient::new();
size_gradient1.add_key(0.3, Vec2::new(0.2, 0.02));
size_gradient1.add_key(1.0, Vec2::splat(0.0));
size_gradient1.add_key(0.3, Vec3::new(0.2, 0.02, 1.0));
size_gradient1.add_key(1.0, Vec3::splat(0.0));

let writer = ExprWriter::new();

Expand Down
Loading

0 comments on commit 1cd61b0

Please sign in to comment.