Point
Sprites for Particle Effects
Author
:
Jack Hoxley
Written : 26th January 2001
Contact : [Web]
[Email]
Download : Graph_11.Zip
[316 Kb]
Contents
of this lesson:
1. Introduction
2. Setting up point sprites
3. A simple particle engine
1.
Introduction
Welcome
to lesson 11, or part 4 of the advanced geometry section - the last part. You
should by this point in time be perfectly capable of using all the important
features of geometry in DirectX8; but I wanted to cover this interesting new
feature called point sprites.
Point
sprites have been designed to make generating particle systems and special effects
much easier than they used to be. Several new features in DirectX8 have been
included just for future games - so they can look better and better; this is
one of them. Particle effects, good ones at least, have often been quite hard
to do - purely on the basis that you need to use up 3-4 vertices for each particle,
and have several thousand particles before it looked good (depending on various
things of course).
Unfortunately,
point sprites being a new feature dont have much support yet - the enumeration
program back in lesson 02 will tell you if they're supported or not (but we'll
cover that again in a minute); despite this you can still get them to work with
most hardware - just not fully featured though.
2.
Setting up point sprites
The
actual particle itself is going to be one vertex, nothing more than that. Through
various render states we can set them up so that they use textures and are of
different sizes. First we need to sort out the actual vertex type:
Private Type PARTICLEVERTEX
v As D3DVECTOR
Color As Long
tu As Single
tv As Single
End Type
Const FVF_PARTICLEVERTEX = (D3DFVF_XYZ Or D3DFVF_DIFFUSE Or D3DFVF_TEX1)
Const nParticles As Long = 750
Dim PrtVertList(0 To nParticles - 1) As PARTICLEVERTEX
|
|
|
|
Not
too complicated really, the only new thing here is the use of a D3DVECTOR instead
of explicitly having an X, Y and Z property, Direct3D expects 3 singles in a
row that tell it the coordinates, this can either be 3 different variables X,
Y and Z or it can be a D3DVECTOR - which is 3 singles under one type definition.
Next
we need to set up the render states that allow us to use point sprites:
'//Create the texture, note that the texture format includes an alpha channel, and that we are
' specifying a colour key...
Set ParticleTex = D3DX.CreateTextureFromFileEx(D3DDevice, App.Path & "\particle.bmp", 32, 32, _
D3DX_DEFAULT, 0, D3DFMT_A1R5G5B5, D3DPOOL_DEFAULT, _
D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, &HFFFF00FF, _
ByVal 0, ByVal 0)
'These next two calls define how we make colours transparent, for a specific colour
'leave them as they are
D3DDevice.SetRenderState D3DRS_SRCBLEND, D3DBLEND_SRCALPHA
D3DDevice.SetRenderState D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA
'Enable the usage of transparencies
D3DDevice.SetRenderState D3DRS_ALPHABLENDENABLE, 1
'Enable point sprite rendering
D3DDevice.SetRenderState D3DRS_POINTSPRITE_ENABLE, 1 '//Enable point sprite rendering
'Allow Direct3D to size the points
D3DDevice.SetRenderState D3DRS_POINTSCALE_ENABLE, 1 '//Allow Direct3D to set/alter the size of the Psprites
'Set the size
D3DDevice.SetRenderState D3DRS_POINTSIZE, FtoDW(ParticleSize)
'// The above code uses this function to pass a floating point number (0.01 for example)
'as a long integer (1 for example)
Function FtoDW(f As Single) As Long
Dim buf As D3DXBuffer
Dim l As Long
Set buf = D3DX.CreateBuffer(4)
D3DX.BufferSetData buf, 0, 4, 1, f
D3DX.BufferGetData buf, 0, 4, 1, l
FtoDW = l
End Function
|
|
|
|
After
all these render states have been successfully set any vertex we render as a
point list will become a point sprite, simple as that. If you've noticed, the
SetRenderState call only allows data of type LONG to be passed; this is a problem
if we want to pass 0.08 for example, visual basic will chop off the .08 part
and pass 0 as the value, but using the clever code from the SDK we can pack
a decimal number into an integer value.
Whilst
this section isn't dealing with setting up the actual vertices (see the next
section) we need to know how to render point sprites. Whilst it may well seem
easy, I had several problems getting the transparencies to work properly (all
solved thanks to MetalWarrior), the important parts being that we need to disable
the depth buffer before the calls - otherwise you get some odd artifacts appearing.
Here's an excerpt from the example code for rendering the point sprites:
D3DDevice.SetRenderState
D3DRS_ALPHABLENDENABLE,
1
D3DDevice.SetRenderState
D3DRS_ZWRITEENABLE,
0 '//Stops Particles screwing things up :)
D3DDevice.SetTexture
0,
ParticleTex
D3DDevice.DrawPrimitiveUP
D3DPT_POINTLIST,
nParticles,
PrtVertList(I),
Len(PrtVertList(0))
D3DDevice.SetRenderState
D3DRS_ZWRITEENABLE,
1 |
|
|
|
First
we enable alpha blending (so that the transparent parts of the textures appear
correctly) then we disable writing to the z-buffer. The next part is normal
rendering, we set the texture then render all the vertices in our array with
one call; note that they are rendered as a POINTLIST - this is important. Finally
we re-enable writing to the z-buffer, otherwise any other geometry drawn will
overwrite what we just rendered...
Because
the pointsprites are rendered using alpha blending it is CRUCIAL that you draw
them in the correct order. You may have noticed that you can render geometry
in any order, and as long as the depth buffer is present it'll all appear correctly,
this is not the case here. The transparent areas are blended with whatever is
currently underneath it, if that's just a black screen (because they're first
rendered) then you'll get a black area around them, and then when you draw the
sky later on you'll still be left with a black square - as the transparent areas
are not updated. To sort this out make sure you draw all objects from back to
front, ie, objects furthest away are drawn first, closest objects are drawn
last; with any point sprites somewhere in the middle.
3.
A simple particle engine
As
a quick taster - here's a screenshot from the sample program for you to look
at, it's this particle engine that I'm about to explain to you:
It
looks like a bit of a mess here, but that's just the way I decided to set it
up, the engine will use a set of constants that controls the shape, speed and
density of the particle stream, the one above is setup like an explosion, but
you can easily set it to other types. The motion blur on the white particles
is an optical illusion, and isn't actually designed to work like this - wait
for the alpha blending article for more information on this...
A
particle engine isn't actually very complicated at all. We only need to design
the maths for one particle, then add a little random variation and apply it
to 100's of particles in order to get the effect seen in the picture. There
are only 3 things that we really need to keep track of as well - position, velocity
and colour, you could count lifetime as well - but I dont. Other particle engines
can get extremely complicated, using extensive mathematical and physics algorithms,
but for this example we'll keep it simple - should you want to be more adventurous
you can modify this base.
The
first thing we need to do is design some data storage for each particle, we'll
keep the vertex data seperately so as not to confuse Direct3D (or slow it down).
Private Enum PARTICLE_STATUS
Alive = 0
Dead = 1
End Enum
Private Type PARTICLE
X As Single 'World Space Coordinates
Y As Single
Z As Single
vX As Single 'Speed and Direction
vY As Single
vZ As Single
StartColor As D3DCOLORVALUE
EndColor As D3DCOLORVALUE
CurrentColor As D3DCOLORVALUE
LifeTime As Long 'How long Mr. Particle Exists
Created As Long 'When this particle was created...
Status As PARTICLE_STATUS 'Does he even exist?
End Type
Dim PrtData(0 To nParticles - 1) As PARTICLE
|
|
|
|
The
first part is an enumeration for keeping track of whether the particle is dead
or alive (when it's dead we recreate it at the source), the next part is the
bulk part of the data required:
X, Y, Z = The particles current position
vX, vY, vZ = The velocity that the particle is travelling at, respective
to each axis
StartColor = The colour of the particle at the source
EndColor = The colour of the particle as it's life runs out
CurrentColor = What the current colour is, this will be an interpolated
value between the start and end colours
Lifetime = How long the particle should live for, in milliseconds. It
is possible that the particle dies earlier than this - if it collides with anything
for example
Created = In order to know when the particle should die we need to know
when it was created.
Status = If this becomes 1 (Dead) then we need to recreate the particle
back at the source
Now
that we have a way of storing information about the particle we need to initialise
the particles; this is where we need to setup the variation. We'll set each
particle going in the same general direction, but slightly different each time
- this is why we get the nice effect of it mushrooming out over time.
Private Function InitParticles() As Boolean
On Error GoTo BailOut:
Dim I As Integer
For I = 0 To nParticles - 1
PrtData(I).Status = Alive
PrtData(I).LifeTime = 10000 + ((Rnd * 5000) - 2500)
PrtData(I).Created = GetTickCount
PrtData(I).X = 0
PrtData(I).Y = -0.5
PrtData(I).Z = 0
PrtData(I).vX = (Rnd * XVariation) - (XVariation / 2)
PrtData(I).vY = (Rnd * YVariation) - (YVariation / 3)
PrtData(I).vZ = (Rnd * ZVariation) - (ZVariation / 2)
Randomize
PrtData(I).StartColor = CreateColorVal(1, 0.7, 0.7, 1)
PrtData(I).EndColor = CreateColorVal(0, 0.7, 0.7, 1)
PrtData(I).CurrentColor = PrtData(I).StartColor
Next I
'//Setup the particle vertices...
Call GenerateVertexDataFromParticles
InitParticles = True
Exit Function
BailOut:
Debug.Print "Could not initialise particles", Err.Number, Err.Description
InitParticles = False
End Function
|
|
|
|
All
we're doing here is going through each particle and setting all it's members
to similiar, but not identical, values. This code will be mirrored again later
on, when we need to recreate particles. You'll also notice that there's a call
to the function "GenerateVertexDataFromParticles", this function copies
the current position and colour to the relevent vertex; this is because we cant
pass this structure to Direct3D, we need to copy it into a valid vertex structure
first...
The
next part is to work out how we are going to update the particles. As all Direct3D
applications tend to be on a loop (the ones we've done are) we just need to
work out how to update the particles smoothly on every loop that the program
makes. In order to do this we're going to use time based animations, which were
covered in the animation lesson previously - so I wont go into any great detail...
Private
Sub
UpdateParticles()
'//0. Any variables required
Dim I
As
Long
'//1. Loop through all particles
For I
= 0 To
nParticles
- 1
If
PrtData(I).Status
=
Alive
Then
'//Update the positions
PrtData(I).X
=
PrtData(I).X
+ ((PrtData(I).vX
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
PrtData(I).Y
=
PrtData(I).Y
+ ((PrtData(I).vY
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
PrtData(I).Z
=
PrtData(I).Z
+ ((PrtData(I).vZ
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
'//Update the velocities
PrtData(I).vX
=
PrtData(I).vX
+ ((XWind
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
PrtData(I).vY
=
PrtData(I).vY
+
((Gravity
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
PrtData(I).vZ
=
PrtData(I).vZ
+ ((ZWind
/ 500)
* (GetTickCount
- _
LastUpdatedParticles))
'//Update The color values
D3DXColorLerp
PrtData(I).CurrentColor,
PrtData(I).StartColor,
PrtData(I).EndColor,
_
(GetTickCount
-
PrtData(I).Created)
/
PrtData(I).LifeTime
'//Check if the particle has gone below ground level...
If
PrtData(I).Y
<
-1
Then
PrtData(I).Status
= Dead
'//Check if it's lifetime has expired
If
GetTickCount
-
PrtData(I).Created
>=
PrtData(I).LifeTime
Then
PrtData(I).Status
= Dead
Else
'//We need to recreate our particle...
PrtData(I).Status
=
Alive
PrtData(I).LifeTime
=
10000
+ ((Rnd
*
5000)
-
2500)
PrtData(I).Created
=
GetTickCount
PrtData(I).X
= 0
PrtData(I).Y
= -0.5
PrtData(I).Z
= 0
PrtData(I).vX
= (Rnd
*
XVariation)
- (XVariation
/ 2)
PrtData(I).vY
= (Rnd
*
YVariation)
- (YVariation
/ 3)
PrtData(I).vZ
= (Rnd
*
ZVariation)
- (ZVariation
/ 2)
Randomize
PrtData(I).StartColor
=
CreateColorVal(1,
0.7,
0.7,
1)
PrtData(I).EndColor
=
CreateColorVal(0,
1, 1,
0.1)
PrtData(I).CurrentColor
=
PrtData(I).StartColor
End If
Next I
LastUpdatedParticles
=
GetTickCount
'//2. Update the raw vertex data
Call
GenerateVertexDataFromParticles
End
Sub |
|
|
|
The
above code is all fairly explanatory, but the general idea is that we go through
each property and update it, finally checking if the particle is dead or not
- if it is dead then on the next loop it'll be recreated. As you may have noticed
this part of the code is identical to the code covered just before this...
If
we now put a call to this function on every loop that the application makes
the particles will act like a proper particle system, combined with the rendering
method described in the previous section you'll be presented with your own working
particle system.
The
only thing not covered is the constants that we've used - a set of values that
we can change to alter the way the particle system operates. You can be as imaginative
as you want in altering these, or adding new ones - but for a basic particle
system these will work fine:
Const ParticleSize As Single = 0.03
Const Gravity As Single = -0.05
Const XWind As Single = 0
Const ZWind As Single = 0
Const XVariation As Single = 0.5
Const YVariation As Single = 0.85
Const ZVariation As Single = 0.5
|
|
|
|
Be
careful when altering these, any big changes could result in absolute chaos....
Only put small amounts of wind on, anything more than 0.1 will tend to pull
the particles away from the source very very quickly - and look more like a
hurricane.
I
strongly suggest that you download the source code for this example, all the
major aspects have been covered, but to keep things short I've left out some
of the obvious things... The source code can be downloaded from the top of the
page you can also send any comments, questions or complaints to the email address
at the top of the page as well... Next - Lesson 12:
Texture Blending for special effects
|