Jump to content

Smoke ribbons, when emitters are too slow



Particle engines are commonly used for fire and smoke effects adding a lot of eye-candy for little effort. Every frame an emitter creates a number of billboard sprites along a vector. Normally this is not an issue when movement along this vector between frames is quite small.


But what if you're talking about something that moves really fast between frames? Such as a rocket launcher or space-ship? Fellow Brit and indy developer Cliff Harris of Gratuitous Space Battles fame ran into the same problem. Here's a screenshot from his blog of a rocket.




The rocket moves too far between frames to space out the particles in a pleasing manner. By adding a line of particles between each frame update a more pleasing effect is achieved.





In Combat-Helo the hero-ship is typically loaded with several 70mm rockets that accelerate to 700 meters per second in approx 1.5 seconds. Even maintaining a short smoke trail of 10 meters can't be maintained as the rocket distance between two frames might be 50 meters or more.


A linked list of billboards (TMesh CreatePlane()) is being trialled. UsingMacklebees billboard shader tweak and changing...

... gl_Vertex.x,gl_Vertex.y
... gl_Vertex.x,gl_Vertex.z

...to work for Planes a 2D billboard function was implemented which handled colour and timing properties with a deviation (waver) offsets.


This creates a ribbon of 50-150 quads (TPlanes) aligned to the camera using the billboard shader. Scale of each quad is roughly double the space between them to ensure some overlap and consistency. The smoke texture has baked lighting with a normal map however a normal map is redundant since the quad is camera aligned and rendered in the transparency layer.


The spacing produces a good fill between two positions each frame. In the example image below the length of the trail is 75 meters, each particle doesn't require much updating since this was written for a specific purpose. As the shader takes care of orientation only timer tests, waver and alpha-colour needs updating although some extra instruction for management could be implemented (see footnote).


Don't attempt to use ScaleMesh() as that slows the whole pipeline down. If you need to change the size of a particle you'll either have to do it in the shader or delete the particle and replace it with a larger one.




Same again showing the AABB of each TMesh/Plane.




That's about as fast I can can come up with using LE commands. The next stage is to replace the TPlane billboards with a single entity built using a TMesh and adding triangles as needed.


Other optimisations might include changing the number of particles depending on the 2D length between the head and tail. We need more 'filler' if seen from the side than head on.


Worst case would be a full salvo from 4 pods (each pod carries 9 rockets), in this event the particle manager could limit them. With the Leadwerks emitter function, once created you can't change the number of particles so having this level of control is handy for all manner of effects that need adjustment over time. This is of course slower than the LE particle system.


Recommended Comments

I cannot take credit for the billboard shader - it was originally in the one of the old mesh.vert files from LE 2.2x. I had originally stated that in my first post but forgot to mention it in the copied version after the previous data loss. But I am glad the post had helped out in any case. BTW, we can't see the images of the results here.

Share this comment

Link to comment

One thing about the billboard shader vertex transformation, it doesn't work to well if you change the camera zoom level (fov).


vertexcameraposition = (gl_ModelViewProjectionMatrix * mat[3]) + vec4(gl_Vertex.x,gl_Vertex.z*(buffersize.x/buffersize.y),0.0,0.0);


A narrow fov makes the billboards 'shrink', need to fix that. But yes, very helpful post, cheers Mack.

Share this comment

Link to comment

It occurs to me, throw imposters into the mix and it's a good basis 3D cloud s. For imposters you just update them if the camera has changed more than a trigger amount.

Share this comment

Link to comment

Haven't come to using particles yet, but this looks great.

Must bookmark this one so I don't forget later on.

Share this comment

Link to comment
Guest Red Ocktober


is there much, or any, performance hit?



Share this comment

Link to comment

There is if you use a lot of them. Have them on a reasonably fast recycle time and it's quire reasonable. You get better performance using your own quads instead of a TPlane.


This creates a simple two triangle square (like TPlane) and you can move and paint with your billboard material just like a TPlane and it worked out faster for me since I don't need any AABB updates from them. They are typically short duration meshes that are always visible if the parent missile is visible.


Every time I create a 'particle' I'm making a quad like this...


Self.Sprite = CreateMesh() ;
Self.Surface = CreateSurface(self.Sprite) ;

Local surf:TSurface = Self.Surface;
AddVertex(surf, Vec3(-1, 0, -1)) ;
AddVertex(surf, Vec3(1, 0, 1)) ;
AddVertex(surf, Vec3(1, 0, -1)) ;
AddVertex(surf, Vec3(-1, 0, 1)) ;

AddTriangle(surf, 0, 1, 2) ;
AddTriangle(surf, 3, 1, 0) ;

SetVertexNormal(surf, 0, Vec3(0, 0, -1)) ;
SetVertexNormal(surf, 1, Vec3(0, 0, -1)) ;
SetVertexNormal(surf, 2, Vec3(0, 0, -1)) ;
SetVertexNormal(surf, 3, Vec3(0, 0, -1)) ;

SetVertexTexCoords(surf, 0, Vec2(0, 1)) ;
SetVertexTexCoords(surf, 1, Vec2(1, 0)) ;
SetVertexTexCoords(surf, 2, Vec2(1, 1)) ;
SetVertexTexCoords(surf, 3, Vec2(1, 1)) ;

ScaleMesh(Self.Sprite, vec3(Self.Size)) ;
Self.Sprite.SetPosition(Self.Position) ;


I'm using about 100 of these particles. A small loop adds around 10-20 of of different sizes to the tail-pipe of a rocket which moves so fast, blink and you'd miss it. If all the quads shared the same surface it should be as fast as you can probably make it.


Updating is done ia pretty small loop in the manager. Particles is a linked list (TList).


Method Update()
	For Local p:TParticle = eachin self.Particles
		if p.IsDead
			p.Free() ;
			Self.Particles.Remove(p) ;
			p.Update() ;
		End If
End Method


And the update in the TParticle object checks for death and alpha fade....


Method Update()
	Local tim:Int = AppTime()
	If tim >= self.Timer Then
		IsDead = True
		if (tim >= (self.Timer - Self.LifeTime))
			Self.Sprite.SetColorf(self.Color.x, self.Color.y, self.Color.z, ((self.Timer - tim) / Self.LifeTime) * Self.Color.w) ;
		End if
End Method

Share this comment

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...