1. Home
  2. Docs
  3. PopcornFX v2
  4. Scripting reference
  5. sim

sim

[v2.2.0] 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
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.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.
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.

 

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

 

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.

 

Note that in v2.2, there are no overloads of the sim.wave functions for vectors, so you can’t use them directly on float3s, you will need to compute the average of each component separately.

Vector versions of the sim.wave functions will come in v2.3

 

    float3 avgPos = float3(sim.wave.average(Position.x),  // returns 1 value per wavefront
                           sim.wave.average(Position.y),
                           sim.wave.average(Position.z));
    float dist = view.distance(Position);                 // once per wavefront
    float myCustomLOD = pow(saturate(dist / 1000), 0.5);  // once per wavefront
    float4 lodColor = Curve.sample(myCustomLOD)           // once per wavefront

 

 

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.

 

In an upcoming version, we will introduce lightweight debug helpers in the debug namespace, such as a debug.drawPoint() and debug.drawLine() function, which will make 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?