Skip to content
wezu edited this page Dec 28, 2016 · 6 revisions

Welcome to the WFX wiki!

WTF is WFX!?

Wezu Effects (or WFX) is a GPU particle rendering system for the Panda3D game engine. It is designed to be a replacement for the build-in Panda3D particle system, but without mimicking the interface of that system.

The system was designed with the following goals:

  • Make use of the GPU to render big amounts of particles faster then using the CPU
  • Make the system compatible with OpenGL 3.1 hardware/drivers
  • Use animated textures
  • Have movable emitters
  • Loading particles from a single file
  • An editor capable of saving particle systems to a file
  • Be able to change the size and mass of particles over the particle life (not just a start and end value)
  • Easy to use global and per particle forces
  • Attract (a gravity-like force), repel (a anti-gravity-like force) (not implemented and it may stay that way!)
  • Make the particles collidable with the environment (not implemented yet!)

##How it works?

The system has two pools of vertices, one for additive blending and one for alpha blending, so only 2 geoms are used to render all the particles (1 geom if all particles use the same blend mode). Each vertex is rendered as a point, the value for the size and position and life of the point is taken from a floating point texture in the vertex shader (gl_VertexID is used to index into the textures), then the fragment shader uses gl_FragCoord, the size of the screen (supplied as a uniform shader input) and the on-screen position of the point to generate UVs. Based on the 'life' variable and another texture input the fragment shader also moves the UVs to animate the particle texture.

The texture holding the position and current life of particles is updated in a series of 3 offscreen buffers. Multiple buffers are needed because without image load/store functionality (OpenGL 4.x) one can't read from and write to the same texture, so one texture must always be an 'input' texture and one an 'output'. The third texture is needed because the system needs the position of each particle from the last two frames to calculate velocity. The buffers are joggled in a very clever way (if I dare to say so myself) and only updated once every 1/60 of a second (while the whole scene may be rendered at 400+ fps).

##How to use it?

The system is written in Python (and GLSL) and you will need a development build of Panda3D (one that supports ShaderAttrib.F_shader_point_size), get it here: Panda3D on GitHub. If you're reading this after version 1.9.3 or 1.10 of Panda3D has been released, then just get it from the Panda3D download page here

To use WFX in your own scripts just import the Wfx class, you also need to have the 'wfx_shaders' folder with all its files:

from wfx import Wfx

particle=Wfx()
particle.load('some_file.wfx') #load particles form a file
particle.start() #start the simulation
#set a global (gravity?) force
particle.global_force=Vec3(0,0,-1) #new way to do it
#particle.set_global_force(Vec3(0,0,-1)) #ugly way to do it
#link a NodePath with a emitter id
particle.emitters[0].node=some_node
#set a local force on particles with id=1
particle.emitters[1].force=Vec3(2, 0.5, -0.1)
#disable particles with a emitter id =3
particle.emitters[3].active=False

Take a look inside wfx.py for a handful of usable functions, all the functions meant for end users are commented, Avoid using functions starting with a underscore ( like _make_points())... or use them if you feel like it, I'm a wiki not a cop.

###API As provided by help(Wfx):

class Wfx(__builtin__.object)
    GPU particle rendering system for Panda3D
    ----------
    Attributes:
    emitters[id].node   -Link a NodePath with a id.
                         When the Node moves or rotates, then the origin
                         of particles with that id moves or rotates with it
    emitters[id].force  -Set a loca Force (Vec3 tuple/list) on all particles with that id
    emitters[id].active -Enable (1) or disable (0) all particles with that id
    global_force        -A force (Vec3 or list/tuple) applied to all particles
    pause               -if set to True, the position of the particles will not be updated
  
    Methods defined here:
  
    __init__(self, update_speed=60.0, camera=None, root=None, window=None,
            vector_field=None, voxel_size=LVector3f(200,200, 200),
            heightmap_resolution=0, world_size=100, heightmap_mask=17,
            heightmap_padding=0.5, collision_depth=1.0, velocity_constant=0.05)
            
        Args (all argumetns are optional):
            update_speed (float)     - how fast will the simulation run in FPS
            camera(NodePath/Camera)  - the default scene camera (default base.camera)
            root(NodePath)           - root node for the simulation (default rander/root)
            window(GraphicsOutput)   - the window to display the particles in (needed only for it's size)
            vector_field(texture)    - a 3D texture for collisions and/or forces,
                                       None to disable, can be a Texture object a txo file or a txo in a multifile
            voxel_size (VBase3)      - size of the vector field in world units
            heightmap_resolution(int)- 0 disables rendering a heightmap, else the size of the heightmap
            world_size(float)        - the size of the heightmap in world space units
            heightmap_mask(int)      - camera bitmask value (0-32) for rendering collision heightmap
            heightmap_padding        - an offset for rendering the heightmap
            collision_depth          - how far are particles tested for collisions
            velocity_constant        - all forces are multiplied by this value (sort of... )
  
    cleanup(self)
        Removes everything, call this when you now longer need the particle system
  
    load(self, *args, **kwargs)
        Loads values needed to run the simulation.
  
        You can pass in the location of a multifile with all the textures/data,
        or all the textures/data as keyword agruments
        Args:
            multifile(string)   - loction(path) of the multifile with the data
                                  (the default editor saves the mutifiles with a .wfx extension)
            pos_0 (Texture)     -pos/life of the particles at time=0
            pos_1 (Texture)     -pos/life of the particles at time=1
            mass (Texture)      -mass sinusiod co-factors
            size (Texture)      -size sinusiod co-factors
            one_pos(Texture)    -position to reset to
            zero_pos(Texture)   -position to reset to
            data (dict)         -{'num_emitters':...,'status':...,'blend_index':...,'forces':...}
            texture (Texture)   -visible texture
            offset (Texture)    -rgba(+U,+V, frame_size, number_of_frames)
            props(Texture)      -rgba(start_life, max_life, emitter_id, bounce)
  
    on_window_resize(self)
        Call this function each time the window changes its size!
  
    reset(self)
        Resets the system, removes all values set by load()
  
    restart(self)
        Restarts the whole system to the initial value (after load() was called)
  
    set_emitter_active(self, emitter_id, active)
        Turns on (active=1) or off (active=0) all the particles with a given emitter_id
        Hint: use the emitters[emitter_id].active propertie, eg. fx.emitters[0].active=True
  
    set_emitter_force(self, emitter_id, force)
        Sets a local force affecting all particles with a given emitter_id
        Hint: use the emitters[emitter_id].force propertie, eg. fx.emitters[0].force=Vec3(1,1,0)
  
    set_emitter_node(self, emitter_id, node)
        Links a node with a emitter_id.
  
        When the node moves or rotates, then the origin of particles
        with that emitter_id moves or rotates with it.
        Hint: use the emitters[emitter_id].node propertie, eg. fx.emitters[0].node=some_node_path
  
    set_emitter_off(self, emitter_id)
        Turns off all the particles with a given emitter_id
        Hint: use the emitters[emitter_id].active propertie, eg. fx.emitters[0].active=True
  
    set_emitter_on(self, emitter_id)
        Turns on all the particles with a given emitter_id
        Hint: use the emitters[emitter_id].active propertie, eg. fx.emitters[0].active=True
  
    set_global_force(self, force)
        Sets a force affecting all particles, force should be vector in world space (list/tuple will also work)
        Hint: use the global_force propertie, eg. fx.global_force=Vec3(0,0,-1)
  
    set_pause(self)
        Pauses the particle system without hiding anything, use this for 'time stop'
        Hint: use the .pause propertie eg. fx.pause=True
  
    start(self)
        Starts the particle system, call this after calling load()

##The editor

The hardest part of making good looking particles is... well making the particles. WFX comes with a editor that should make it a bit easier. The editor is still in development, more informations on how to use will fallow. Soon. Have some screenshots of it while you wait. setup

  1. Particle Pool - the total number of available particles. This field is interpreted (run through eval()) so a value of 64*64 will give you 4096 particles. The editor will find a texture size that is power-of-two and big enough to fit the set number of particles. There could actually be more particles available, but never less. 1 048 576 (1024*1024) should be a soft limit, you can have more but above this size/number the system may slow down significantly, on the other hand the difference between running physics for 4k particles and 250k particles is minute (that is 'small' not '60 seconds').

main panel curve editor texture editor

Clone this wiki locally