Using the ParticleSystem

Gorilla3D provides an integrated particle system for rendering complex and individual particle effects.

A particle effect is produced by a so-called emitter represented by the basic TGorillaParticleEmitter class. Extend this class to build your own effect. Gorilla3D already supplies some of the most popular effects, like: fire, smoke, rain, snow, waterfall, eruption, explosion.

It is possible to manipulate each particle by the so-called influencers. The framework already provides a lot of the basic influencer types, like: linear movement, spiral movement, gravity, wind, particle color, trailed movement, traktor movement and physics.

Features

  • Randomizable particle properties by presets
  • Cachable particles
  • Count limitation
  • Auto emittance
  • Interactive emmittance
  • Endless or limited emmittance
  • Physically based particle collision detection with other scene objects (not with particles!)
  • Autosorting of particles depending on assigned camera

Shader / Material

The particle emitter expects a pointsprite compatible material source to be applied. Gorilla3D therefor provides the TGorillaParticleMaterialSource as default material. It supports rendering a single texture, texture-atlas (for animated particles) and colored particles on pointsprite basis.

Have a look at Materials

Of course you can write your own shader if those features do not suit your needs.

Shader Attributes

To allow extended particle functionality Gorilla3D changes the default values of some vertex attributes, because at the moment it is not possible to declare our own vertex attributes.

In the following table we show the attribute settings for each particle, when using a TGorillaParticleEmitter:

Vertex-Attribute Value
a_Position position
a_Normal velocity
a_Color0 current color
a_TexCoord0 current texture or texture atlas offset
a_Binormal X = size, Y = angle, Z = weight
a_Tangent X = age, Y = life time, Z = delta time

Influencers

Influencers are helper classes to manipulate particles at runtime.

Physics Particle Support

Our particles system can be linked to the physics system for particle collision detection.

In the following example we use the rain particle emitter as basis with a Q3 Physics Driver.

uses
  [...]
  Gorilla.Material.Particle,
  Gorilla.Particle.Emitter,
  Gorilla.Particle.Influencer,
  Gorilla.Particle.Rain,
  Gorilla.Particle.Spot,
  Gorilla.Physics,
  Gorilla.Physics.Q3.Body;
 
TForm1 = class(TForm)
  procedure FormCreate(Sender: TObject);
  protected
    FViewport : TGorillaViewport;
    FCamera : TGorillaCamera;
    FPhysics : TGorillaPhysicsSystem;
    FParticles : TGorillaRainParticleEmitter;
    FInfluencer : TGorillaPhysicsInfluencer;
    FSplashes : TGorillaSpotParticleEmitter;
 
    procedure DoOnGorillaPhysicsNotifyContact(const AContact : Pointer {PQ3ContactConstraint};
        const AOffset, ANormal : TPoint3D; const ABodyA, ABodyB : TGorillaPhysicsBody);
  [...]
end;
 
[...]
 
procedure TForm1.DoOnGorillaPhysicsNotifyContact(const AContact : Pointer {PQ3ContactConstraint};
    const AOffset, ANormal : TPoint3D; const ABodyA, ABodyB : TGorillaPhysicsBody);
begin
  // collision detected - create splashes on particle-sphere/box/terrain/mesh collision
  // not on particle-particle collision (too much)
  if (PTypeInfo(TQ3Body(ABodyA).UserDataType) = TypeInfo(PGorillaParticle))
  and (PTypeInfo(TQ3Body(ABodyB).UserDataType) = TypeInfo(PGorillaParticle)) then
    Exit;
 
  if (PTypeInfo(TQ3Body(ABodyA).UserDataType) = TypeInfo(PGorillaParticle)) then
    FSplashes.EmitParticle(
      PGorillaParticle(TQ3Body(ABodyA).UserData)^.Position
      )
  else if (PTypeInfo(TQ3Body(ABodyB).UserDataType) = TypeInfo(PGorillaParticle)) then
    FSplashes.EmitParticle(
      PGorillaParticle(TQ3Body(ABodyB).UserData)^.Position
      );
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  [...]
 
  // create physics system with contact callback function to display splashes
  FPhysics := TGorillaPhysicsSystem.Create(FViewport);
  FPhysics.OnBeginContact := DoOnGorillaPhysicsNotifyContact;
 
  // create rain particle emitter
  FParticles := TGorillaRainParticleEmitter.Create(FViewport);
  FParticles.Parent := FViewport;
  FParticles.Camera := FCamera;
  FParticles.EmitParticles := 25;
 
  // preconfigure particles
  FParticles.Colored.StartColor := TAlphaColorF.Create( TAlphaColorRec.Blue );
  FParticles.Colored.EndColor := TAlphaColorF.Create( TAlphaColorRec.Blueviolet );
  FParticles.ParticleSize.Preset(20, 20, 1, false);
  FParticles.ParticleGrowth.Preset(0, 0, 1, false);
  FParticles.Colored.Enabled := false;
  FParticles.Wind.Enabled := false;
 
  // create a physics influencer
  FInfluencer := TGorillaPhysicsInfluencer.Create(FParticles);
  FInfluencer.Emitter := FParticles;
  // link to physics system component
  FInfluencer.Physics := FPhysics;
  FInfluencer.Enabled := true;
 
  // another particle emitter to simulate rain drop splashes when they hit an obstacle
  FSplashes := TGorillaSpotParticleEmitter.Create(FViewport);
  FSplashes.Parent := FViewport;
  FSplashes.Camera := FCamera;
  FSplashes.LoadTexture('splash.png');
 
  // Activate physics
  FPhysics.Active := true;
 
  // Activate rain particle emitter
  FParticles.Start();
  FParticles.Splashes.Stop(); // BUGFIX: in some cases sub-emitters rendering black
 
  // Active splash emitter
  FSplashes.Start();
end;

Remarks

Point rendering is in some cases hardware dependent.

We've detected rendering problems with onboard-gpu's like Intel HD Graphics.

Here pointsize is clamped by hardware integration. In effect particle will only be scaled within a specific size range.

Changes since v1.1

Due to design time support we changed the TParticlePreset and TParticleVectorPreset structured to classes. This might affect elder code in your project.

Example

Fire effect

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LParticles := TGorillaFireParticleEmitter;
    LParticleMat : TParticleMaterialSource;
begin
  LParticles := TGorillaFireParticleEmitter.Create(FGorilla);
  LParticles.Parent := FGorilla;
  LParticles.Camera := FCamera;
 
  // a fire effect contains multiple parts: a front and back flame and smoke
  LParticleMat := LParticles.Back.MaterialSource as TParticleMaterialSource;
  LParticleMat.Texture.LoadFromFile('fire2.png');
 
  LParticles.LoadTexture('fire.png');
  LParticles.Smoke.LoadTexture('smoke.png');
  LParticles.Scale.Point := Point3D(5, 5, 5);
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Smoke effect with wind influencer

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LParticles := TGorillaSmokeParticleEmitter;
    LParticleMat : TParticleMaterialSource;
    LWind : TGorillaWindParticleInfluencer;
begin
  LParticles := TGorillaSmokeParticleEmitter.Create(FGorilla);
  LParticles.Parent := FGorilla;
  LParticles.Camera := FCamera;
 
  // load smoke texture atlas
  LParticles.LoadTexture('smoke.png');
 
  // randomize particle size and growth
  LParticles.ParticleSize.Preset(16, 16, 1, false);
  LParticles.ParticleGrowth.Preset(4, 8, 1, false);
 
  LWind := TGorillaWindParticleInfluencer.Create(LParticles);
  LWind.Origin := TPoint3D.Create(5, 0, 0);
  LWind.Size   := TPoint3D.Create(5, 3, 3);
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Eruption effect with color influencer

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LParticles : TGorillaEruptionParticleEmitter;
    LColorInfl : TGorillaColoredParticleInfluencer;
begin
  LParticles := TGorillaEruptionParticleEmitter.Create(FGorilla);
  LParticles.Parent := FGorilla;
  LParticles.Camera := FCamera;
 
  LColorInfl := TGorillaColoredParticleInfluencer.Create(LParticles);
  LColorInfl.StartColor := TAlphaColorF.Create(0, 0, 1, 1);
  LColorInfl.EndColor := TAlphaColorF.Create(0, 0, 0, 0);
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Rain effect

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LParticles : TGorillaRainParticleEmitter;
begin
  LParticles := TGorillaRainParticleEmitter.Create(FGorilla);
  LParticles.Parent := FGorilla;
  LParticles.Camera := FCamera;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Snow effect

Form1.pas
procedure TForm1.FormCreate(Sender: TObject);
var LParticles : TGorillaSnowParticleEmitter;
begin
  LParticles := TGorillaSnowParticleEmitter.Create(FGorilla);
  LParticles.Parent := FGorilla;
  LParticles.Camera := FCamera;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Mouse interaction

Form1.pas
var 
  FParticles : TGorillaParticleEmitter;
 
procedure TForm1.FormCreate(Sender: TObject);
var LParticleMat : TParticleMaterialSource;
    LColorInfl : TGorillaColoredParticleInfluencer;
begin
  // create the particle material shader
  LParticleMat := TParticleMaterialSource.Create(FGorilla);
  LParticleMat.Parent := FGorilla;
  LParticleMat.UseTexture := false;
  LParticleMat.IsTextureAtlas := false;
 
  // create particle emitter for spraying effect
  FParticles := TGorillaParticleEmitter.Create(FGorilla);
  FParticles.Parent := FGorilla;
 
  // connect particle shader and emitter
  LParticleMat.Emitter := FParticles;
 
  // limit number of particles
  FParticles.EmitParticles := 100;
  FParticles.MaxParticles  := 10000;
 
  // disable auto emittance, becaus we want only to emit on mouse click
  FParticles.AutoEmit := false;
 
  // enable particle caching
  FParticles.Reuse := true;
 
  // randomize particle properties
  // 1) random position of particles
  FParticles.ParticlePosition.Preset(0, 0, 1000, Point3D(1, 1, 1), [axX, axY, axZ]);
 
  // 2) random velocity of particles
  FParticles.ParticleVelocity.Preset(5, 10, 1000, Point3D(1, 2, 1), [axX, axY, axZ]);
  FParticles.ParticleVelocity.RangeY.Negative := false;
 
  // 3) the rest of properties
  FParticles.ParticleSize.Preset(2, 3, 1, false);
  FParticles.ParticleAngle.Preset(0, 360, 1, false);
  FParticles.ParticleWeight.Preset(1, 1, 1, false);
  FParticles.ParticleGrowth.Preset(0, 0, 1, false);
  FParticles.ParticleLifeTime.Preset(500, 5000, 1000, false);
 
  // create a color influencer for a starting and end color
  LColorInfl:= TGorillaColoredParticleInfluencer.Create(FParticles);
  LColorInfl.StartColor := TAlphaColorF.Create(0, 1, 0, 1);
  LColorInfl.EndColor := TAlphaColorF.Create(0, 0, 1, 0);
end;
 
var 
  FLatest : TPointF;
  FMouseMove : Boolean = false;
  FClickPt : TPoint3D; 
 
procedure TForm1.DoOnViewportMouseUp(ASender : TObject; AButton : TMouseButton;
  AShift : TShiftState; X, Y : Single);
begin
  FMouseMove := false;
end;
 
procedure TForm1.DoOnViewportMouseDown(ASender : TObject; AButton : TMouseButton;
  AShift : TShiftState; X, Y : Single);
var I : Integer;
begin
  if (ssLeft in AShift) then
  begin
    FMouseMove := true;
    FLatest := PointF(X, Y);
 
    // emit particles at mouse position
    FClickPt := FGorilla.ScreenToWorld(Point(round(X), round(Y)));
    for I := 0 to (FParticles.EmitParticles - 1) do
      FParticles.EmitParticle(FClickPt);
  end;
end;
 
procedure TForm1.DoOnViewportMouseMove(ASender : TObject; AShiftState : TShiftState;
  X, Y : Single);
var lDiff   : TPointF;
    I       : Integer;
    lOrigin : TPoint3D;
begin
  if (ssLeft in AShiftState) then
  begin
    // emit particles at mouse position
    FClickPt := FGorilla.ScreenToWorld(Point(round(X), round(Y)));
    for I := 0 to (FParticles.EmitParticles - 1) do
      FParticles.EmitParticle(FClickPt);
  end;
 
  if FMouseMove then
  begin
    FLatest := PointF(X, Y);
  end;
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  // on some constellations you will need an interval to update the viewport
  FGorilla.Invalidate();
end;

Next step: Physics