Chapter 3 more and more happy particles of rain smoke magic.
We will start with 3D effects in this chapter and look at most of the useful special effects technologies that many programmers consider to learn how to make particle systems. Programmers use particle systems to manage diverse results. Particle systems can create excellent weather effects, such as Yu or Xue, as well as magic effects, smoke, fire, fragment, rapids, and even more. In fact, you can say that the particle system is based on all 3D effects. That's why we need to learn about it. We leave the 3D chapter empty.
A cool particle system is used to create all the different effects by adjusting the variables in the system. This means that the same code will drive your rain, snow effect and magic effect or smoke. You can even reuse the code of these particle systems across several games or modified versions. You can have your artist save it as a particle editor. Therefore, they can quickly generate and kill effects without writing code.
Those are just a few of the reasons why particle systems rock. I can list more, but you can already play games and feel the magic of the particle system. You may even have read other books about how to create particle systems.
I plan to spend two chapters on particle systems. In this chapter, we will talk about basic particle systems-what they are and how they are created. In the next chapter, we will spend some time creating the particle system editor. If you already know how to use them and write an editor, you may want to jump to the next chapter to learn how to simplify them, create realistic effects, and flexible particle systems, use a simple scripting language.
This is going to be a very long roller coaster ride, so keep yourself in. Now your car has moved from here. You felt the door was below and started to transport the hill.
What is a particle system?
In actual life, particles are small slices. For example, if you break a brick with a hammer, you get the particles of the brick. A raindrops, snowflake, sprayed blood, and burning debris can also be considered particles. Figure 18 is a particle. In the video game programming field, we have mentioned that a particle is a two-dimensional space bound by texture. This quads is very small, and programmers usually render them using alpha mixing, so they are partly transparent. Some programmers, using DirectX DOC) also refer to the use of vertices because they are essentially 2D genie (images) on special points.
The model of each particle allows you to create complex effects. For example, you can create a blizzard to use a group of particles. Every frame, you cycle them and adjust the position of each particle. Adjust the X position so that you have created a very convincing blizzard effect because you understand the physical model of the real phenomenon. You have a large particle that simulates the natural power of each particle and binds it with a texture. Then you can make the snowflake effect.
All particle systems have their core:
Particle array update function rendering Function
Particle array: All particle systems have a particle array. In C ++, the use of the particle class (cparticle is a very common name). Raindrops and snowflakes are examples of this class. A few use waterfall or spring water to use water drops, or simulate smoke particles in the smoke column, or even simulate spell particles with energy emitted during magic emission.
UPDATE function: In the particle array, update function calls cyclically to execute dual processing. A typical function moves the position of a particle. Calculated using physical variables, such as neutral, wind direction, and particle width. The update function may be confusing to set particle attributes (for example, color, opacity, like smoke rising, it is clearer.
Rendering function: This function carefully sets the rendering state, such as converting the Alpha mixture) to output particles to the screen. Loop playback in the system and make appropriate graphical calls (settexture and drawprimitive need to get these particles to the correct position.
What about all particle processes?
Typically, in the C ++ particle system, you can have a cparticlesystem class that carefully controls all cparticles. The updated rendering function is set to public, and the cparticle array is set to private or protected so that cparticlesystem can modify it only. The following code is a summary.
Now, of course, we will execute update and rendering from the hard part. Others will be described in the remaining chapter.
Is it complicated to make a particle system?
When designing a particle system, the first question you asked: I want to use it flexibly? The particle system can be reused to render those particles. Generally, update Code removes code from physical computing. These physical calculations can be simple or complex.
A simple particle system is easy to understand. You usually update it more beautifully. In the same time, a simple particle system may be what you want, or your game is flexible enough.
For example, we say that each simple particle system is executed to design snow. Your update function loops every particle in a large number and uses gravity to let it fall. No problem. It is easy to understand, but not flexible.
You want to add the fountain effect to the snow grain subsystem. Now you have to choose: execute a new particle system-meaning redo all the management work (annoying !) Or, expand a function in the snow system so you can deal with the spring water. This may mean removing the gravity code and replacing it with the variable you set.
You are willing to make more things complicated for the particle system to process. I thought it would be much simpler at the beginning, but you go to the last chapter, and at the end you will execute very complex operations-the processing of the system is large in size.
Particle attribute Core
We started our friend, Mr. particle. The core idea is that particles are born in a 3D world. System functions move around them and they have a short and happy life. They even died for many reasons. Therefore, we correctly define the lifetime of our particles. Lifetime
The lifetime of most particle systems executing particles is measured in seconds. Update the function to maintain the update time-if the lifetime is exceeded, the particle will die. As a choice, some particles also die if they hit the wall or other game objects (for example, the snow and water often disappears after the impact.
So we need the lifetime of the particle, and we need a 3D vector to control the position of the particle, but what else do we need? Here all the attributes of particles are listed. We will add more from simple to complex:
Location survival speed color size gravity texture injection speed
But wait! Our system does not need all attributes. Note: to update a function, you must create a new particle. When the UPDATE function creates a new particle, it assigns an attribute color to each particle for its lifetime. Therefore, our system requires variables to tell the updated function how to select values. One way is to determine the range of random values for system updates. If we set the maximum and minimum attributes of the latest particle, it cannot pick up new random values. This means we will need the following attributes:
Maximum minimum lifetime, maximum minimum speed, maximum color, and maximum minimum size. Now we need the attributes of the particle system. We also need to determine possible additional attributes. Add more attributes and move them to the Section.
Core of particle Writing System
We started to get enough concepts. At this point, you can look at the simple ch18p1_simpleparticle program and then use the following code.
Cparticle
Let's begin the definition of the basic particle system. We use cparticle to depict a particle.
Class cparticle {public: cparticle () {m_fsize = 1.0f; m_flifetime = 1.0f; m_fage = 0.0f;} virtual ~ Cparticle () {} float m_fsize; float m_flifetime; float m_fage; d3dxcolor m_color; d3dxcolor m_colorstep; d3dxvector3 m_vpos; d3dxvector3 m_vdir; inline bool Update (float callback ){}}
Tips d3dxcolor and d3dxvector3 are the help classes provided by d3dx, which encapsulate colors and vectors. They are convenient to use the operators with heavy loads. For example, you can use ++ to overlay them.
A good object-oriented design (OOD) says you can expose member variables and you will add functions. For example, if you take a public member with m_fsize, I will set it to the m_fsize Protection Type and use the getsize and setsize functions to allow users to change particle attributes.
For readability, I did not select an affiliated function. You may use it in a real particle system.
As you can see, cparticle is not very complicated. It has a series of public variables, the attributes of the particles-set the default value through the constructor, And the inline function executes the update. Don't worry about updating functions-you will know how it works later. Now let's look at cparticle's mother cparticleemitter.
Cparticleemitter
This class contains our particle array, using particle attributes (gravity, texture, etc)
Class cparticleemitter
{Public: cparticleemitter (); Virtual ~ Cparticleemitter (); Virtual void Update (float felapsedtime, float ftimedelta); Virtual hresult render (); Virtual hresult identifier (jsonpdev, const char * strtexturefilename); Virtual void identifier (); PRIVATE: d3dxvector3 blocks; optional m_vpos; float blocks; d3dxcolor m_color1; d3dxcolor m_color2; float m_fminsize; float m_fmaxsize; optional values; int m_ivbsize; optional m_pd3ddevice; lpdirect3dvertexbuffer8 m_vbparticipant; lpdirect3dtexture8 m_texparticle; crecyclgarray M_participant ;}
The most important method is update and rendering.
Restoredeviceobjects and invalidatedeviceobjects are used to initialize and release resources and use the named DirectX particle framework. Every member of our class is defined as a virtual function. Note that the current member function is not a system attribute.
Tip: I deleted the access functions. I didn't use the access functions in cparticle. Ambassador I decided to use them here. We often use the client code cparticleemitter. It may very rare that when the client changes, it will directly change the attributes. Most of the time, only the contact class will be cparticleemitter. However, the client will be exposed to cparticleemitter attributes frequently, such as not always. This means that no function is accessed. It is a meaningful accident that if I change the attributes of cparticleemitter, I have to modify the additional code file (not good). So, I spent a small amount of time adding the access function.
Cparticleemitter sprays the direct3d Device on m_pd3ddevice, uses m_vbparticipant and m_texparticle to continuously track vertex buffer rendering particles (we will see every second), and uses textures on the particles.
I save it to prevent confusion to the end. Time tells us a magic little question hidden behind what you read: What are particles on the m_particle Earth? In the previous Code, m_participant is crecylingarray. crecylingarray is a new class. See the following.
Crecylingarray is constructed based on templates. If you do not know the C ++ template, you can refer to the appendix below. Now your brain is going to focus on the template. Let's draw a picture of crecylingarray.
Crecyclic garray works like STD: vector, but there are some key differences: it does not use dynamic memory allocation. You can set a maximum value. The Customer Code, crecyclgarray is like a vector-you can add an object to it and delete it. In the background, however, crecyclgarray does not use dynamic memory-it uses some bool signs to tell the allocation of static members, each possible value. When you use crecyclgarray, the array gets the next free object for you. When you delete the object, the crecylingarray flag is deleted by you. There is an actual memory allocation or storage unit allocation; instead, crecylingarray re-uses the memory to create and destroy. So you can ask. Is new and delete used? Two things:
Memory Allocation is slow,
However, heap fragmentation can guide the performance (the system can drag away and cause worse memory errors. Not a joke!
So there is enough reason to use crecylingarray. It gives us the best way-performance allocation of free objects (like Standard Dynamic Allocation) without the hassle of speed or heap allocation. Whoohoo!
Using a reconfiguring array uses the reconfiguring Array
Now you know why I created crecylingarray. I hope you like it. Then you know how to use it. I will show you how it works internally.
Course 1: How to Create a recycling array.
Crecylingarray has two template parameters. Array object type, the maximum array size (in that case, the maximum object array will be processed .) For example, the following code creates a recycling array containing 50 cmyclass objects.
Crecyclic garray M_myarray;
Therefore, it is generally good. Next lesson: how to create and destroy objects. Create an object and call the new method. I don't like to overload the new operator, so I chose to replace it with clear methods. You will retrieve the new pointer in your object and think about the following code:
Warning
Set the maximum value carefully. If you allocate 50 objects, try to allocate more objects, and the crecyclic garray will throw an exception. I like structured exception handling in C ++.
Cmyclass * newclass = m_myarray.new ();
Delete an object and call the delete-delete method. In the particle array, it is actually a pointer:
Warning: crecyclic garray does not call the new constructor. It uses a new empty object to retrieve the band, so make sure that your operation is working = reload an object and you will use crecylingarray.
M_myarray.delete (5 );
M_myarray.delete (newclass );
Keep in mind that the delete function doesn't null the pointer you pass to it.
Table 18.1 demonstrates other uses, but it is robust and uses the crecyclgarray method.
Methods of the crecyclgarray class
Getnumfreeelements ()
Getnumusedelements ()
Gettotalelements ()
Getat (INDEX)
Isalive (INDEX)
Deleteall ()
How does crecyclic garray work?
Now, let's take a look at crecyclic garray.
In fact, it is a very simple system. Internally, crecyclgarray has a booleans equivalent object Value of the maximum size. It really means that the object is allocated, and it is assumed to be released.
Let's look at the new method, a complex array:
Tarrayelement * New () {If (getnumfreeelements () <1) Throw (STD: out_of_range (* crecyclgarray: New: Too extends objects! "); Int I = m_ilastnew; For (INT q = 0; q = Inumelements) I = 0 ;}m_aelements [I] = m_newtempalte; m_iusedelements ++; m_balive [I] = true; m_ilastnew = I; return (& m_aelements [I]);}
The new method starts from the last time and looks for false identifiers. After finding a false identifier, it is set to true. The object count increases and the object is set to be equal to an empty object (see Figure 18.5 ).
Every time a constructor is called, The New dependency is equivalent to the clear object state.
Other methods are simple, so I don't want to talk about them here.
Only warn the delete overload pointer object to use an index-some pointers arithmetic going on there to determine the index of the object. I tried to make this pointer math as obvious as possible, the ugly thing to convert pointer to int, etc. If you look at this code, I encourage you to debug and skip-everything will become clearer.
Introduction
Unlike clear dogs or brush your teeth, however, it can easily manage a large number of objects in your video game-you will not worry about the memory allocation time release time for its switching speed. You can avoid random array out of bounds-because all the memory is allocated up front. If you need to support 5000 bullets, but the system does not run more memory, you will know when you create your array, not at some random point inside your game. I am using crecyclgarray in many places in the game-I hope you will feel its benefits.
Set Particle System
There is enough information about crecyclgarray. Let's go back to the task at hand-a simple particle system.
The first step is to set up direct3d Resources in the particle system. In this case, it means placing some code in the particle system's initialization function to create a texture and our particle's vertex buffer.
Neither task is anything worth losing sleep over. Let's look at all the glorious initialization functions:
Hresult identifier: substring (lpdirect3ddevice8 pdev, const char * strtexturefilename) {hresult hr; m_pd3ddevice = pdev; identifier (); If (failed (hR = identifier (m_pd3ddevice, strtexturefilename, & m_texparticle) {return (HR);} If (failed (hR = m_pd3ddevice-> createvertexbuffer (m_ivbsize * sizeof (vertex_), expires | average, d3dfvf_particle, d3dpool_default, & m_vbparticipant) Return (HR); Return s_ OK ;}
First, we saved the direct3d device we will use. Secondly, we cleared the particle array and cleared it. Simply call d3dxcreatetexturefromfile to load a texture (using cparticlesystem, the texture file is directed to restoredeviceobjects), and then call createvertexbuffer to create our vertex buffer.
Some different things create vertex buffering, however. Start with d3dusage_dynamic and d3dusage_points. Tells direct3d how to use vertex buffering.
The d3dusage_dynamic identifier tells direct3d that there will be dynamic and frequent changes to the d3dusage_points identifier in the vertex buffer. Do not tell us that the data element is describing the vertex (called in direct3d) the vertex genie.
Tip: Yes, there are a lot of things hitting you, but there are actually meaningful things embedded in DirectX. Particle System really are that common.
Vertex Genie and new vertex format
We use other different things to create the vertex format. We have a vertex structure called vertex_xyz_diffuse_tex2 location, color, and texture coordinates:
Typedef struct {d3dxvector3 position; d3dcolor color; float tu, TV;} vertex_xyz_diffuse_tex1;
Vertex structures work many things, but we need new structures for particle systems, including different information:
Typedef struct {d3dxvector3 position; float pointsize; d3dcolor color;} vertex_particle;
As you can see, the new vertex structure, vertex_particle, is very similar to vertex_xyz_diffuse_tex1. There are two major differences: we have added texture coordinates and point sizes.
Removing texture coordinates is simple. Call d3drs_pointspriteenable in rendering status. When the rendering status is true, direct3d automatically draws the entire texture at each point. In other words, when d3drs_pointspriteenable is set to true, our vertex texture structure does not depict a triangle point-it depicts the center of the triangle. Direct3d completed not what we involved-it may be extending the particle position in 2D, texture coordinates (0.0, 0.0), (1.0f, 0.0), (1.0, 1.0) and (0.0, 1.0) four vertices in one dimension. Or the 3D card supports dot genie on the hardware, direct3d may just pass along the information it gets, along with a note that says, "Hi mr. graphics card, these particle centers, draw them and use these entire textures if they have quad
Therefore, we no longer have to maintain texture coordinates in the vertex structure. We can rest assureed that direct3d knows enough to map our entire texture onto the particle, and to put the center point of that Partcle at the positoin we specify. Surprised, isn't it?
Okay, so what is the vertex size? Essentially, pointsize tells direct3d about the dot size our dot genie; in other words, large point particles will be placed in camera space units. The particle size is 2.0 twice the size of the dot. It requires a bit of size in the vertex structure; however, putting one in there gives you the freedom to make your particles differrent sizes.
Our new vertex structure. Remember, you can arbitrarily change the vertex structure-normally, we will use these structures to render our vertices, but then we will use vertex_xyz_diffuse_tex1 (our "normal" structure) to no longer render the ry.
To solve the problem of rendering mysterious particles, we create vertex buffering. Now we know how to initialize the system and how to render it.
Rendering Particles
If you do not know better, you may write your system in two ways:
Call drawprimitive once to give each particle
One call to drawprimitive gives all branch particles-in this step, you can lock the vertex buffer and then cyclically activate each particle. You unlock and then call drawprimitive to render the object of the particle system at a time. In this case, you need a large vertex buffer-a particle that puts down all of your points in the system at a time.
It is not ideal after generation. This situation is what I call a "Goldilocks situation" you 've got one bowl of porridge that's way too hot, and one that's way too cold. what you need is a bowl that's just right ".
Option 1 is not good, because you waste some time calling drawprimitive-you will potentially call thousands of times per frame. Not good. If you select 2, you can have less processing because your graphics card works with your CPU at the same time. This situation wocould be class to hiring an artist to work on your game, and then telling him he cocould't start on the artwork untill the code was 100% complete.
The "just right" situation is to hand small chunks of particles to the graphics card. in this way, there is no idle-the graphics card begins processing the first chunk of particles while the CPU moves on and begins to prepare the vertex buffer for the next rendering.
Next question: involves how big the chunks shocould be. There may be the best exact value somewhere-I agree to look for it through exercises. I used code 10% and found the current frame speed. If it is optimized or not.
Therefore, to get the best performance, our rendering code must set the rendering status like a ballet dance, lock the vertex buffer, pumping in a bunch of particle system coordinates, unlock the buffer, and call drawprimitive, repeat this process.
Set rendering status
Now we are ready to solve the entity rendering function. The I'm going to walk you ghrough the render method is in ch18pl_simpleparticle s.cpp. The rendering code is replaced by the big chunk, and the rendering code is a large segment. I wrote a lot of comments, so it is easy to understand.
This is the first paragraph
Hresult cparticleemitter: render () {hresult hr; m_pd3ddevice-> setrenderstate (expiration, true); m_pd3ddeivce-> setrenderstate (expiration, true); m_pd3ddevice-> setrenderstate (expiration, ftodw (0.00f); m_pd3ddevice-> setrenderstate (expiration, ftodw (0.00f); m_pd3ddevice-> setrenderstate (expiration, ftodw (0.00f); m_pd3ddevice-> setrenderstate (expiration, ftodw (1.00f ));
Yes, it is exciting to set some rendering statuses here. Not to mention the ftodw function-it is used to convert a parameter from float to DWORD. For the morbidly curous. Here the ftodw looks like:
Inline DWORD ftodw (float F) (return * (DWORD *) & F );}
}
That's it-nothing more than a way to wrap an uugly cast into an innocent-looking function. Now table 18.2 explains all rendering states.
Set texture and activate texture
After we set the rendering status, we set a small number of other rendering statuses that will be more familiar.
M_pd3ddevice-> setstreamsource (0, m_vbparticipant, sizeof (vertex_particle ));
M_pd3ddevice-> setvertexshader (d3dfvf_particle );
M_pd3ddevice-> settexture (0, m_texparticle );
These three functions call our texture buffering and activation functions. Note that we specify d3dfvf_particle for all calls:
# Define d3dfvf_particle (d3dfvf_xyz | d3dfvf_diffuse | d3dfvf_psize)
We need to set the format of different vertex structures known by direct3d, which has a location, a divergent color, and a vertex size. Therefore, the logo d3dfvf_xyz and d3dfvf_diffuse d3dfvf_psize are marked. If it is not clear, see Chapter 1.
Lock vertex buffer
Now let's process the following tasks. Understand what locks our vertex buffer:
Vertex_particle * pvertices;
DWORD dwnumparticipant lestorender = 0;
If (failed (hR = m_vbparticipant-> lock (0, m_ivbsize * sizeof (vertex_particle), (byte **) & pvertices, d3dlock_discard) return hr;
Here, a member variable m_ivbsize indicates the number of particle vertices that we work at the same time. Then, I said, there are 10% particles, so the system said, 2000 particles, the system processed the first 200, handed over to the graphics card, and then processed the next 200.
Place particles in vertex Buffering
It's interesting now. Then we lock the vertex buffer, and we need to start filling. We need to determine the loop.
For (INT q = 0; q Position = part. m_vpos; pvertices-> pointsize = 1.0f; pvertices-> color = (DWORD) part. m_color; pvertices ++ ;}}
Each particle is processed cyclically to determine whether it is activated. If so, we need to render it, so we need its position, color, and vertex buffer size information (pvertices ). we then increase the vertex pointer, so we move the next structure in the vertex buffer.
Note: We have not closed the loop.
Push the vertex to the graphics card
Now process the next part
If (++ dwnumparticipant lestorender = m_ivbsize) {m_vbparticipant-> unlock (); If (failed (hR = m_pd3ddevice-> drawprimitive (success, 0, dwnumparticipant lestorender) return hr; if (failed (hR = m_vbparticipant-> lock (0, m_ivbsize * sizeof (vertex_particle), (byte **) & pvertices, d3dlock_discard) return hr; dwnumparticipant lestorender = 0 ;}}
Then we increase the pointer and we need to determine that the vertex buffer is still in the room (for example, the next vertex). If there is no problem in the room, we will continue to move forward to the next particle. However, if there isn' t room, we need a little bit of work.
It is difficult to preceding section of code at the beginning. Here, we reduce the dwnumparticle storender counter and then compare the size of the vertex buffer. If count is equal to size, this means that the buffer is filled up. Now time to push that chunk of vertex data out to the 3D card via call to drawprimitive.
As you can see from the code inside the if block, once we determine that the vertex buffer is full, we unlock it and depict it (d3dpt_pointlist ID, telling direct3d to press the point data, do not use the Triangle List and band. After the painting is completed, we lock the buffer and specify the d3dlock_discard flag (now we send the chunk vertex data to the card, so we don't need anything, so we discard it ).
Clear: push out the stragglers
The last code before we finish
M_vbparticipant-> unlock ()
If (dwnumparticipant lestorender () {If (failed (hR = m_pd3ddevice-> drawprimitive (cost, 0, dwnumparticipant lestorender) return hr;} m_pd3ddevice-> setrenderstate (expiration, false );
M_pd3ddevice-> setrenderstate (d3drs_pointscaleenable, false );