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