.. _shader-smrticles: SMRTicles ========= Smart Particles ~~~~~~~~~~~~~~~ *Stateful Massively-parallel Runtime for particles* **or:** *Simulations with Multiple Render Targets* **or:** *Whatever you'd like* Noisemaker's shader pipeline includes common architecture for GPU-accelerated agent-based particle simulations. It provides a unified framework for initializing, simulating, and rendering perhaps millions of particles with shared state management and composable behaviors. .. raw:: html

         
         
Loading...
Parameters
---- 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 factors this common infrastructure into two **wrapper effects** (``pointsEmit`` and ``pointsRender``) that sandwich effect-specific **behavior middleware**. This architecture achieves: 1. **Code Reduction**: ~40% less shader code per effect 2. **Consistency**: All effects share the same deposit/blend approach 3. **Composability**: Mix and match behaviors in a single pipeline 4. **Maintainability**: Bug fixes propagate to all particle effects ---- Architecture Overview --------------------- Pipeline Structure ^^^^^^^^^^^^^^^^^^ Every SMRTicles composition follows this three-stage pipeline: .. code-block:: none (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: .. code-block:: javascript // 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 -------------- SMRTicles uses three shared global textures for agent state, created by ``pointsEmit`` and consumed by all downstream effects: .. list-table:: :header-rows: 1 :widths: 15 20 65 * - Texture - Format - Contents * - ``global_xyz`` - rgba32f - [x, y, z, alive_flag] — Position in normalized [0,1] space, w=1 alive * - ``global_vel`` - rgba32f - [vx, vy, vz, seed] — Velocity vector and per-agent random seed * - ``global_rgba`` - rgba8 - [r, g, b, a] — Agent color (sampled from input or mono white) State Texture Sizing ^^^^^^^^^^^^^^^^^^^^ Agent count is controlled by the ``stateSize`` parameter, which sets the dimensions of the state textures. Total agents = stateSize × stateSize. .. list-table:: :header-rows: 1 :widths: 20 30 50 * - 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: .. list-table:: :header-rows: 1 :widths: 15 85 * - Value - Meaning * - ``1.0`` - Alive and active * - ``0.0`` - Dead — respawn at random position next frame * - ``-1.0`` - 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. Runs every frame to handle respawns. **Key Parameters:** .. list-table:: :header-rows: 1 :widths: 20 15 15 50 * - Parameter - Type - Default - Description * - ``stateSize`` - int - 256 - State texture dimensions (64–2048) * - ``layout`` - enum - random - Initial distribution: random, grid, center, ring, clusters, spiral * - ``seed`` - float - 0.0 - Random seed for reproducibility * - ``attrition`` - float - 0.0 - Per-frame respawn chance (0–10%) * - ``resetState`` - 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:** .. list-table:: :header-rows: 1 :widths: 20 15 15 50 * - Parameter - Type - Default - Description * - ``density`` - float - 50.0 - Percentage of agents to render (0–100) * - ``intensity`` - float - 75.0 - Trail persistence (0=instant fade, 100=no decay) * - ``inputIntensity`` - float - 10.15 - Input blend factor (0=trail only, 100=input visible) * - ``viewMode`` - enum - flat - Viewport mode: flat (2D) or ortho (3D) * - ``rotateX/Y/Z`` - float - varies - 3D rotation in radians (when viewMode=ortho) * - ``viewScale`` - float - 0.8 - Zoom factor for 3D view * - ``posX/Y`` - float - 0.0 - Position offset for 3D view **Internal Passes:** 1. **Diffuse**: Decay existing trail by intensity factor 2. **Copy**: Prepare write buffer for hardware blending 3. **Deposit**: Point-sprite rendering of agents to trail (additive blend) 4. **Blend**: Composite trail over input texture .. note:: Additional sprite rendering modes (textured sprites, billboards, custom shapes) are planned for future releases. ---- Behavior Middleware ------------------- All behavior effects live in the ``points`` namespace and follow a consistent pattern: 1. Read ``global_xyz``, ``global_vel``, ``global_rgba`` from pipeline 2. Apply effect-specific logic 3. Write updated state back (MRT output to same textures) 4. Passthrough ``inputTex`` to ``outputTex`` for 2D chain continuity Available Behaviors ^^^^^^^^^^^^^^^^^^^ .. list-table:: :header-rows: 1 :widths: 15 85 * - Effect - Description * - ``physical`` - Physics simulation with gravity, wind, drag, and wander forces * - ``flow`` - Luminance-based flow field tracing with configurable behaviors * - ``hydraulic`` - Gradient descent flow (hydraulic erosion pattern) * - ``flock`` - Boids flocking: separation, alignment, cohesion * - ``physarum`` - Slime mold chemotaxis with sensor-based steering * - ``dla`` - Diffusion-limited aggregation (crystal growth) * - ``life`` - Particle life: type-based attraction/repulsion matrix * - ``lenia`` - Particle Lenia: continuous cellular automata with kernel-based attraction * - ``attractor`` - Strange attractors: Lorenz, Rössler, Aizawa, Thomas, etc. ---- Implementation Notes -------------------- MRT (Multiple Render Targets) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Behavior effects use MRT to update all three state textures in a single pass: .. code-block:: glsl // 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; } Point-Sprite Deposit ^^^^^^^^^^^^^^^^^^^^ The deposit pass uses ``drawMode: "points"`` to scatter agents: .. code-block:: glsl // 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: 1. **Create effect directory**: ``shaders/effects/points/myeffect/`` 2. **Define the effect** (``definition.js``): .. code-block:: javascript 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" } } ] }) 3. **Implement shaders**: Create ``glsl/agent.glsl`` and ``wgsl/agent.wgsl`` with your behavior logic. 4. **Test**: Use the MCP tools to verify compilation and rendering: .. code-block:: bash # 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 ``density`` parameter in ``pointsRender`` culls agents before vertex processing, saving GPU work. - **Trail vs. chemistry**: Effects like ``physarum`` maintain separate pheromone textures for simulation accuracy, independent of visual trail intensity. - **3D projection**: The ortho view mode in ``pointsRender`` adds rotation matrix computation per vertex. Use flat mode for 2D-only effects. - **Neighbor queries**: Effects like ``flock`` and ``life`` have O(n²) neighbor lookups. Use spatial hashing (built into the shaders) or limit ``stateSize`` for real-time performance.