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.
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.
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 are helper classes to manipulate particles at runtime.
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.
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 ABodyA, ABodyB : TQ3Body); [...] end; [...] procedure TForm1.DoOnGorillaPhysicsNotifyContact(const ABodyA, ABodyB : TQ3Body); begin // collision detected - create splashes on particle-sphere/box/terrain/mesh collision // not on particle-particle collision (too much) if (PTypeInfo(ABodyA.UserDataType) = TypeInfo(PGorillaParticle)) and (PTypeInfo(ABodyB.UserDataType) = TypeInfo(PGorillaParticle)) then Exit; if (PTypeInfo(ABodyA.UserDataType) = TypeInfo(PGorillaParticle)) then FSplashes.EmitParticle( PGorillaParticle(ABodyA.UserData)^.Position ) else if (PTypeInfo(ABodyB.UserDataType) = TypeInfo(PGorillaParticle)) then FSplashes.EmitParticle( PGorillaParticle(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 := TParticlePreset.Create(20, 20, 1, false); FParticles.ParticleGrowth := TParticlePreset.Create(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; FParticles.Start(); FSplashes.Start(); end;
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.
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;
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 := TParticlePreset.Create(16, 16, 1, false); LParticles.ParticleGrowth := TParticlePreset.Create(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;
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;
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;
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;
var FParticles : TGorillaParticleEmitter; procedure TForm1.FormCreate(Sender: TObject); var LParticleMat : TParticleMaterialSource; LVPreset : TParticleVectorPreset; 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 LVPreset := TParticleVectorPreset.Create(0, 0, 1000, Point3D(1, 1, 1), [axX, axY, axZ]); FParticles.ParticlePosition := LVPreset ; // 2) random velocity of particles LVPreset := TParticleVectorPreset.Create(5, 10, 1000, Point3D(1, 2, 1), [axX, axY, axZ]); LVPreset.RangeY.Negative := false; FParticles.ParticleVelocity := LVPreset; // 3) the rest of properties FParticles.ParticleSize := TParticlePreset.Create(2, 3, 1, false); FParticles.ParticleAngle := TParticlePreset.Create(0, 360, 1, false); FParticles.ParticleWeight := TParticlePreset.Create(1, 1, 1, false); FParticles.ParticleGrowth := TParticlePreset.Create(0, 0, 1, false); FParticles.ParticleLifeTime := TParticlePreset.Create(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