Skip to content

[Particle] Evaluate per-particle seed vs packed random attributes #3075

Description

@GuoLei1990

Context

PR #3060 fixes Size over Lifetime TwoCurves, but the current 168-byte particle instance layout has no unused random component. a_Random0.z is therefore shared by Noise strength and Size over Lifetime; when both modules are enabled, Noise takes precedence and both shaders consume the same random factor. This makes two independent modules visibly correlated.

The instance buffer currently sends three vec4 random attributes (48 bytes per particle). Some modules require independent XYZ factors, so reclaiming a float by silently sharing another slot would only move the coupling elsewhere.

Goal

Give particle modules stable, independent random values while reducing or at least not increasing instance-buffer bandwidth and vertex attribute usage.

Options to evaluate

1. Per-particle seed + salted GPU derivation

Store one particle seed and derive module/axis values using stable salts, for example:

  • Noise: hash(seed + NOISE_SALT)
  • Size: hash(seed + SIZE_SALT)
  • Velocity XYZ: hash3(seed + VELOCITY_SALT)
  • Force XYZ: hash3(seed + FORCE_SALT)

This matches the ownership model used by Unity: one seed per particle, with module/curve IDs offsetting randomness.

Risks to validate:

  • Vertex shader hash cost is paid per vertex, not once per instance; mesh particles may amplify the ALU cost substantially.
  • WebGL1 lacks the integer/bitwise facilities used by common 32-bit hashes.
  • CPU paths such as sub-emitter inherited size/color/rotation must reproduce the same derived values.
  • Results must remain stable when unrelated modules are enabled or disabled.

2. Pack CPU-generated random factors

Keep generating factors once on the CPU, but store them as NormalizedUShort4 or NormalizedUByte4 instead of 32-bit floats.

A possible layout is:

  • StartSpeedGravity: two floats
  • packed random 0: Color, Noise, Size, Rotation
  • packed random 1: Texture Sheet, Velocity X/Y/Z
  • packed random 2: Force X/Y/Z, Limit Velocity

This gives Size an independent channel without increasing attribute count. Estimated instance stride:

  • NormalizedUShort4: about 148 bytes, down from 168 bytes (~12%)
  • NormalizedUByte4: about 136 bytes, down from 168 bytes (~19%), with 8-bit interpolation precision

Benchmark matrix

Compare the current float attributes, packed factors, and seed/hash variants across:

  • Billboard and mesh render modes
  • Low- and high-vertex meshes
  • Representative particle counts
  • WebGL1 and WebGL2
  • Mobile TBDR and desktop GPUs where available

Measure:

  • CPU particle emission/update time
  • Instance-buffer upload bytes and time
  • Vertex GPU time / frame time
  • Visual distribution and determinism
  • CPU/GPU agreement for sub-emitter inheritance

Acceptance criteria

  • Noise and Size over Lifetime have independent random factors when both are enabled.
  • Enabling an unrelated module does not change another module's random sequence.
  • No regression in WebGL1 compatibility.
  • No increase in required vertex attribute count.
  • The selected design is supported by benchmark evidence rather than bandwidth or ALU assumptions alone.

Related: #3060

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions