Skip to content

Commit b376c5d

Browse files
committed
Fixup particles docs.
1 parent 709ac86 commit b376c5d

1 file changed

Lines changed: 12 additions & 169 deletions

File tree

docs/particles.md

Lines changed: 12 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,14 @@
11
# Particles
22

3-
A `Particles` is a GPU-resident container of named attribute buffers, drawn by
4-
instancing a geometry once per element. The libprocessing analogue of a Houdini
5-
point cloud.
6-
7-
## Pieces
8-
9-
- **`compute::Buffer`** (`crates/processing_render/src/compute.rs`) — typed GPU
10-
storage with CPU-side write, GPU readback, and a Python wrapper that tracks
11-
element type. Backs every Particles attribute buffer.
12-
- **`Attribute`** (`crates/processing_render/src/geometry/attribute.rs`) —
13-
named typed attribute identity (`AttributeFormat::{Float, Float2, Float3,
14-
Float4}`), shared between Geometries and Particles. Builtins: `position`,
15-
`normal`, `color`, `uv`, plus the particles-only `rotation` (Float4 quat),
16-
`scale` (Float3), `dead` (Float, 0=alive).
17-
- **Upstream `processing/bevy`** commit `ee443e51` adds `GpuBatchedMesh3d` and
18-
`GpuInstanceBatchReservations` — a fixed-capacity batch where a compute pass
19-
writes per-instance transforms into the upstream input buffer before
20-
`early_gpu_preprocess` consumes them.
21-
22-
## Construction
23-
24-
Empty:
25-
26-
```rust
27-
let velocity = geometry_attribute_create("velocity", AttributeFormat::Float3)?;
28-
let p = particles_create(10_000, vec![geometry_attribute_position(), velocity])?;
29-
```
30-
31-
One zero-initialized buffer per requested attribute, sized
32-
`capacity * attr.format.byte_size()`.
33-
34-
Mesh-seeded:
35-
36-
```rust
37-
let source = geometry_sphere(5.0, 32, 24)?;
38-
let p = particles_create_from_geometry(
39-
source,
40-
vec![position_attr, uv_attr, color_attr],
41-
)?;
42-
```
43-
44-
Capacity = mesh vertex count. Builtins seed from the matching mesh attribute
45-
(`position``ATTRIBUTE_POSITION`, `normal``ATTRIBUTE_NORMAL`, `color`
46-
`ATTRIBUTE_COLOR`, `uv``ATTRIBUTE_UV_0`); particles-only builtins and custom
47-
attributes start at zero.
48-
49-
## Apply
50-
51-
```rust
52-
let spin = compute_create(shader_create(SPIN_WGSL)?)?;
53-
compute_set(spin, "dt", ShaderValue::Float(0.016))?;
54-
particles_apply(p, spin)?;
55-
```
56-
57-
`particles_apply` binds each attribute buffer by name; bindings the shader
58-
doesn't declare are skipped. Workgroup size is fixed at 64.
59-
60-
Built-in kernels: `particles_kernel_noise()` (uniforms `scale`, `strength`,
61-
`time`), `particles_kernel_transform()` (`translate`, `rotation_axis`,
62-
`rotation_angle`, `scale`, with identity defaults seeded so unset uniforms are
63-
no-ops).
64-
65-
## Pack pass
66-
67-
Bridges Particles attribute buffers into the per-instance slots reserved by
68-
`GpuBatchedMesh3d`. Runs as render-schedule systems:
69-
70-
- `extract_particles_draws` (ExtractSchedule) — copies Particles + buffer
71-
handles into the render world keyed by `ParticlesDraw` markers.
72-
- `prepare_pack_bind_groups` (RenderSystems::PrepareBindGroups) — looks up or
73-
builds the pack pipeline for the specialization key + bind group.
74-
- `dispatch_pack` (Core3d, before `early_gpu_preprocess`) — dispatches.
75-
76-
The pack shader (`particles/pack.wgsl`) is specialized per
77-
`(HAS_ROTATION, HAS_SCALE, HAS_DEAD)`. For each slot it writes:
78-
79-
- `mesh_input_buffer[base+i].world_from_local``mat3x4` from rotation × scale
80-
+ position translation.
81-
- `mesh_input_buffer[base+i].tag = i` — slot index, available via
82-
`mesh_functions::get_tag(instance_index)`.
83-
- `MeshCullingData[base+i].dead` — from the `dead` buffer if present, else 0.
84-
85-
## Materials
86-
87-
`ParticlesMaterial = ExtendedMaterial<StandardMaterial, ParticlesExtension>`
88-
binds a `colors: Handle<ShaderBuffer>` and reads `particle_colors[mesh.tag]`.
89-
Lit vs unlit is the `unlit` flag on the base `StandardMaterial`;
90-
`apply_pbr_lighting` short-circuits when set.
91-
92-
Immediate-mode:
93-
94-
```rust
95-
graphics_record_command(g, DrawCommand::FillBuffer(color_buffer_entity))?;
96-
graphics_record_command(g, DrawCommand::Particles { particles, geometry: shape })?;
97-
```
98-
99-
`fill(buffer)` sets the ambient albedo source; the next
100-
`DrawCommand::Particles` allocates a `ParticlesMaterial` carrying that buffer.
101-
102-
Explicit:
103-
104-
```rust
105-
let mat = material_create_pbr()?;
106-
material_set_albedo_buffer(mat, color_buffer_entity)?;
107-
material_set(mat, "roughness", ShaderValue::Float(0.4))?;
108-
```
109-
110-
`material_set_albedo_buffer` / `material_set_albedo_color` swap the backing
111-
asset between plain PBR and `ParticlesMaterial` while preserving every other
112-
`StandardMaterial` field.
113-
114-
Custom shaders (per-particle UV, per-particle scalars, anything beyond color)
115-
require a `CustomMaterial` that reads `mesh.tag` and indexes its own buffer.
116-
117-
## Emit
118-
119-
CPU-driven:
120-
121-
```rust
122-
particles_emit(
123-
p,
124-
n,
125-
vec![
126-
(position_attr, position_bytes), // n * 12 bytes
127-
(color_attr, color_bytes), // n * 16 bytes
128-
(dead_attr, vec![0u8; n * 4]), // alive
129-
],
130-
)?;
131-
```
132-
133-
Writes to `[head, head+n) mod capacity` and advances `emit_head`. Two writes
134-
when wrapping. No GPU allocator, no compaction. Capacity is a visible contract:
135-
`>= peak_emission_rate × longest_lifespan`.
136-
137-
GPU-driven:
138-
139-
```rust
140-
particles_emit_gpu(p, n, spawn_kernel)?;
141-
```
142-
143-
Auto-binds attribute buffers and `emit_range: vec4<f32> = (base_slot, n,
144-
capacity, 0)`. The kernel derives its target slot from `emit_range`.
145-
146-
No auto-defaults — if the field has a `dead` attribute, the caller must
147-
include it (typically `n` zero-floats) or new slots inherit the previous
148-
occupant's death.
149-
150-
## Lifecycle
151-
152-
`dead` is a builtin Float attribute (0=alive, non-zero=dead). When registered,
153-
the pack pass writes it into `MeshCullingData::dead`; non-zero slots are
154-
skipped in preprocessing.
155-
156-
Aging is user-managed via an apply kernel that increments age and flips
157-
`dead` when age exceeds ttl. See `particles_lifecycle.rs`. Seed `dead = 1.0`
158-
for unemitted ring slots so they don't render before being filled.
159-
160-
## Examples
161-
162-
- `particles_basic` — sphere-mesh-seeded particle cloud, PBR per-particle color.
163-
- `particles_animated` — 10×10×10 grid rotating around Y via custom apply.
164-
- `particles_oriented` — per-particle quaternion + scale.
165-
- `particles_colored` / `particles_colored_pbr` — explicit material setup.
166-
- `particles_emit` — continuous CPU ring-buffer emission.
167-
- `particles_emit_gpu` — fountain spawned by a compute kernel.
168-
- `particles_lifecycle` — emit + age + shrink-on-death.
169-
- `particles_from_mesh` — sphere mesh as position source.
170-
- `particles_noise` — built-in noise kernel jittering positions.
171-
- `particles_stress` — 1M cubes on a grid, R/G/B lights, transform spin.
3+
`Particles` are a collection of attribute buffers that can be used in order to sequence compute shaders. They are
4+
isomorphic to `Mesh` in the sense that they contain attributes and sets of data. In this way, you can think of a
5+
`Mesh` as the CPU representation of a `Particles` object, and the `Particles` object as the GPU representation of a
6+
`Mesh`. This allows convenient initialization of particle simulations from existing meshes, or using a mesh as a
7+
constraint for a particle simulation, like a volume or a surface.
8+
9+
Another way to consider particles would be as the compute equivalent of `Graphics`. Where the `Grpahics` object
10+
allows you to issue high level rasterization commands, the `Particles` object allows you to issue high level compute
11+
commands. In this way, you can think of a `Particles` object as a compute shader that is executed on the GPU, and the
12+
attributes as the inputs and outputs of the compute shader. In practice, a compute shader may also require additional
13+
data, such as textures or bound vertex buffers, but the `Particles` object provides a high level abstraction for
14+
sequencing compute shaders and managing their inputs and outputs.

0 commit comments

Comments
 (0)