SMRTicles¶
Smart Particles¶
Stateful Massively-parallel Runtime for particles
or:
Simulations with Multiple Render Targets
or:
Whatever you’d like
Noisemaker’s shader pipeline provides common architecture for GPU-accelerated particle simulations based on agents. It can initialize, simulate, and render perhaps millions of particles. These particles share state management and composable behaviors.
Philosophy¶
Traditional particle systems duplicate substantial boilerplate code across effects:
Agent initialization and respawn logic
Color sampling from input textures
Trail accumulation and decay
Point-sprite rendering and blending
SMRTicles places effect-specific behavior middleware between two wrapper effects: pointsEmit and pointsRender. The wrappers provide the common infrastructure. This architecture achieves:
Code Reduction: ~40% less shader code per effect
Consistency: All effects share the same deposit/blend approach
Composability: Combine behaviors in a single pipeline
Maintainability: Bug fixes propagate to all particle effects
Architecture Overview¶
Pipeline Structure¶
Every SMRTicles composition follows this three-stage pipeline:
(input texture)
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ pointsEmit (render) │
│ • Initialize agents with positions, velocities, colors, count │
│ • Handle respawn when agents die or attrition triggers │
│ • Sample colors from input texture │
│ • Output: global_xyz, global_vel, global_rgba │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Behavior Middleware (points) │
│ • Read xyz/vel/rgba state from pipeline │
│ • Apply effect-specific movement/physics/sensing │
│ • Write updated state back (ping-ponged by runtime) │
│ • Examples: attractor, flock, flow, physarum, life, physical │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ pointsRender (render) │
│ • Diffuse: decay existing trail by intensity factor │
│ • Deposit: scatter agent colors via point-sprite rendering │
│ • Blend: composite trail with input by inputIntensity factor │
│ • Output: final image to outputTex │
└─────────────────────────────────────────────────────────────────────┘
DSL Usage¶
SMRTicles compositions use method chaining in the Polymorphic DSL:
// Basic particle system with physics
noise().pointsEmit().physical().pointsRender().write(o0)
// Slime mold simulation
noise().pointsEmit(stateSize: 512).physarum().pointsRender().write(o0)
// Strange attractor with 3D viewport
noise().pointsEmit().attractor().pointsRender(viewMode: ortho).write(o0)
// Flow field tracing over an input image
noise().pointsEmit().flow().pointsRender().write(o0)
State Textures¶
The pointsEmit effect creates three shared global textures for agent state. All downstream effects consume them:
Texture |
Format |
Contents |
|---|---|---|
|
rgba32f (may fall back to rgba16f) |
[x, y, z, alive_flag] — Position in normalized [0,1] space, w=1 alive |
|
rgba32f (or rgba16f on constrained devices) |
[vx, vy, vz, seed] — Velocity vector and per-agent random seed |
|
rgba8 |
[r, g, b, a] — Agent color (sampled from input or mono white) |
State Texture Sizing¶
The stateSize parameter controls agent count by setting the dimensions of the state textures. Total agents = stateSize × stateSize.
stateSize |
Agent Count |
Use Case |
|---|---|---|
64 |
4,096 |
Debugging, low-end devices |
128 |
16,384 |
Light simulations |
256 |
65,536 |
Default, good balance |
512 |
262,144 |
High density effects |
1024 |
1,048,576 |
Physarum, massive swarms |
2048 |
4,194,304 |
Maximum (GPU memory dependent) |
Alive Flag Protocol¶
The xyz.w component encodes agent lifecycle state:
Value |
Meaning |
|---|---|
|
Alive and active |
|
Dead — respawn at random position next frame |
|
Dead — respawn at current xy (in-place respawn) |
Effects signal agent death by writing xyz.w = 0.0 (or -1.0 for in-place). The pointsEmit wrapper detects this on the next frame and handles respawn.
Wrapper Effects¶
pointsEmit¶
Namespace: render
Purpose: Initialize and maintain agent state. The effect runs every frame to handle respawns.
Key Parameters:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
int |
256 |
State texture dimensions (64–2048) |
|
enum |
random |
Initial distribution: random, grid, center, ring, clusters, spiral |
|
float |
0.0 |
Random seed for reproducibility |
|
float |
0.0 |
Per-frame respawn chance (0–10%) |
|
button |
— |
Force all agents to respawn |
Spawn Patterns:
random: Uniform random distribution across canvas
grid: Regular grid layout
center: Concentrated in center region
ring: Circular ring distribution
clusters: N random cluster centers with Gaussian spread
spiral: Archimedean spiral from center
pointsRender¶
Namespace: render
Purpose: Accumulate agent trails and composite with input.
Key Parameters:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
float |
50.0 |
Percentage of agents to render (0–100) |
|
float |
75.0 |
Trail persistence (0=instant fade, 100=no decay) |
|
float |
10.15 |
Input blend factor (0=trail only, 100=input visible) |
|
enum |
flat |
Viewport mode: flat (2D), ortho (fixed 3D rotation), or perspective (movable camera) |
|
float |
varies |
3D rotation in radians (when viewMode=ortho or perspective) |
|
float |
0.8 |
Zoom factor for ortho view; perspective view uses |
|
float |
0.0 |
Camera position offset (when viewMode=ortho or perspective) |
|
float |
0.0 |
Camera position offset along the depth axis (when viewMode=perspective) |
|
float |
60.0 |
Perspective camera field of view in degrees (when viewMode=perspective) |
Internal Passes:
Diffuse: Decay existing trail by intensity factor
Copy: Prepare write buffer for hardware blending
Deposit: Point-sprite rendering of agents to trail (additive blend)
Blend: Composite trail over input texture
Note
pointsRender deposits one hardware point per agent. For textured
sprites, procedural shapes, and depth-sorted alpha blending, use
pointsBillboardRender instead — see below.
pointsBillboardRender¶
Namespace: render
Purpose: Like pointsRender, but scatters each agent as a screen-facing
quad (billboard) instead of a single hardware point, so it can carry a
texture or a procedural shape, size and rotation variation, and (in
perspective view) depth-sorted alpha blending with aperture defocus blur.
Key Parameters:
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
enum |
texture |
Billboard fill: |
|
surface |
none |
Sprite texture, used when |
|
enum |
additive |
|
|
float |
8.0 |
Base billboard size in pixels |
|
float |
0.0 |
Per-agent size / rotation randomization (0–100), seeded by |
|
float |
20.0 |
Per-billboard opacity contribution |
|
— |
— |
Same camera model as |
|
float |
0.0 |
When viewMode is ortho or perspective: distance (world units) over which billboards shrink / dim as camera distance grows; 0 disables the fade |
|
float |
0.0 |
When viewMode is ortho or perspective: defocus blur strength; 0 disables it |
|
float |
80.0 |
When viewMode is ortho or perspective: camera distance that stays in
focus when |
blendMode: alpha sorts agents back-to-front every frame (a GPU merge
sort over their camera-space depth) before drawing, so overlapping
billboards composite correctly instead of fighting z-order under additive
blending. aperture blurs billboards in proportion to their distance
from focalDistance, using a precomputed mean-sprite texture so the blur
cost does not scale with billboard count.
Behavior Middleware¶
All behavior effects live in the points namespace and follow a consistent pattern:
Read
global_xyz,global_vel,global_rgbafrom pipelineApply effect-specific logic
Write updated state to the same textures through MRT output
Pass
inputTexunchanged tooutputTexfor 2D chain continuity
Available Behaviors¶
Effect |
Description |
|---|---|
|
Physics simulation with gravity, wind, drag, and wander forces |
|
Luminance-based flow field tracing with configurable behaviors |
|
Gradient descent flow (hydraulic erosion pattern) |
|
Boids flocking: separation, alignment, cohesion |
|
Slime mold chemotaxis with sensor-based steering |
|
Diffusion-limited aggregation (crystal growth) |
|
Particle life: type-based attraction/repulsion matrix |
|
Particle Lenia: continuous cellular automata with kernel-based attraction |
|
Strange attractors: Lorenz, Rössler, Aizawa, Thomas, etc. |
|
Not a simulation: arranges every agent into a fixed XZ grid with Y set from a height map, and color from a diffuse map — a landscape’s worth of positions rather than a physics or flow-field behavior |
Implementation Notes¶
MRT (Multiple Render Targets)¶
Behavior effects use MRT to update all three state textures in a single pass:
// GLSL fragment shader
layout(location = 0) out vec4 outXYZ;
layout(location = 1) out vec4 outVel;
layout(location = 2) out vec4 outRGBA;
void main() {
// Read previous state
vec4 xyz = texelFetch(xyzTex, ivec2(gl_FragCoord.xy), 0);
vec4 vel = texelFetch(velTex, ivec2(gl_FragCoord.xy), 0);
vec4 rgba = texelFetch(rgbaTex, ivec2(gl_FragCoord.xy), 0);
// Apply behavior logic...
// Write updated state
outXYZ = xyz;
outVel = vel;
outRGBA = rgba;
}
At pipeline creation, the runtime compares the combined attachment formats
with the backend’s per-sample color budget. If an MRT group is too wide, it
demotes trailing rgba32f attachments to rgba16f while further
reductions are available. On a 32-byte device, pointsEmit keeps position
data in global_xyz at full precision and uses half precision for
global_vel. A smaller reported budget can also demote global_xyz and
may still be insufficient for the irreducible three-attachment pass.
Point-Sprite Deposit¶
The deposit pass uses drawMode: "points" to scatter agents:
// Vertex shader
void main() {
// Map gl_VertexID to state texture coordinate
int texSize = int(sqrt(float(vertexCount)));
ivec2 coord = ivec2(gl_VertexID % texSize, gl_VertexID / texSize);
// Read agent position from state texture
vec4 xyz = texelFetch(xyzTex, coord, 0);
// Cull dead agents
if (xyz.w < 0.5) {
gl_Position = vec4(-10.0); // Off-screen
return;
}
// Transform to clip space
gl_Position = vec4(xyz.xy * 2.0 - 1.0, 0.0, 1.0);
gl_PointSize = 1.0;
}
Ping-Pong State¶
The runtime automatically ping-pongs state textures between frames. Effects read from the previous frame’s state and write to the current frame’s buffer. The updateFrameSurfaceBindings() method ensures within-frame visibility when multiple effects share textures.
Creating Custom Behaviors¶
To create a new SMRTicles behavior:
Create effect directory:
shaders/effects/points/myeffect/Define the effect (
definition.js):
import { Effect } from '../../../src/runtime/effect.js'
export default new Effect({
name: "MyEffect",
namespace: "points",
func: "myEffect",
tags: ["sim", "agents"],
description: "Custom particle behavior",
textures: {}, // Use shared global textures
outputXyz: "global_xyz",
outputVel: "global_vel",
outputRgba: "global_rgba",
globals: {
myParam: {
type: "float",
default: 1.0,
uniform: "myParam",
ui: { label: "my param", control: "slider" }
}
},
passes: [
{
name: "agent",
program: "agent",
drawBuffers: 3,
inputs: {
xyzTex: "global_xyz",
velTex: "global_vel",
rgbaTex: "global_rgba"
},
uniforms: { myParam: "myParam" },
outputs: {
outXYZ: "global_xyz",
outVel: "global_vel",
outRGBA: "global_rgba"
}
},
{
name: "passthrough",
program: "passthrough",
inputs: { inputTex: "inputTex" },
outputs: { fragColor: "outputTex" }
}
]
})
Implement shaders: Create
glsl/agent.glslandwgsl/agent.wgslwith your behavior logic.Test: Use the MCP tools to check compilation and rendering:
# Verify shader compiles
mcp compileEffect --effect_id points/myeffect --backend webgl2
# Check for non-monochrome output
mcp renderEffectFrame --effect_id points/myeffect --backend webgl2
Performance Considerations¶
State size scaling: Memory and bandwidth scale quadratically with
stateSize. Use the smallest size that achieves your visual goal.Density culling: The
densityparameter inpointsRenderculls agents before vertex processing, saving GPU work.Trail vs. chemistry: Effects like
physarummaintain separate pheromone textures for simulation accuracy, independent of visual trail intensity.3D projection: The ortho view mode in
pointsRenderadds rotation matrix computation per vertex. Use flat mode for 2D-only effects.Neighbor queries: Effects like
flockandlifehave O(n²) neighbor lookups. Use spatial hashing (built into the shaders) or limitstateSizefor real-time performance.