Progress update: July 2018


#1

collision

Hi everyone, a new progress update is here along with a bunch of new enhancements added in the last month! But first off, huge thanks to all the Patrons supporting the project - every little bit helps me spend more time on this project.

What’s new? I’ve been mostly working on the particle system which I will be focusing on in this post. The new features are:

Particle system exposed to user space

CParticleSystem component is now available and can be used by anyone using the latest master version! Note that this interface is not yet stable and is subject to change. Also note that a variety of features are still missing.

To add a particle system to the scene simply create a CParticleSystem component same as with any other component.

HSceneObject particleSO = SceneObject::create("Particles");
HParticleSystem particleSystem = particleSO->addComponent<CParticleSystem>();

Assigning a material
Next up you will need to assign a material to render your particles with. You can use the newly added BuiltinShader::ParticlesUnlit shader for your material, or use your own.

HShader particleShader = gBuiltinResources().getBuiltinShader(BuiltinShader::ParticlesUnlit);
HMaterial particleMaterial = Material::create(particleShader);

HTexture someTexture = ...;
particleMaterial->setTexture("gTexture", someTexture);

// Optionally use a sprite texture if you need texture animation
// HSpriteTexture someSpriteTexture = ...;
// particleMaterial->setSpriteTexture("gTexture", someSpriteTexture);

Once the material is created you need to assign it to a ParticleSystemSettings object. This object controls all the general settings for a particular ParticleSystem component.

ParticleSystemSettings psSettings;
psSettings.material = particleMaterial;

particleSystem->setSettings(psSettings);

Emission shape
Once the material is assigned you will also need at least one ParticleEmitter. Emitters determine the shape from which the particles are emitted from, as well as a set of general properties such as particle speed, lifetime, size and similar.

// Create an emitter that emits particles from a volume of a sphere
PARTICLE_SPHERE_SHAPE_DESC sphereShape;
sphereShape.radius = 0.3;
sphereShape.thickness = 1.0f

auto emitter = bs_shared_ptr_new<ParticleEmitter>();
emitter->setShape(ParticleEmitterSphereShape::create(sphereShape));

And set a few other basic properties.

// Using constants here for simplicity, but random ranges and curves are accepted as well
emitter->setEmissionRate(200.0f);
emitter->setInitialSize(0.1f);
emitter->setInitialSpeed(2.0f);
emitter->setInitialLifetime(5.0f);

A variety of emitter shapes are available, along with many customization options. See BsParticleEmitter.h for a full list.

Finally, to actually add the emitter to the particle system retrieve a modifiable emitter list through ParticleSystem::getEmitters() and add an entry.

particleSystem->getEmitters().add(emitter);

Evolvers
At minimum, the material and a single emitter is all you need for a particle system. But by adding ParticleEvolvers you get to control particle behavior in more advanced ways. There are a few evolver types available now, and a lot more to come later, and a few new ones we talk about later in this text.

They are created and added similarly to emitters.

// Add an evolver that adds texture animation to the particles
PARTICLE_TEXTURE_ANIMATION_DESC texAnimDesc;
texAnimDesc.numCycles = 5;

auto texAnimEvolver = bs_shared_ptr_new<ParticleTextureAnimation>(texAnimDesc);
particleSystem->getEvolvers().add(texAnimEvolver);

More particle settings

ParticleSystemSettings contains a variety of other properties you might want to tweak. orientation field allows you to tweak how are particles positioned relative to the camera. They can orient towards camera point or plane (most common), rotate around Y (e.g. grass billboards) or be limited for a single plane (for 2D particles).

You may also control particle sorting through the sort field. Sorting is also new with this update, and ensures you can sort the particles to suit the needs of your blending mode. For example, proper transparency requires the particles to be rendered back to front.

simulationSpace controls in which space are particles emitted/simulated. Local space ensures the entire particle system will move with its parent object (e.g. electricity sparks on a moving car). While world space ensures the particles stay at their original position while newly emitter particles spawn at new position (e.g. flamethrower flames when the character is moving).

And a variety of other options like the maximum number of particles, duration of animation (for curves), looping and random seeds (allowing you to control the randomness of particles).

ParticleSystemSettings psSettings;
psSettings.orientation = ParticleOrientation::ViewPlane;
psSettings.orientationLockY = true;
psSettings.sortMode = ParticleSortMode::Distance;
psSettings.material = particleMaterial;

particleSystem->setSettings(psSettings);

Collisions

collision

The particles can now interact with the world through the new ParticleCollisions evolver.

The system comes in two modes: Plane which is cheaper performance wise but limits the collisions to a user-provided set of planes, and World which allows complete collision with any physical object (i.e. Collider) in the scene.

// Create a plane collider
PARTICLE_COLLISONS_DESC collisionsDesc;
collisionsDesc.mode = ParticleCollisionMode::Plane;
collisionsDesc.radius = 0.2f;

auto collisionEvolver = bs_shared_ptr_new<ParticleCollisions>(collisionsDesc);

// Add a single plane to collide with
collisionEvolver->setPlanes( { Plane(Vector3::UNIT_Y, 0.0f)});

// Register the evolver
particleSystem->getEvolvers().add(collisionEvolver);
// Create a world collider
PARTICLE_COLLISONS_DESC collisionsDesc;
collisionsDesc.mode = ParticleCollisionMode::World;
collisionsDesc.radius = 0.2f;

auto collisionEvolver = bs_shared_ptr_new<ParticleCollisions>(collisionsDesc);

// Register the evolver
particleSystem->getEvolvers().add(collisionEvolver);

Velocity evolvers

orbit-linear

New evolvers have been added for controlling velocity and acceleration:

ParticleVelocity
Applies linear velocity, controllable through a curve and evaluated over particle lifetime.

PARTICLE_VELOCITY_DESC velocityDesc;
orbitDesc.velocity = Vector3(0.0f, 0.2f, 0.0f);

auto velocityEvolver = bs_shared_ptr_new<ParticleVelocity>(velocityDesc);
particleSystem->getEvolvers().add(velocityEvolver);

ParticleOrbit
Applies velocity in a way that makes particles orbit a point. The particles can orbit at a fixed distance or move towards/away the point, as well as spinning around. You can control the orbit planes per-axis.

PARTICLE_ORBIT_DESC orbitDesc;
orbitDesc.center = Vector3(0.0f, 0.0f, 0.0f);
orbitDesc.velocity = Vector3(0.0f, 1.2f, 0.0f); // In rotations/second
orbitDesc.radial = 0.4f;

auto orbitEvolver = bs_shared_ptr_new<ParticleOrbit>(orbitDesc);
particleSystem->getEvolvers().add(orbitEvolver);

Gravity
Finally, gravity can now be enabled on particles. Instead of using an evolver this is done through ParticleSystemSettings::gravityScale field. Gravity scale of 1 will make the particles use the same gravity as the physics system.

ParticleSystemSettings psSettings = particleSystem->getSettings();
psSettings.gravityScale = 1.0f;

particleSystem->setSettings(psSettings);

Mesh emitters

mesh-emit

Meshes can now be used as particle emitter shapes! This includes both static as well as skinned meshes. You can control whether you want to emit from the entire mesh surface, mesh edges or mesh vertices. Particles will inherit mesh normals for their initial velocity.

Use the ParticleEmitterStaticMeshShape and ParticleEmitterSkinnedMeshShape types to add static and skinned mesh shapes to an emitter, respectively.

// When creating a static shape supply a HMesh handle
PARTICLE_STATIC_MESH_SHAPE_DESC staticMeshShape;
staticMeshShape.mesh = ExampleFramework::loadMesh(ExampleMesh::Pistol);

emitter->setShape(ParticleEmitterStaticMeshShape::create(staticMeshShape));

// When creating a skinned shape supply a Renderable component
// (same scene object should also have an Animation component that runs the animation)
HRenderable renderable = ...;

PARTICLE_SKINNED_MESH_SHAPE_DESC skinnedMeshShape;
skinnedMeshShape.renderable = renderable;

emitter->setShape(ParticleEmitterSkinnedMeshShape::create(skinnedMeshShape));

Other improvements

  • RTTI system was refactored resulting in a significantly leaner system with a lot less overhead. Field getter/setters used to use a std::function along with Any to store function pointers, both of which come with dynamic allocations and significant overhead. Now the system is just storing cheap, fast, raw function pointers.
  • IReflectable types no longer have mRTTIData field. This field was being used for temporary storage during serialization & deserialization, but it was present on ALL the IReflectable types, ALL the time. And there are a lot of those types, meaning we were adding a lot of overhead that isn’t being used except for a few specific types and even then only during serialization/deserialization. Now this field has been removed and instead RTTIType calls will be made on their own RTTIType instance. This means you can use RTTIType directly to store temporary serialization data. The instances are allocated from a frame allocator, making their allocations are lightning fast. And keeping the data local to RTTIType makes the code significantly cleaner, along with the overhead benefits of removing mRTTIType.
  • A lot of work on the editor. Major fixes to the animation window, making the animation curve editor general purpose so it can be used for the particle system, adding GUIColorGradient GUI element, color gradient picker window and more.
  • Moving more of the script bindings to use the automated script binding system. Significant strides made on making all GUIElement types script exportable.
  • BS_PARAMS modifier that allows a Vector parameter be exported as a variable parameter entry in script code (params in C#)
  • Evaluation for integrated and doubly integrated animation curves. As well as min/max range calculation for normal and integrated curves. Planned to be used for the analytical particle system evaluation (explained more below).
  • Major performance improvements when rendering with MSAA enabled. In some cases 200% or more.

What’s next?

I’m still primarily continuing the work on the particle system, with new features in focus:

  • GPU particles. Build a system that performs particle simulation fully in a shader. This is intended to be used for massive particle effects with tens of thousands of particles. Ideally I want to add support for vector fields, so particle effects can be exported from tools such as Vectoraygen or Maya Fluids. I’ve tried to prepare the current system so most of it can also be used for GPU work, but regardless a significant amount of work is still to be done.
  • 3D particles. Extend the current system so particle rendering can use arbitrary 3D meshes instead of just billboards.
  • Emitter bursts. Currently particles can only be emitted continuously, but it would be useful to specify times at which to emit bursts of particles for more advanced effects.
  • Analytic particles. Evaluate particle properties analytically. This would be only possible with specific evolver types but in general would allow the code to get a complete particle system state just from a time value, instead of evaluating the particle system in incremental steps. This allows the particle system to run in reverse with no additional logic. It also makes particle system bounds calculation much faster as they do not need to be recalculated every frame, which can result in a significant performance boost.

There is of course more to come after that. GPU particles being the main big thing that remains, while most of the other work should be minor additions and tweaks.

On the editor side I’m working on exposing the particle system to the editor, making scene loading asynchronous and potentially refactoring how resource handles work in script code to allow more control with async loading. I’m also resolving all remaining issues and working on various polish and quality of life improvements, preparing for the next release.

That’s it for now, cheers!