Processing 2D images and textures-creating a 3D explosion effect, simple Particle System

Source: Internet
Author: User
Tags time in milliseconds
Problem

You want to create a beautiful 3D explosion effect in the 3D world.

Solution

You can create an explosion by mixing a large number of small flame images (called particles), as shown in 3-22. Note that a single particle is very dark. However, if you add a large number of particles, you will get a pretty explosive effect.

Figure 3-22 Single explosive particle

At the beginning of the explosion, all particles are at the initial position of the explosion. Because the colors of all images are superimposed together, a bright fireball is formed, you need to use additive blending to draw particles to achieve this effect.

As time passes, particles will exit from the initial position. In addition, each particle image needs to be smaller and darker (fade out) while leaving the center ).

A beautiful 3D explosion requires 50 to 100 particles. Because these images are only 2D images displayed in the 3D world and require billboard, this tutorial is based on 3-11.

You will create a shader-based Real-time particle system. In each frame, the survival time (AGE) of each particle is calculated. Its position, color, and size are calculated based on the survival time. Of course, these calculations should be carried out in the GPU so that the CPU can do more important work.

Working Principle

As mentioned above, this chapter will reuseCodeTherefore, we need to understand the principle of spherical billboarding. For each particle, you still need to define six vertices, and you only need to define them once and pass them to the video card. For each vertex, the following data must be contained for vertex shader:

    • 3D billboard center point position of the initial explosion location (used for billboarding) (vector3 = three floating point numbers)
    • Texture coordinates (used for billboarding) (vector2 = two floats)
    • Particle creation time (a floating point number)
    • Particle survival time (a floating point number)
    • Direction of particle movement (vector3 = three floating point numbers)
    • A random number used to apply different behaviors to each particle (a floating point number)

From this data, the GPU can compute everything needed based on the current time. For example, it can calculate how long the particle has survived. When you multiply the survival time by the particle direction, you can get the distance from the initial position of the particle.

You need a vertex format to store the data, using one vector3 to store the location; one vector4 to store the second, third, and fourth data; and the other vector4 to store the last two data. For details about how to create a custom vertex format, see tutorial 5-14:

 public struct vertexexplosion {public vector3 position; Public vector4 texcoord; Public vector4 additionalinfo; Public vertexexplosion (vector3 position, vector4 texcoord, vector4 additionalinfo) {This. position = position; this. texcoord = texcoord; this. additionalinfo = additionalinfo;} public static readonly vertexelement [] vertexelements = new vertexelement [] {New vertexelement (0, 0, vertexelementformat. vector3, vertexelementmethod. default, vertexelementusage. position, 0), new vertexelement (0, 12, vertexelementformat. vector4, vertexelementmethod. default, vertexelementusage. texturecoordinate, 0), new vertexelement (0, 28, vertexelementformat. vector4, vertexelementmethod. default, vertexelementusage. texturecoordinate, 1),}; public static readonly int sizeinbytes = sizeof (float) * (3 + 4 + 4);} 

This looks very similar to 3-11 in the tutorial. In addition to replacing vector2 with a vector4 as the second parameter, an additional two floating point numbers can be passed to the vertex shader. To create a random direction, you must use a randomizer. Therefore, add the following variables to the xNa class:

 
Random rand;

Perform initialization in the initialize method of the game class:

 
Rand = new random ();

Now you can create a vertex. The following method generates a vertex Based on tutorial 3-11:

Private void createexplosionvertices (float time) {int participant = 80; explosionvertices = new vertexexplosion [Participant * 6]; int I = 0; For (INT partnr = 0; partnr <participant; partnr ++) {vector3 startingpos = new vector3 (5, 0); float R1 = (float) Rand. nextdouble ()-0.5f; float r2 = (float) Rand. nextdouble ()-0.5f; float R3 = (float) Rand. nextdouble ()-0.5f; vector3 movedirection = new vector3 (R1, R2, R3); movedirection. normalize (); float r4 = (float) Rand. nextdouble (); r4 = R4/4.0f * 3.0f + 0.25f; explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 1, time, 1000 ), new vector4 (movedirection, R4); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 0, time, 1000), new vector4 (movedirection, r4); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 0, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 1, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 1, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 0, time, 1000), new vector4 (movedirection, R4 ));}}

This method needs to be called when a new explosion is initialized. It accepts the current time as the parameter. First, the number of particles used in the explosion is defined, and an array is created to save the six vertices of each particle. Then, create these vertices. For each vertex, The startingpos is first stored, that is, the center point of the explosion.

Then, you want each particle to have a different direction. This can be achieved by generating two random numbers in the [0, 1] interval, but you need to subtract 0.5 to place them in the [-0.5f, + 0.5f] interval. Create a vector3 based on this random value, and normalize this vector3 so that the random direction has the same length.

TIPS:Normalization is recommended because vectors (0.5f, 0.5f, 0.5f) are longer than vectors (0.5f, 0, 0. Therefore, when you add the position of the first vector, if you use the second vector as the direction of motion, the particle will move faster. As a result, the explosion will become likeCubeBody instead of spherical.

Then, obtain another random value to apply the effect to each particle, because the velocity and size of the particle must be adjusted by this value. A random number between 0 and 1 is required and then scaled to the [0.25f, 1.0f] range (who wants a velocity particle ?).

Finally, add the six vertices of each particle to the vertex array. Note that each of the six vertices contains the same information. Except for texture coordinates, texture coordinates are used to define the offset of each vertex's deviation from the center position in vertex shader.

HLSL

Now that you have prepared the vertex, you can write vertex and pixel shader.

First define the output structure of XNA-HLSL variables, texture samplerzer, vertex shader and pixel shader:

// XNa interface upload xview; float4x4 xprojection; float4x4 xworld; float3 xcampos; float3 xcamup; float xtime; // texture samplers texture success; sampler success = Success {texture = <strong>; magfilter = Linear; minfilter = Linear; mipfilter = Linear; addressu = clamp; addressv = clamp;}; struct expvertextopixel {float4 position: position; float2 texcoord: texcoord0; float4 color: color0 ;}; struct exppixeltoframe {float4 color: color0 ;};

Because every particle in the explosion has to apply a billboard, you need the same variable. This time, you still needProgramTo store the current time in the xtime variable, so that vertex shader can calculate the survival time of each particle.

As described in tutorial 3-11, vertex shader transfers 2D screen position and texture coordinates to pixel shader. The color information must be transmitted from vertex shader because you want to fade out the particles. As usual, pixel shader only calculates the color of each pixel.

Vertex shader

Vertex shader applies billboard to vertices, so use the following code from the sphere billboarding in 3-11 of the Tutorial:

// Technique: paibillboardvertex (float3 billboardcenter, float2 cornerid, float size) {float3 eyevector = billboardcenter-xcampos; float3 sidevector = cross (eyevector, xcamup ); sidevector = normalize (sidevector); float3 upvector = cross (sidevector, eyevector); upvector = normalize (cores); float3 finalposition = billboardcenter; finalposition + =. x-0.5f) * sidevector * size; finalposition + = (0.5f-cornerid. y) * upvector * size; return finalposition ;}

In short, you pass the center position, texture coordinates (used to distinguish the current vertex), and billboard size of the billboard to this method, which returns the 3D position of the vertex. For more information, see tutorial 3-11.

Below is the vertex shader, which uses the method just defined:

Extends explosionvs (float3 inpos: position0, float4 intexcoord: texcoord0, float4 inextra: texcoord1) {extends output = (bytes) 0; float3 startingposition = MUL (inpos, xworld ); float2 texcoords = intexcoord. XY; float birthtime = intexcoord. z; float maxage = intexcoord. w; float3 movedirection = inextra. XYZ; float random = inextra. W ;}

Vertex shader accepts one vector3 and two vector4 stored in each vertex. First, store the data content in some variables. The central billboard location is stored in vector3. You have defined that the X and Y components in the first vector4 contain texture coordinates, and the third and fourth components contain the time when the particle was created and the survival time. The second vector4 contains the moving direction of the particle and an additional random floating point number.

Now, you can subtract the current time from the birthtime to get the time when the particle already exists and store it in the xtime variable. However, when using time span, you want to use the relative value between 0 and 1 to indicate the time, 0 to indicate the start time, and 1 to indicate the end time. So divide age by maxage. maxage indicates that the particle should "die" at this time ".

 
Float age = xtime-birthtime; float relage = age/maxage;

When the particle is new, xtime is the same as birthtime, and relage is 0. When it approaches the end, age is almost equal to maxage, and relage is close to 1.

First, adjust the particle size based on its age. You want to increase the size of each particle from the beginning and then gradually decrease. However, you do not want to completely disappear, so you need to use the following code:

 
Float size = 1-relage * relage/2.0f;

It seems a little hard to understand. If you use a graph, it can help you understand the code, as shown in the figure 3-22 on the left. For the age of each particle on the horizontal axis, you can find the corresponding size on the vertical axis. For example, when relage = 0, the size is 1, and when relage = 1, the size is 0.5f.

Figure 3-23 size-relage function; displacement-relage Function

However, when relage is 1, it will continue to decrease. This means that the size will change to a negative value (as seen in the code below, this will cause the image to expand in the reverse direction ). Therefore, you must keep the value between 0 and 1.

Because the size of 1 particles is too small, simply multiply them by a number for scaling. Here is 5. To make each particle different, multiply the size by a random number:

 
Float sizer = saturate (1-relage * relage/2.0f); float size = 5.0f * random * sizer;

Now the particle size will decrease over time, and the next step is to calculate the 3D position of the particle center. You already know the initial 3D position (the center of the explosion) and the moving direction of the particle. You need to know how far the particle has moved along this direction. Of course, this distance corresponds to the particle's survival time. A simple method is to let the particle move at a constant speed, but in reality this speed will decrease.

On the Right of Figure 3-23, the horizontal axis indicates the particle's survival time, and the vertical axis indicates the distance of the particle from the center. You can see that the distance increases linearly at the beginning, but after a while, the distance increases at a lower speed and remains unchanged at the end. This means that the starting speed remains unchanged and the last value is 0.

Fortunately, this curve is only 1/4 of the simple sine function. For any given relage between 0 and 1, you can use this function to find the distance on the corresponding curve:

Float totaldisplacement = sin (relage * 6.28f/4.0f );

A complete sine curve period is from 0 to 2 * Pi = 6.28. Because you only want 1/4 cycles, You need to divide them by 4. For relage between 0 and 1, totaldisplacement has a value corresponding to the curve shown in the right figure 3-23.

Multiply this value by a factor to make the particle leave the center a little bit. In this example, this factor is 3. A larger value leads to a larger explosion. This value is multiplied by a random value so that each particle has its own speed:

 
Float totaldisplacement = sin (relage * 6.28f/4.0f) * 3.0f * random;

Once you know the distance from the particle moving in the direction of motion, you can easily obtain the current position of the particle:

 
Float3 billboardcenter = startingposition + totaldisplacement * movedirection; billboardcenter + = age * float3 (0,-1, 0)/1000.0f;

The last line of code applies a gravity to the particle. Because the xtime variable contains the current time in milliseconds, this line of code will reduce the particle by one unit per second.

TIPS:If you want to apply an explosion effect to a moving object, such as an airplane, you just need to simply add a code to let all particles move in the direction of the object's movement. You can pass this direction in an additional texcoord2 vector of the vertex.

Now with the 3D location and size of the billboard center, you are ready to pass these values to the billboarding method:

Float3 finalposition = billboardvertex (billboardcenter, texcoords, size); float4 finalposition4 = float4 (finalposition, 1); float4x4 previewprojection = MUL (xview, xprojection); output. position = MUL (finalposition4, previewprojection );

This code is from tutorial 3-11. Calculate the billboarded position. As usual, this 3D position needs to be multiplied by a 4 × 4 matrix to be converted to a 2D screen position, but before that, float3 needs to be converted to float4. You store the final 2D position in the position variable of the output structure.

The above code can provide a beautiful result, but it will be better to apply the fade-out effect when the particle approaches the end. When a particle is just generated, you want to make it completely visible. When relage = 1 is reached, you want to make it completely transparent.

To achieve this effect, you can use linear reduction, but it will be better if you use the curve in the left graph 3-23. This time, you want to range from 1 to 0, so you do not need to divide by 2:

 
Float alpha = 1-relage * relage;

Now we can define the modulated color of the particle:

 
Output. Color = float4 (0.5f, 0.5f, 0.5f, alpha );

In pixel shader, you multiply the color of each pixel by this color. This will adjust the Alpha value of the pixel to the value calculated here, so that the particle will gradually become transparent. The RGB component of the color is also softened by factor 2, otherwise you will easily see independent particles.

Don't forget to pass the texture coordinates to pixel shader so that you can know which vertex of the texture corresponding to the billboard vertex:

Output. texcoord = texcoords; return output;
Pixel shader

Fortunately, pixel shader is very simple compared to vertex shader:

 
Exppixeltoframe explosionps (effecpsin): color0 {exppixeltoframe output = (exppixeltoframe) 0; output. Color = tex2d (texturesampler, pSIN. texcoord) * pSIN. color; return output ;}

For each pixel, you obtain the corresponding color from the texture and multiply the color by the modulated color calculated by vertex shader. This modulated color reduces the brightness and sets the Alpha value for the particle's survival time.

The following is the definition of technique:

 
Technique explosion {pass pass0 {vertexshader = compile vs_1_1 explosionvs (); pixelshader = compile ps_1_1 explosionps ();}}
Set the technique parameter in xNa

Do not forget to set all xNa-to-HLSL variables in the xNa code:

Effecffect. currenttechnique = effecffect. techniques ["explosion"]; effecffect. parameters ["xworld"]. setvalue (matrix. identity); effecffect. parameters ["xprojection"]. setvalue (quatmouscam. projectionmatrix); effecffect. parameters ["xview"]. setvalue (quatmouscam. viewmatrix); effecffect. parameters ["xcampos"]. setvalue (quatmouscam. position); effecffect. parameters ["xexplosiontexture"]. setvalue (mytexture); effecffect. parameters ["xcamup"]. setvalue (quatmouscam. upvector); effecffect. parameters ["xtime"]. setvalue (float) gametime. totalgametime. totalmilliseconds );
Additive Blending (additional hybrid)

Because this technique depends on the mix, you need to set the draw status before drawing. For more information about the Alpha mix, see the tutorials 2-12 and 3-3.

In this example, you want to use additive blending to combine all colors. This can be achieved by setting the rendering status:

 
Device. renderstate. alphablendenable = true; device. renderstate. sourceblend = blend. sourcealpha; device. renderstate. destinationblend = blend. One;

The first line of code enables Alpha mixing. The color of each pixel is determined by the following rules:

Finalcolor = sourceblend * sourcecolor + destblend * destcolor

Based on the rendering status set above, this rule is changed to the following formula:

 
Finalcolor = sourcealpha * sourcecolor + 1 * destcolor

The video card will draw each pixel multiple times, because you will draw a large number of explosive images (you need to disable writing into the depth buffer ). Therefore, when the video card has drawn 79 of the 80 particles and starts to draw the last one, the following operations are performed on each pixel of the particle:

1. Find the current color of the pixels stored in the frame buffer, and multiply the three color channels by 1 (corresponding to 1 * destcolor in the preceding rule ).

2. obtain the new color of the particle calculated in pixel shader, multiply the three channels of the color by the alpha channel, the alpha channel depends on the current particle survival time (corresponding to sourcealpha * sourcecolor in the preceding rule ).

3. Add the two colors and save the results to the frame buffer.

At the particle's start time (relage = 0), sourcealpha is equal to 1, so additive blending produces a Big Bang. At the particle End Time (relage = 1), newalpha is 0, and the particle does not produce any effect.

Note:The second example of this hybrid rule can be found in tutorial 2-12.

Disable write to depth buffer

You have to consider the last aspect. When the particles that exit the camera are first drawn, all particles after it will not be drawn (this will lead to no mixing )! So when you draw a particle, You need to disable the Z-buffer test and draw the particle as the last element in the scene. But this is not a good method, because when the explosion leaves the camera far away and there is a building between the camera and the explosion, the explosion will still be drawn, as if there is no such building between them!

A better way is to draw the scenario first. Then, close the Z-buffer write and draw the particle as the last element. In this way, you can solve this problem:

    • Each pixel of each particle is compared with the current content (including depth information) in Z-buffer. If an object has already been drawn between the camera and the explosion, then the explosion particle will not pass the Z-buffer test and will not be drawn.
    • Because the particle does not change the Z-buffer, even if the second particle is drawn after the first particle (of course, when there is no other object between the camera and the explosion ).

The following code is used:

 
Device. renderstate. depthbufferwriteenable = false;

Then, draw the triangle used for explosion:

 
Effecffect. begin (); foreach (effectpass pass in effecffect. currenttechnique. passes) {pass. begin (); device. vertexdeclaration = new vertexdeclaration (device, vertexexplosion. vertexelements); device. drawuserprimitives <vertexexplosion> (primitivetype. trianglelist, explosionvertices, 0, explosionvertices. length/3); pass. end ();} else ffect. end ();

After drawing the explosion, you need to enable the Z-buffer write again:

 
Device. renderstate. depthbufferwriteenable = true;

Call the createexplosionvertices method when you want to start a new explosion! In this example, this method is called when you press the Space key:

 
If (keystate. iskeydown (Keys. Space) createexplosionvertices (float) gametime. totalgametime. totalmilliseconds );
Code

The following describes how to generate the vertex required for the explosion:

Private void createexplosionvertices (float time) {int participant = 80; explosionvertices = new vertexexplosion [Participant * 6]; int I = 0; For (INT partnr = 0; partnr <participant; partnr ++) {vector3 startingpos = new vector3 (5, 0); float R1 = (float) Rand. nextdouble ()-0.5f; float r2 = (float) Rand. nextdouble ()-0.5f; float R3 = (float) Rand. nextdouble ()-0.5f; vector3 movedirection = new vector3 (R1, R2, R3); movedirection. normalize (); float r4 = (float) Rand. nextdouble (); r4 = R4/4.0f * 3.0f + 0.25f; explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 1, time, 1000 ), new vector4 (movedirection, R4); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 0, time, 1000), new vector4 (movedirection, r4); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 0, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (1, 1, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 1, time, 1000), new vector4 (movedirection, R4 )); explosionvertices [I ++] = new vertexexplosion (startingpos, new vector4 (0, 0, time, 1000), new vector4 (movedirection, R4 ));}}

The following is the complete draw method:

Protected override void draw (gametime) {Device. clear (clearoptions. target | clearoptions. depthbuffer, color. black, 1, 0); ccross. draw (quatcam. viewmatrix, quatcam. projectionmatrix); If (explosionvertices! = NULL) {// draw billboards effecffect. currenttechnique = effecffect. techniques ["explosion"]; effecffect. parameters ["xworld"]. setvalue (matrix. identity); effecffect. parameters ["xprojection"]. setvalue (quatcam. projectionmatrix); effecffect. parameters ["xview"]. setvalue (quatcam. viewmatrix); effecffect. parameters ["xcampos"]. setvalue (quatcam. position); effecffect. parameters ["xexplosiontexture"]. setvalue (mytexture); effecffect. parameters ["xcamup"]. setvalue (quatcam. upvector); effecffect. parameters ["xtime"]. setvalue (float) gametime. totalgametime. totalmilliseconds); device. renderstate. alphablendenable = true; device. renderstate. sourceblend = blend. sourcealpha; device. renderstate. destinationblend = blend. one; device. renderstate. depthbufferwriteenable = false; effecffect. begin (); foreach (effectpass pass in effecffect. currenttechnique. passes) {pass. begin (); device. vertexdeclaration = new vertexdeclaration (device, vertexexplosion. vertexelements); device. drawuserprimitives <vertexexplosion> (primitivetype. trianglelist, explosionvertices, 0, explosionvertices. length/3); pass. end ();} else ffect. end (); device. renderstate. depthbufferwriteenable = true;} base. draw (gametime );}

The following is the vertex shader code:

Extends explosionvs (float3 inpos: position0, float4 intexcoord: texcoord0, float4 inextra: texcoord1) {extends output = (bytes) 0; float3 startingposition = MUL (inpos, xworld ); float2 texcoords = intexcoord. XY; float birthtime = intexcoord. z; float maxage = intexcoord. w; float3 movedirection = inextra. XYZ; float random = inextra. w; float age = xtime-birthtime; float relage = age/maxage; float sizer = saturate (1-relage * relage/2.0f); float size = 5.0f * random * sizer; float placement = sin (relage * 6.28f/4.0f) * 3.0f * random; float3 billboardcenter = startingposition + totaldisplacement * movedirection; billboardcenter + = age * float3 (0,-)/1000.0f; float3 finalposition = billboardvertex (billboardcenter, texcoords, size); float4 finalposition4 = float4 (finalposition, 1); float4x4 previewprojection = MUL (xview, xprojection); output. position = MUL (finalposition4, previewprojection); float alpha = 1-relage * relage; output. color = float4 (0.5f, 0.5f, 0.5f, alpha); output. texcoord = texcoords; return output ;}

Vertex shader uses the billboardvertex method defined earlier. At the end of the tutorial, you can see the simple pixel shader and technique definitions.

Note:On the xNa official website, point Sprite is not as flexible as billboard, but it has the advantage that only one vertex can be used to draw a particle, in billboard, there are six required, which can reduce the amount of data sent to the video card.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.