PopcornFX v2.19

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
  1. Home
  2. Docs
  3. PopcornFX v2.19
  4. Scripting reference
  5. Namespaces
  6. sim

sim

The sim namespace contains simulation and LOD-related functions.

These functions are mostly useful for optimizations. Their results depend on various runtime parameters, such as localized pages status and resolution, simulation unit wavefront size (particle page-size), and should be used with care.

 

Return typeDescription
intsim.updateRate()
Update rate of the current simulation unit.

  • 0: full update rate
  • 1: high update rate
  • 2: medium update rate
  • 3: low update rate
intsim.frameStep()
Frame update rate frequency of the current simulation unit.
This value is the number of frames the simulation will likely wait before running at this update rate again.
There is no guarantee this particle will wait this exact amount of frames before re-running at this update rate.
It could be more, it could be less, but it is generally a good approximation.
Example: Close-by effects running their ‘low’ update rate graph might have it run once every 4 frames.
Far-away effects running their ‘low’ update rate graph might have it run once every 32 frames.
boolsim.isDeterministic()
Returns true if the simulation for this particle is running in deterministic mode, false otherwise.
boolsim.isRunningOnGPU()
Returns true if the current simulation is running on the GPU, false otherwise (CPU simulation).
The returned value can differ between spawn and evolve.
#sim.disable_opts(# value) [v2.16.0]
Disables compiler optimizations on the input value.
The input can be any numeric value or vector, and it will be returned untouched by the function.
This is mostly useful for compiler debugging.
int2sim.selfId() [v2.16.0]
Returns the internal 64-bit particle ID.
Beware when using this ! The ID is not 0-based, its value is mostly undefined and the internal bit-layout can change without notice in a future SDK update.
This value will never be the same for any particle in the entire PopcornFX scene, unless the x component overflows a 32-bits integer and wraps around.
int2sim.parentId() [v2.16.0]
Returns the sim.selfId of the parent particle that spawned the current particle.
Beware when using this ! The ID is not 0-based, its value is mostly undefined and the internal bit-layout can change without notice in a future SDK update.
This value will never be the same for any particle in the entire PopcornFX scene, unless the x component overflows a 32-bits integer and wraps around
intsim.effectId() [v2.16.0]
Returns the effect instance internal ID. This value is unique at any given time and will not change during the lifetime of an instance.
However, when the instance dies and a new instance is created, possibly from a different effect, the ID is recycled.

 

LOD

Return typeDescription
floatsim.lod()
LOD level of the current simulation unit.
This value is in the [0, 1] range.
floatsim.lodBias()
LOD bias of the current simulation unit.
This value is in the [-1, 1] range.
floatsim.lodDistanceMin()
Returns the min LOD distance of the current simulation unit.
Takes into account layer LOD config overrides.
This value is in the [-1, 1] range.
floatsim.lodDistanceMax()
Returns the max LOD distance of the current simulation unit.
Takes into account layer LOD config overrides.
This value is in the [-1, 1] range.
floatsim.lodQueryPoint(float3 position)
Returns the LOD level of the specified position.
This value is in the [0, 1] range.
floatsim.lodQueryBox(float3 boxMin, float3 boxMax)
Returns the LOD level of the specified axis-aligned bounding box.
boxMin is the min corner of the bounding-box.
boxMax is the max corner of the bounding-box.
This value is in the [0, 1] range.

 

Wave functions

The sim.wave namespace contains simulation wavefront-wide reduction functions.
This is considered an advanced topic and should be used with care, as incorrect usage can severely degrade the visual quality of your effects, in non-obvious ways.

 

These functions will perform reduction operations across an entire simulation wavefront, similar to how the GPU wave intrinsics work.
When the simulations run on the CPU, these will usually work with 1024-wide wavefronts, but this number can vary dynamically based on the simulation load and machine configuration.

To better understand what simulation wavefronts are, you can see this dev-blog article on the PopcornFX execution model (which still holds in v2.0)

 

The sim.wave functions are most useful in the context of optimization and LOD, to reduce stream values (1 value per particle) to normal values (1 value per wavefront), and therefore reduce the computational cost of whatever computation is done next with that value. They can typically be used to compute a wavefront-wide LOD metric, or run an expensive operation (such as sampling a turbulence or a spatial layer) once per wavefront instead of once per particle, at the expense of precision/correctness (which is often fine in the context of LOD when particles are far away).

 

Return typeDescription
floatsim.wave.ratio(bool condition)
Returns the ratio of the number of particles for which condition is true over the total number of active particles in the current simulation wave.
If all particles evaluate condition to true, the function will return 1.0. If half of the particles evaluate condition to true, the function will return 0.5.
boolsim.wave.any(bool condition)
Boolean ‘OR‘ across all particles of the current simulation wave:
Returns true if condition is true for any particle.
Returns false as soon as at least one particle evaluates condition to false.
boolsim.wave.all(bool condition)
Boolean ‘AND‘ across all particles of the current simulation wave:
Returns true if condition is true for all particles.
Returns false as soon as at least one particle evaluates condition to true.
intsim.wave.or(int value)
Bitwise ‘OR‘ across all particles of the current simulation wave.
intsim.wave.and(int value)
Bitwise ‘AND‘ across all particles of the current simulation wave.
floatsim.wave.average(float value)
Returns the average of value across all particles in the current simulation wave.
float
int
sim.wave.min(float value)
sim.wave.min(int value)

Returns the smallest value of value across all particles in the current simulation wave.
float
int
sim.wave.max(float value)
sim.wave.max(int value)

Returns the largest value of value across all particles in the current simulation wave.
float
int
sim.wave.sum(float value)
sim.wave.sum(int value)

Returns the sum of value across all particles in the current simulation wave.Note: if value is a constant, the expression will be evaluated in the nodegraph compiler, which has a wavefront size of 1.
Therefore you cannot use sim.wave.sum(1) to get the current runtime wavefront physical size. If you need the size to compute an average by dividing it with another sum, use sim.wave.average(x) instead
intsim.wave.id() [v2.7.0]
Returns the particle ID in the current simulation wavefront
float<n>
int<n>
bool<n>
sim.wave.at(auto value, int laneId) [v2.18.0]
Returns the value of value at lane index laneId
float<n>
int<n>
bool<n>
sim.wave.at_cst(auto value, int laneId) [v2.18.0]
Returns the value of value at a constant lane index laneId.
laneId cannot vary per-particle, it must be a single value per-wave.

 

Examples

Getting the view distance of all particles and doing a curve lookup and LOD computation per particle vs per simulation unit.

Per particle:

    float dist = view.distance(Position);                 // closest cam & distance computed once per particle
    float myCustomLOD = pow(saturate(dist / 1000), 0.5);  // pow, saturate, and div all computed once per particle
    float4 lodColor = Curve.sample(myCustomLOD)           // curve sampled once per particle

 

Using the sim.wave.min function to get the closest distance across the entire simulation wavefront:

    float dist = view.distance(Position);                    // once per particle
    float closest = sim.wave.min(dist);                      // returns 1 value per wavefront
    float myCustomLOD = pow(saturate(closest / 1000), 0.5);  // once per wavefront
    float4 lodColor = Curve.sample(myCustomLOD)              // once per wavefront

 

Here, we have cut down on the cost of the pow, saturage, div, and curve sampling computations by doing them once per wavefront. However, the view.distance function is still computed per particle.

If we can get away with computing that metric using the average position instead of the closest, we can use sim.wave.average(Position) to compute the average position, then do the view.distance call on that average position, and the view.distance call will now run once per wavefront instead of once per particle.

 

 

Pitfalls

Observation with debug particle renderers will interact with the system and change the results.

As the simulation units topology is determined by some internal runtime heuristics, amongst which particle-page localization, trying to visually debug the result of these functions with regular particle renderers will usually end up modify the sim unit bounding boxes, destroy page localization, and will make the thing impossible to observe.

 

Since v2.16, you can use the lightweight debug helpers in the debug namespace, such as a debug.drawLine(), which do not use regular particle renderers, and therefore do not affect particle bounds, making it possible to visually debug things without influencing parts of the simulation sensitive to locality / bounds.

Was this article helpful to you? Yes No

How can we help?