• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / bevy_hanabi / 11656781118

04 Nov 2024 01:58AM UTC coverage: 56.541% (-1.3%) from 57.846%
11656781118

push

github

web-flow
Allow particles to be arbitrary meshes. (#377)

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... (continued)

33 of 182 new or added lines in 7 files covered. (18.13%)

29 existing lines in 2 files now uncovered.

3557 of 6291 relevant lines covered (56.54%)

22.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/render/batch.rs
1
use std::{
2
    fmt::Debug,
3
    ops::{Index, Range},
4
};
5

6
#[cfg(feature = "2d")]
7
use bevy::math::FloatOrd;
8
use bevy::{
9
    prelude::*,
10
    render::render_resource::{Buffer, CachedComputePipelineId},
11
};
12

13
use super::{
14
    effect_cache::{DispatchBufferIndices, EffectSlices},
15
    EffectCacheId, GpuCompressedTransform, LayoutFlags,
16
};
17
use crate::{
18
    spawn::EffectInitializer, AlphaMode, EffectAsset, EffectShader, ParticleLayout, PropertyLayout,
19
    TextureLayout,
20
};
21

22
/// Data needed to render all batches pertaining to a specific effect.
23
#[derive(Debug, Component)]
24
pub(crate) struct EffectBatches {
25
    /// Handle of the underlying effect asset describing the effect.
26
    pub handle: Handle<EffectAsset>,
27
    /// One batch per particle group.
28
    pub group_batches: Vec<EffectBatch>,
29
    /// Index of the buffer.
30
    pub buffer_index: u32,
31
    /// Index of the first Spawner of the effects in the batch.
32
    pub spawner_base: u32,
33
    /// The initializer (spawner or cloner) for each particle group.
34
    pub initializers: Vec<EffectInitializer>,
35
    /// The effect cache ID.
36
    pub effect_cache_id: EffectCacheId,
37
    /// The indices within the various indirect dispatch buffers.
38
    pub dispatch_buffer_indices: DispatchBufferIndices,
39
    /// The index of the first [`GpuParticleGroup`] structure in the global
40
    /// [`EffectsMeta::particle_group_buffer`] buffer. The buffer is currently
41
    /// re-created each frame, so the rows for multiple groups of an effect are
42
    /// guaranteed to be contiguous.
43
    pub first_particle_group_buffer_index: u32,
44
    /// Particle layout.
45
    pub particle_layout: ParticleLayout,
46
    /// Flags describing the render layout.
47
    pub layout_flags: LayoutFlags,
48
    /// The mesh to draw.
49
    pub mesh: Handle<Mesh>,
50
    /// Texture layout.
51
    pub texture_layout: TextureLayout,
52
    /// Textures.
53
    pub textures: Vec<Handle<Image>>,
54
    /// Alpha mode.
55
    pub alpha_mode: AlphaMode,
56
    /// Entities holding the source [`ParticleEffect`] instances which were
57
    /// batched into this single batch. Used to determine visibility per view.
58
    ///
59
    /// [`ParticleEffect`]: crate::ParticleEffect
60
    pub entities: Vec<u32>,
61
    /// Configured shaders used for the particle rendering of this batch.
62
    /// Note that we don't need to keep the init/update shaders alive because
63
    /// their pipeline specialization is doing it via the specialization key.
64
    pub render_shaders: Vec<Handle<Shader>>,
65
    /// Init and update compute pipelines specialized for this batch.
66
    pub init_and_update_pipeline_ids: Vec<InitAndUpdatePipelineIds>,
67
    /// The order in which we evaluate groups.
68
    pub group_order: Vec<u32>,
69
}
70

71
impl Index<u32> for EffectBatches {
72
    type Output = EffectBatch;
73

74
    fn index(&self, index: u32) -> &Self::Output {
×
75
        &self.group_batches[index as usize]
×
76
    }
77
}
78

79
/// Single effect batch to drive rendering.
80
///
81
/// This component is spawned into the render world during the prepare phase
82
/// ([`prepare_effects()`]), once per effect batch per group. In turns it
83
/// references an [`EffectBatches`] component containing all the shared data for
84
/// all the groups of the effect.
85
#[derive(Debug, Component)]
86
pub(crate) struct EffectDrawBatch {
87
    /// Group index of the batch.
88
    pub group_index: u32,
89
    /// Entity holding the [`EffectBatches`] this batch is part of.
90
    pub batches_entity: Entity,
91
    /// For 2D rendering, the Z coordinate used as the sort key. Ignored for 3D
92
    /// rendering.
93
    #[cfg(feature = "2d")]
94
    pub z_sort_key_2d: FloatOrd,
95
    /// For 3d rendering, the position of the emitter so we can compute distance
96
    /// to camera. Ignored for 2D rendering.
97
    #[cfg(feature = "3d")]
98
    pub translation_3d: Vec3,
99
}
100

101
/// Batch data specific to a single particle group.
102
#[derive(Debug)]
103
pub(crate) struct EffectBatch {
104
    /// Slice of particles in the GPU effect buffer for the entire batch.
105
    pub slice: Range<u32>,
106
}
107

108
impl EffectBatches {
109
    /// Create a new batch from a single input.
110
    pub fn from_input(
×
111
        input: BatchesInput,
112
        spawner_base: u32,
113
        effect_cache_id: EffectCacheId,
114
        init_and_update_pipeline_ids: Vec<InitAndUpdatePipelineIds>,
115
        dispatch_buffer_indices: DispatchBufferIndices,
116
        first_particle_group_buffer_index: u32,
117
    ) -> EffectBatches {
118
        EffectBatches {
119
            buffer_index: input.effect_slices.buffer_index,
×
120
            spawner_base,
121
            initializers: input.initializers.clone(),
×
122
            particle_layout: input.effect_slices.particle_layout,
×
123
            effect_cache_id,
124
            dispatch_buffer_indices,
125
            first_particle_group_buffer_index,
126
            group_batches: input
×
127
                .effect_slices
128
                .slices
129
                .windows(2)
130
                .map(|range| EffectBatch {
131
                    slice: range[0]..range[1],
132
                })
133
                .collect(),
134
            handle: input.handle,
×
135
            layout_flags: input.layout_flags,
×
NEW
136
            mesh: input.mesh.clone(),
×
137
            texture_layout: input.texture_layout,
×
138
            textures: input.textures,
×
139
            alpha_mode: input.alpha_mode,
×
140
            render_shaders: input
×
141
                .effect_shaders
142
                .iter()
143
                .map(|shaders| shaders.render.clone())
144
                .collect(),
145
            init_and_update_pipeline_ids,
146
            entities: vec![input.entity.index()],
×
147
            group_order: input.group_order,
×
148
        }
149
    }
150
}
151

152
/// Effect batching input, obtained from extracted effects.
153
#[derive(Debug)]
154
pub(crate) struct BatchesInput {
155
    /// Handle of the underlying effect asset describing the effect.
156
    pub handle: Handle<EffectAsset>,
157
    /// Entity index excluding generation ([`Entity::index()`]). This is
158
    /// transient for a single frame, so the generation is useless.
159
    pub entity: Entity,
160
    /// Effect slices.
161
    pub effect_slices: EffectSlices,
162
    /// Layout of the effect properties.
163
    pub property_layout: PropertyLayout,
164
    /// Effect shaders.
165
    pub effect_shaders: Vec<EffectShader>,
166
    /// Various flags related to the effect.
167
    pub layout_flags: LayoutFlags,
168
    pub mesh: Handle<Mesh>,
169
    /// Texture layout.
170
    pub texture_layout: TextureLayout,
171
    /// Textures.
172
    pub textures: Vec<Handle<Image>>,
173
    /// Alpha mode.
174
    pub alpha_mode: AlphaMode,
175
    pub particle_layout: ParticleLayout,
176
    pub initializers: Vec<EffectInitializer>,
177
    /// The order in which we evaluate groups.
178
    pub group_order: Vec<u32>,
179
    /// Emitter transform.
180
    pub transform: GpuCompressedTransform,
181
    /// Emitter inverse transform.
182
    pub inverse_transform: GpuCompressedTransform,
183
    /// GPU buffer where properties for this batch need to be written.
184
    pub property_buffer: Option<Buffer>,
185
    /// Serialized property data.
186
    // FIXME - Contains a single effect's data; should handle multiple ones.
187
    pub property_data: Option<Vec<u8>>,
188
    /// Sort key, for 2D only.
189
    #[cfg(feature = "2d")]
190
    pub z_sort_key_2d: FloatOrd,
191
}
192

193
#[derive(Debug)]
194
pub(crate) struct InitAndUpdatePipelineIds {
195
    pub(crate) init: CachedComputePipelineId,
196
    pub(crate) update: CachedComputePipelineId,
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc