zoukankan      html  css  js  c++  java
  • [翻译]XNA 3.0 Game Programming Recipes之nineteen

    PS:自己翻译的,转载请著明出处
                                              3-12创建一个三维爆炸效应/简单粒子系统
    问题
        你想在你的3D世界里创建一个漂亮的爆炸效果。
    解决方案
        您可以创建一个爆炸效应相加融合了很多小火球图像(称为粒子)超过对方。你可以看到这样一个火球粒子在图3-22。请注意,一个单一的粒子是非常模糊的。但是,如果您添加了许多粒子彼此紧挨着对方,你会得到一个非常令人信服的爆炸。

        爆炸开始时发生,所有这些粒子是在初始的地方爆炸, 将产生一个明亮的火球,因为它是所有的粒子颜色的图像相加。您将当绘制这些粒子你会得到添加混合使用。
        随着时间的推移,颗粒应离开它的初始地方。此外,您还希望每个粒子图像获得较小的和失去的色彩(消失) ,而他们正在远离中心。
        一个很棒的爆炸效果需要50到100个粒子。因为这些图象是简单的2D图象显示在3D世界里,每一个图象需要成为billboarded,所以这一节建立在3-11节的结果上的。
        您将建立一个真正的着色器为基础的粒子系统。每帧,同期的粒子计算。其位置,颜色和大小根据目前粒子的年龄计算。当然,你希望所有这些计算都在GPU上发生,所以你的CPU仍然保持对重要的计算保留空间。
    它是如何工作的
        如前所述,本章将重用3-11的代码,所以在球形billboarding基础下被推荐。对于每一个粒子,你会再次确定6 顶点,你会想确定只有一次这样他们就可以被转移到显卡和留不变。对于每一个顶点,你需要以下数据到GPU的顶点着色器里的变量:
         1。billboard的初始3D的中心的位置在爆炸的开始
         2。纹理坐标(Vector2=two floats)
         3。粒子被创建的时间(one float)
         4。你希望粒子存活多久(one float)
         5。你希望粒子移动的方向(Vector3=three floats)
         6。一个随机的值,这将用来使每个粒子的行为独特(one float)
         从这些数据,GPU能计算你所知道的任何事,基于当前的时间。例如,它能计算粒子能存活多长时间。当您乘这个年龄段的方向颗粒,你可以找到多远的粒子它离开原来的位置,这是爆炸的中心。
        你需要一个顶点格式,能够为每个顶点储存所有这些数据。您将使用一个Vector3存储位置,一个Vector4储存第二,第三和第四个项目;和另一Vector4存储最后两个项目。创建一个自定义顶点格式,可存储这些数据,如5-13节解释:
     1 public struct VertexExplosion
     2 {
     3     public Vector3 Position;
     4     public Vector4 TexCoord;
     5     public Vector4 AdditionalInfo;
     6     public VertexExplpsion(Vector3 Position,Vector4 TexCoord,Vector4 AdditionalInfo)
     7     {
     8          this.Position=Position;
     9          this.TexCoord=TexCoord;
    10          this.AdditiionalInfo=AdditionalInfo;
    11     }
    12     public static readonly VertexElement[] VertexEements=new VertexElement[]
    13     {
    14         new VertexElement(0,0,VertexElementFormat.Vector3,VertexElementMenthod.Default,VertexElementUsage.Position,0),
    15         new VertexElement(0,12,VertexElementFormat.Vector4,VertexElementMenthod.Default,VertexElementUsage.TextureCoordinate,0),
    16         new VertexElement(0,28,VertexElementFormat.Vector4,VertexElementMenthod.Default,VertexElementUsage.TextureCoordinate,1),
    17     };
    18     public static readonly int SizeInByte=sizeof(float)*(3+4+4);
    19 }
        这看起来很像的顶点格式定义在3-11节,但它存储一个Vector4而不是Vector2作为第二个参数,从而使两个浮点以传到您的顶点着色器为每一个顶点。创建自由的方向,你需要一个随机性,添加这些变量到你的XNA类中:
    1 Random rand;
        你需要初始化这个,如在你的游戏类中的Initialize方法:
    1 rand=new Random();
        现在,所有设置,以便您可以开始创建您的顶点。这种方法生成的顶点基于3-11节:
     1 private void CreateExplosionVertices(float time)
     2 {
     3      int particles=80;
     4      explosionVertices=new VertexExplosion[particles*6];
     5      int i=0;
     6      for(int partnr=0;partnr<particles;partnr++)
     7      {
     8          Vector3 startingPos=new Vector3(5,0,0);
     9          float r1=(float)rand.NextDouble()-0.5f;
    10          float r2=(float)rand.NextDouble()-0.5f;
    11          float r3=(float)rand.NextDouble()-0.5f
    12          Vector3 moveDirection=new Vector3(r1,r2,r3);
    13          moveDirection.Normalize();
    14          float r4=(float)rand.NextDouble();
    15          r4=r4/4.0f*3.0f+0.25f;
    16          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(1,1,time,1000),new Vector4(moveDirection,r4));
    17          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(0,0,time,1000),new Vector4(moveDirection,r4));
    18          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(1,0,time,1000),new Vector4(moveDirection,r4));
    19          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(1,1,time,1000),new Vector4(moveDirection,r4));
    20          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(0,1,time,1000),new Vector4(moveDirection,r4));
    21          explosionVertices[i++]=new VertexExplositon(startingPos,new Vector4(0,0,time,1000),new Vector4(moveDirection,r4));
    22      }
    23 }
       当你要建立一个新的爆炸这个方法随时被调用,它将获得当前时间从调用这个方法。首先,你要确定有多少粒子在爆炸中,创建一个数组它能保存每个粒子的六个顶点。下一步,来创建这些顶点。每一个粒子,首先保存startingPos,它可以表明你想把爆炸的中心位置在哪。
       接下来,您想给您的每一个粒子一个独特的方向。为此,你可以在[0,1]这个区间产生一个自由的值,然后再减去0.5f,这样他们在[-0.5f,+0.5]的区域移动。基于这些值建立一个Vector3,规格化Vector3去创建平等长度随机方向。
    提示 这是正常化的建议,因为(0.5f,0.5f,0.5f)向量长于(0.5f,0,0)向量。所以,如果你的位置与增量的第一向量,这粒子会快得多,如果您使用的是第二个向量作为方向。结果,爆炸将蔓延像立方体而不是像一个球体。
       下一步,你会发现另一种随机值,将用于补充一些独特性到每个粒子,由于粒子的速度和规模将被此值调整。随机值要求在0到1之间,它将在[0.25f,1.0f]区域取值(谁会希望粒子的速度为0?)
        最后,您添加每个粒子的6个顶点到顶点数组中。请注意,每个六个顶点保存相同的信息,除了纹理坐标,这将是所使用的billboarding代码在您的顶点着色到确定偏移量在彼此顶点到billboard的中心之间。
    HLSL
        现在您已经准备好您的顶点,是时候开始编码您的顶点和像素着色器。同样地往常一样,你开始的变量由XNA传递到您的HLSL代码中,纹理取样器,和顶点着色器的输出结构和像素着色器:
     1 //------XNA interface-----
     2 float4*4 xView;
     3 float4*4 xProjection;
     4 float4*4 xWorld;
     5 float3 xCamPos;
     6 float3 xCamUp;
     7 float xTime;
     8 //---------Texture Samplers-------
     9 Texture xExplosionTexture;
    10 sampler textureSampler=sampler_state{texture=<xExplosionTexture>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AddressU=CLAMP;AddressV=CLAMP;}
    11 struct ExpVertexToPixel
    12 {
    13     float4 Position:POSITION;
    14     float2 TexCoord:TEXCOORD0;
    15     float4 Color:COLOR0;
    16 };
    17 struct ExPixelToFrame
    18 {
    19    float4 Color:COLOR0;
    20 };
        因为您想要billboard每个粒子的爆炸,您需要将相同的变量在这一节的billboarding里 。这一次,你还需要您的XNA程序存储当前时间在xTime变量中,以便您的顶点着色可以计算每个颗粒能活多久。
        在3-11节,您的顶点着色器将通过二维屏幕坐标的顶点,加上纹理坐标到像素着色器。因为您需要的粒子到消失过一段时间后,您也想通过一些颜色信息从您的顶点着色到像素着色器。像往常一样,您支持的Pixel Shader将只计算每一个像素的颜色。
    顶点着色器
        您的顶点着色器将需要billboard的顶点,因此,您可以使用此方法,其中包含有用的球形billboarding3-11节的代码:
     1 //--------Technique:Explosition-----------
     2 float3 BillboardVertex(float3 billboardCenter,float2 cornerID,float size)
     3 {
     4     float3 eyeVector=billboardCenter-xCamPos;
     5     float3 sideVector=cross(eyeVector,xCamUp);
     6     sideVector=normalize(sideVector);
     7     float3 upVector=cross(sideVector,eyeVector);
     8     upVector=normalize(upVector);
     9     float3 finalPosition=billboardCenter;
    10     finalPosition+=(cornerID.x-0.5f)*sideVector*size;
    11     finalPosition+=(0.5f-cornerID.y)*upVector*size;
    12     return finalPosition;
    13 }
        概括地说,你传递billboard的中心到这个方法,加上纹理坐标(需要确定当前顶点)和您要多大的billboard,以是。该方法返回billboarded三维位置的顶点。欲了解更多信息,见3-11节。
        现在是时候了顶点着色器,这将充分利用以往的方法定义:
     1 ExpVertexToPixel ExplosionVS(float3 inPos:POSITIONo,float4 inTexCoord:TEXCOORDo,float4 inExtra:TEXCOORD1)
     2 {
     3     ExpVectexToPixel Output=(ExpVertexToPixel)o;
     4     float3 startingPosition=mul(inPos,xWorld);
     5     float2 texCoords=inTexCoord.xy;
     6     float birthTime=inTexCoord.z;
     7     float maxAge=inTexCoord.w;
     8     float3 moveDirection=inExtra.xyz;
     9     float random=inExtra.w;
    10 }
        这顶点着色收到Vector3和两个Vector4S您储存的每个顶点。 首先,数据内容存储在一些更有意义的变量。billboard的中心位置存在Vector3。您所定义的X和Y第一Vector4的组成部分包含纹理协调,第三和第四部分存储粒子的创建时间和他能被看见多久。最后Vector4包含方向你想要粒子朝哪移动和一个额外随机浮点数。
        在这一时刻,你用当前的时间减去粒子创建的时间就是它可以存活的时间,存放在xTime变量中。然而,当工作时间范围,你总是希望与相对值在0和1之间, 0是指开始时间范围和1意味着结束的时间范围。所以,你希望用年龄来区分最大的粒子,并让这些粒子"die":
    1 float age=xTime-birthTime;
    2 float relAge=age/maxAge;
         为你自己象这样检查材质:当粒子是新的,xTime将会被作为birthTime,relAge将会是0。当这个粒子几乎结束,这年龄将几乎等于maxAge,这个relAge变量几乎等于1。
         让我们首先根据其年龄调整的粒子的大小。你想每个粒子的开始大爆炸,然后变得越来越小,因为它变老了。然而,你不想要它完全衰退,所以,例如,您可以使用此代码:
    1 float size=1-relAge*relAge/2.0f;
         这看起来有点吓人,但它更容易理解,如果你想显示它,正如在图3-23左边部分。每一个粒子的relAge在水平轴上,你可以在垂直的轴上找到相应的大小。列如,在relAge=0,大小将是1,relAge=1时,大小将会是0.5f.

          然而,relAge它已成为1后继续增加 。这意味着将成负数(如你可以看到在下面的代码,这将导致图像拉伸在对面方向) 。所以,你将要饱和此值介于0和1之间 。
         由于颗粒尺寸1太小在您的应用程序,只要简单按一定数量比例增加他们。使每一个粒子都是唯一的,你同样也可以用一个粒子的随机的值去乘以它的大小,所以每一个粒子都有自己的起始(结束)大小:
    1 float sizer=saturate(1-relAge*relAge/2.0f);
    2 float size=5.0f*random*sizer;
        现在您已经减少规模因为粒子变老,下一个东西来计算在三维粒子的中心位置。你知道原来的三维位置(爆炸的中心),以及粒子必须移动的方向。所有您需要知道是多远粒子已经在这个方向上。当然,这也是相应粒子的年龄。一个简单的办法是假定粒子不断移动以一定的速度,但实际上这样的速度过一会是减少的。
        看看图3-23的右边部分。横轴再次代表您的粒子的年龄,而垂直轴表示粒子已从中心爆炸移动多远。你看,首先是距离的增加而线性,但经过一段时间, 距离增加有点慢,在最后的距离不会改变的很多了。这意味着,在开始的速度将保持不变,并结束时的速度,粒子速度几乎是0 。
        您很有运气,这种曲线就是简单的四分之一的正弦波。对于任何给定的relAge在0和1之间,你可以找到相应的位移曲线使用此功能:
    1 float totalDisplacement=sin(relAge*6.28f/4.0f);
         整个正弦波期间从0到2 *圆周率= 6.28 。既然你想只有四分之一这期间,你除以4 。现在relAge从0到1 , totalDisplacement将有一个值相应的曲线图在图3-23的右边部分。
        注意:虽然正弦工程完全在这种情况下,它不等于数学于现实中发生的事情相等。我已经选择正玄作为必要的简单段落,否则需要更换数学的两页代码,这将提醒您注意代码太多。正确的方法为目前置换所找到详细讨论了在二维爆炸教程,在我的网站上。
        您将要乘以这个值,使颗粒移动位远离中心。 在这个例子中,一个因素的3是一个很好的值,但随时实验一下。较大的值,将造成更大的爆炸。此外,这个值乘以随机值,所以将每个粒子在一个独特的移动速度:
    1 float totalDisplacement=sin(relAge*6.28f/4.0f)*3.0f*random;   
        一旦你知道有多少粒子在给定的时间已经在其方向,你可以很容易地找到它目前三维位置:
    1 float3 billboardCenter=startingPosition+totalDisplacement*moveDirection;
    2 billboardCenter+=age*float3(0,-1,0)/1000.0f;
        最后一行添加了一些爆炸引力。由于xTime变量将包含当前时间以毫秒为单位,这条线每一秒将粒子拉下来一个单位。
      提示如果您想爆炸移动物体,如飞机,您只需添加一行,然后将所有的粒子的方向移动的物体。您需要通过这个方向作为额外TEXCOORD2变量内的顶点。
        现在您已经三维位置的billboard中心和它的大小,您准备好通过这些值观您billboarding方法:
    1 float3 finalPosition=BillboardVertex(billboardCenter,texCoords,size);
    2 float4 finalPosition4=float4(finalPosition,1);
    3 float4*4 preViewProjection=mul(xView,xProjection);
    4 Output.Position=mul(finalPosition4,preViewProjection);
        此代码来自3-11节。首先计算你的billboarded位置。同样地一如既往,这个三维位置需要转化为二维屏幕空间乘以它由4*4 矩阵,但在此之前可以做到这一点,在float3需要转换为float4。您存储的最后二维屏幕坐标的强制性Position领域的Output结构。
        这是它的位置。这应该已经提供一个不错的结果,当然事情会看起来好当您混合您的粒子,因为它们距离的附近的结束。当粒子仍然是新的,你希望它是完全可见。当它到达relAge = 1 ,您希望它成为完全透明的。
        为了实现这一目标,您可以使用线性衰减,但结果会更好当您使用曲线如图3-23左侧部分。只有这个时候,你希望它伸展从1改为0 , 所以没有必要再除以2 :
    1 float alpha=1-relAge*relAge;
        所以,现在你可以定义颗粒调制颜色:
    1 Output.Color=float4(0.5f,0.5f,0.5f,alpha);
        在您的像素着色器,你会用这个颜色乘以粒子每个像素的颜色。这将调整其Alpha值为您的值计算,所以粒子将随着时间变得更加透明。颜色的RGB部分同样因子2 ;否则,你会很容易看到不同的粒子。
        不要忘记通过纹理坐标的像素着色器,以便它知道在Billboard的角相当于其中纹理的角:
    1 Output.TexCoord=texCoords;
    2 return Output;
    象素着色器
        这是它的顶点着色;幸运的是,支持Pixel Shader是比较容易的顶点着色:
    1 ExpPixelToFrame EXplosionPS(ExpVertexToPiexl PSIn):COLORo
    2 {
    3     ExpPiexlToFrame Output=(ExpPixelToFrame)o;
    4     Output.Color=tex2D(textureSampler,PSIn.TexCoord)*PSIn.Color;
    5     return Output;
    6 }
        每个像素的,你从这纹理里找到相应的颜色,在您的顶点着色器里你用已经计算的调制颜色乘这个颜色。这调制颜色降低亮度和设置Alpha值来符合当前的粒子的年龄。
       这是微不足道的技术定义:
    1 technique Explosion
    2 {
    3     pass Passo
    4     {
    5         VertexShader=compile vs_1_1 ExplosionVS();
    6         PixelShader=compile ps_1_1 ExplosionPS();
    7     }
    8 }
    在XNA中设置技术效果参数
       在你的XNA代码中,不要忘记去设置所有的XNA-to-HLSL参数:
    1 expEffect.CurrentTechnique=expEffect.Techniques["Explosion"];
    2 expEffect.Parameters["xWorld"].SetValue(Matrix.Identity);
    3 expEffect.Parameters["xProjection"].SetValue(quatMousCam.ProjectionMatrix);
    4 expEffect.Parameters["xView"].SetValue(quatMousCam.ViewMatrix);
    5 expEffect.Parameters["xCamPos"].SetValue(quatMousCam.Position);
    6 expEffect.Parameters["xExplosionTexture"].SetValue(myTexture);
    7 expEffect.Parameters["xCamUp"].SetValue(quatMousCam.UpVector);
    8 expEffect.Parameters["xTime"].SetValue((float)gameTime.TotalGameTime.TotalMilliseconds);
    9 Additive Blending
         由于这种技术在很大程度上依赖混合,你需要设置渲染之间的正确状态。你可以阅读更多关于alpha混合的在2-13和3-3节。
         在这种情况下,您要使用的添加剂混合的颜色,使所有爆炸图像加在一起在对方的顶部。在实际上使三角形之前你可以设置绘制的状态来实现这个:
    1 device.RenderState.AlphaBlendEnable=true;
    2 device.RenderState.SourceBlend=Blend.SourceAlpha;
    3 device.RenderState.DestinationBlend=Blend.One;
         第一行代码使alpha混合器可以使用。每一个象素被绘制,颜色将会定义这些规则:finalColor=sourceBlend*sourceColor+destBlend*destColor和先前的绘制状态,成为下面这样:finalColor=sourceAlpha*sourceColor+1*destColor
        请注意,您的图形卡将使每个像素被绘制很多次,因为你是渲染很多附近爆炸图像的彼此对方的顶部(你会禁止向深度缓冲写数据) 。因此,当您的图形卡已经绘制80个粒子中的79个,现在它必须去绘制最后的粒子,它为每个像素会做到这一点因为它必须去绘制:
        1。在祯缓冲中找到象素存储的当前的颜色,并用1乘其三颜色通道(在早期的规则里是1*destColor)。
        2。获得这个颜色它是被象素着色器为最后的粒子新计算出的颜色,并用alpha通道去乘以其他三个颜色通道,这取决于当前的粒子的年龄(在早期的规则中是sourceAlpha*sourceColor)。
        3。总结这两个颜色,并保存所产生的颜色的帧缓冲(最终规则) 。
       在粒子的开始(relAge=0),sourceAlpha将会等于1,所以你添加混合器将导致很亮的爆炸效果,在粒子的结束(relAge=1),newAlpha将会是0,粒子在屏幕上将没有效果。
    注意:在混合规则的第二个例子,参见2-13节
    Disabling Writing to the Depth Buffer
         你仍然需要思考最后一个方面。最接近于摄象机的粒子最先被绘制,在它背后的所有粒子不会被绘制(这将不会被混合)!因此,当您绘制您的粒子,你可以关闭的Z轴缓冲去试验的每个像素,使您的粒子作为屏幕上的最后元素。然而,这是不是一个很好的主意,因为当爆炸在离摄象机很远处发生时,在爆炸和摄象机之间有一个建筑物,爆炸将被绘制好象它在建筑物之前一样!
        一个更好的解决办法将是使你的场景首先正常绘制,然后关闭Z轴缓冲的写功能,使粒子作为最后元素绘制到场景中。这样,您解决这两个问题:
        1。每个粒子的每个象素被当前z-buffer中的内容一遍遍检查(它包含有你当前场景的深度信息)。在这种情况下,有一个对象已经绘制在爆炸和摄象机之间,爆炸的象素不会被传递到z-buffer测试中也不会被绘制。
        2。直到粒子不改变z-buffer,第二个粒子会被绘制即使第一个粒子在它的前面(当然,在爆炸和摄象机之间没有别的对象)。
       这里的代码可以帮你解决问题:
    1 device.RenderState.DephtBufferWriteEnable=false;
       其次,绘制保存有爆炸粒子的三角:
    1 expEffect.Begin();
    2 foreach(EffectPass pass in expEffect.CurrentTechnique.Passes)
    3 {
    4      pass.Begin();
    5      device.VertexDeclaration=new VertexDeclaration(device,VertexExplosion.VertexElements);
    6      device.DrawUserPrimitives<VertexExplosion>(PrimitiveType.TriangleList,explosionVertices,0,explosionVertices.Length/3);
    7      pass.End();
    8 }
    9 expEffect.End();
        在您完成渲染的爆炸,请务必使Z轴缓冲写作可用,然后再开始下一帧渲染:
    1 device.RenderState.DepthBufferWriteEnable=true;
         调用CreateExplosionVertices方法不管什么时候你想开始你的新爆炸!在这个例子里,当用户按下空格键这方法被调用:
    1 if((keyState.IsKeyDown(keys.Space)))
    2     CreateExplosionVertices((float)gameTime.TotalGameTime.TotalMilliseconds);
    代码
       参看上面的代码。
  • 相关阅读:
    微信小程序如何调用API实现数据请求-wx.request()
    微信小程序如何调用API实现数据请求-wx.request()
    详解Android数据存储技术
    详解Android数据存储技术
    详解Android数据存储技术
    带你走进CSS定位详解
    带你走进CSS定位详解
    bootstrap教程,SQL
    带你走进CSS定位详解
    jQuery基础与JavaScript与CSS交互-第五章
  • 原文地址:https://www.cnblogs.com/315358525/p/1532874.html
Copyright © 2011-2022 走看看