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 type |
Description |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LOD
Return type |
Description |
|
|
|
|
|
|
|
|
|
|
|
|
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 type |
Description |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.