| 
 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 
                                       |