#particle #2d #shader #bevy #fragment-shader

bevy_enoki

2D Particle system plugin, that works well in wasm

1 unstable release

0.1.0 Mar 30, 2024

#151 in Graphics APIs

Download history 160/week @ 2024-03-29 37/week @ 2024-04-05 13/week @ 2024-04-12 23/week @ 2024-04-19

233 downloads per month

MIT license

6MB
1.5K SLoC

Bevy Enoki

License: MIT or Apache 2.0 Crate

Enoki - A 2D particle system for the Bevy game engine.

animation

Overview

The Enoki particle system is a CPU calculate particle system, that uses GPU Instancing and works well with wasm. It provides a material trait that lets you easily implement your own fragment shader for all your VFX needs. Additionally, spawner configuration are provided via ron files, which can be hot reloaded.

The default material allows not only for custom textures, but also sprite sheet animations over the particle lifetime.

🚧 This Plugin just released and is still under heavy development. The particle behavior is very basic and performance could be better. Expect rapid change. Planned is orbital velocity, attractors and simple physics. When 2d is fleshed out and the best it can be, 3d might happen.

Compatibility

bevy bevy_enoki
0.13 0.1

Examples

cargo run --examples material
cargo run --examples sprites
cargo run --examples dynamic

Usage

Add the bevy_enoki dependency to your Cargo.toml

bevy_enoki = "0.1"

Add the EnokiPlugin to your app

App::new()
    .add_plugins(DefaultPlugins)
    .add_plugins(EnokiPlugin)
    .run()

Create your first particle spawner. Here is a default effect config

use bevy_enoki::prelude::*;

fn setup(
    mut cmd : Commands,
    mut materials: ResMut<Assets<SpriteParticle2dMaterial>>,
    server : Res<AssetServer>,
){
    cmd.spawn(Camera2dBundle::default());

    //spawning quads
    cmd.spawn((
        ParticleSpawnerBundle {
            // load the effect configuration from a ron file
            effect: server.load("fire.particle.ron"),
            // the default materiel is just a flat white color
            // that gets multiplied by any color curve inside the
            // effect definition of the `ron` file.
            material: DEFAULT_MATERIAL,
            ..default()
        },
    ));

    // if you want to add a texture, use the inbuild `ColorParticle2dMaterial`
    let texture_material = materials.add(
        // hframes and vframes define how the sprite sheet is divided for animations,
        // if you just want to bind a single texture, leave both at 1.
        SpriteParticle2dMaterial::new(server.load("particle.png"), 6, 1),
    );

    cmd.spawn((
        ParticleSpawnerBundle {
            effect: server.load("fire.particle.ron"),
            material: texture_material,
            ..default()
        },
    ));
}

Create a custom Material

Just like any other Bevy material, you can define your own fragment shader.

#[derive(AsBindGroup, Asset, TypePath, Clone, Default)]
pub struct FireParticleMaterial {
    #[texture(0)]
    #[sampler(1)]
    texture: Handle<Image>,
}

impl Particle2dMaterial for FireParticleMaterial {
    fn fragment_shader() -> bevy::render::render_resource::ShaderRef {
        "custom_material.wgsl".into()
    }
}

fn setup(){
    App::default()
        .add_plugins(DefaultPlugins)
        .add_plugins(EnokiPlugin)
        .add_plugins(Particle2dMaterialPlugin::<FireParticleMaterial>::default())
        .run()
}

Now create the Shader

//assets/custom_material.wgsl
#import bevy_enoki::particle_vertex_out::{ VertexOutput }

@group(1) @binding(0) var texture: texture_2d<f32>;
@group(1) @binding(1) var texture_sampler: sampler;


@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
    var out = in.color
    // go wild
    return out;
}

That's it, now add the Material to your Spawner! These are the values provided by the vertex shader:

struct VertexOutput {
  @builtin(position) clip_position: vec4<f32>,
  @location(0) @interpolate(flat) color: vec4<f32>,
  @location(1) uv : vec2<f32>,
  @location(2) lifetime_frac : f32,
  @location(3) lifetime_total : f32,
};

The Effect Asset

Here is a default ron config

#[derive(Deserialize, Default, Clone, Debug)]
pub enum EmissionShape {
    #[default]
    Point,
    Circle(f32),
}

#[derive(Asset, TypePath, Default, Deserialize, Clone, Debug)]
pub struct Particle2dEffect {
    pub spawn_rate: f32,
    pub spawn_amount: u32,
    pub emission_shape: EmissionShape,
    pub lifetime: Rval<f32>,
    pub linear_speed: Option<Rval<f32>>,
    pub linear_acceleration: Option<Rval<f32>>,
    pub direction: Option<Rval<Vec2>>,
    pub angular_speed: Option<Rval<f32>>,
    pub angular_acceleration: Option<Rval<f32>>,
    pub scale: Option<Rval<f32>>,
    pub color: Option<Color>,
    pub gravity_direction: Option<Rval<Vec2>>,
    pub gravity_speed: Option<Rval<f32>>,
    pub linear_damp: Option<Rval<f32>>,
    pub angular_damp: Option<Rval<f32>>,
    pub scale_curve: Option<Curve<f32>>,
    pub color_curve: Option<Curve<Color>>,
}

This how you create a Curve. Currently, Supports Color and f32. RVal stands for any Value with a randomness property between 0 - 1.

let curve = Curve::new()
    .with_point(Color::RED, 0.0, None)
    .with_point(Color::BLUE, 1.0, Some(bevy_enoki::prelude::EaseFunction::SineInOut));

// max 1.0, randomness of 0.1 (0.9 - 1.1)
let rval = Rval::new(1.0, 0.1);

Dependencies

~40–79MB
~1M SLoC