[翻译]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节解释:
接下来,您想给您的每一个粒子一个独特的方向。为此,你可以在[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代码中,纹理取样器,和顶点着色器的输出结构和像素着色器:
在3-11节,您的顶点着色器将通过二维屏幕坐标的顶点,加上纹理坐标到像素着色器。因为您需要的粒子到消失过一段时间后,您也想通过一些颜色信息从您的顶点着色到像素着色器。像往常一样,您支持的Pixel Shader将只计算每一个像素的颜色。
顶点着色器
您的顶点着色器将需要billboard的顶点,因此,您可以使用此方法,其中包含有用的球形billboarding3-11节的代码:
现在是时候了顶点着色器,这将充分利用以往的方法定义:
在这一时刻,你用当前的时间减去粒子创建的时间就是它可以存活的时间,存放在xTime变量中。然而,当工作时间范围,你总是希望与相对值在0和1之间, 0是指开始时间范围和1意味着结束的时间范围。所以,你希望用年龄来区分最大的粒子,并让这些粒子"die":
让我们首先根据其年龄调整的粒子的大小。你想每个粒子的开始大爆炸,然后变得越来越小,因为它变老了。然而,你不想要它完全衰退,所以,例如,您可以使用此代码:
然而,relAge它已成为1后继续增加 。这意味着将成负数(如你可以看到在下面的代码,这将导致图像拉伸在对面方向) 。所以,你将要饱和此值介于0和1之间 。
由于颗粒尺寸1太小在您的应用程序,只要简单按一定数量比例增加他们。使每一个粒子都是唯一的,你同样也可以用一个粒子的随机的值去乘以它的大小,所以每一个粒子都有自己的起始(结束)大小:
看看图3-23的右边部分。横轴再次代表您的粒子的年龄,而垂直轴表示粒子已从中心爆炸移动多远。你看,首先是距离的增加而线性,但经过一段时间, 距离增加有点慢,在最后的距离不会改变的很多了。这意味着,在开始的速度将保持不变,并结束时的速度,粒子速度几乎是0 。
您很有运气,这种曲线就是简单的四分之一的正弦波。对于任何给定的relAge在0和1之间,你可以找到相应的位移曲线使用此功能:
注意:虽然正弦工程完全在这种情况下,它不等于数学于现实中发生的事情相等。我已经选择正玄作为必要的简单段落,否则需要更换数学的两页代码,这将提醒您注意代码太多。正确的方法为目前置换所找到详细讨论了在二维爆炸教程,在我的网站上。
您将要乘以这个值,使颗粒移动位远离中心。 在这个例子中,一个因素的3是一个很好的值,但随时实验一下。较大的值,将造成更大的爆炸。此外,这个值乘以随机值,所以将每个粒子在一个独特的移动速度:
提示如果您想爆炸移动物体,如飞机,您只需添加一行,然后将所有的粒子的方向移动的物体。您需要通过这个方向作为额外TEXCOORD2变量内的顶点。
现在您已经三维位置的billboard中心和它的大小,您准备好通过这些值观您billboarding方法:
这是它的位置。这应该已经提供一个不错的结果,当然事情会看起来好当您混合您的粒子,因为它们距离的附近的结束。当粒子仍然是新的,你希望它是完全可见。当它到达relAge = 1 ,您希望它成为完全透明的。
为了实现这一目标,您可以使用线性衰减,但结果会更好当您使用曲线如图3-23左侧部分。只有这个时候,你希望它伸展从1改为0 , 所以没有必要再除以2 :
不要忘记通过纹理坐标的像素着色器,以便它知道在Billboard的角相当于其中纹理的角:
这是它的顶点着色;幸运的是,支持Pixel Shader是比较容易的顶点着色:
这是微不足道的技术定义:
在你的XNA代码中,不要忘记去设置所有的XNA-to-HLSL参数:
在这种情况下,您要使用的添加剂混合的颜色,使所有爆炸图像加在一起在对方的顶部。在实际上使三角形之前你可以设置绘制的状态来实现这个:
请注意,您的图形卡将使每个像素被绘制很多次,因为你是渲染很多附近爆炸图像的彼此对方的顶部(你会禁止向深度缓冲写数据) 。因此,当您的图形卡已经绘制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,第二个粒子会被绘制即使第一个粒子在它的前面(当然,在爆炸和摄象机之间没有别的对象)。
这里的代码可以帮你解决问题:
参看上面的代码。
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类中:
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 }
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,它可以表明你想把爆炸的中心位置在哪。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 }
接下来,您想给您的每一个粒子一个独特的方向。为此,你可以在[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变量中,以便您的顶点着色可以计算每个颗粒能活多久。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 };
在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节。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 }
现在是时候了顶点着色器,这将充分利用以往的方法定义:
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包含方向你想要粒子朝哪移动和一个额外随机浮点数。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 }
在这一时刻,你用当前的时间减去粒子创建的时间就是它可以存活的时间,存放在xTime变量中。然而,当工作时间范围,你总是希望与相对值在0和1之间, 0是指开始时间范围和1意味着结束的时间范围。所以,你希望用年龄来区分最大的粒子,并让这些粒子"die":
1 float age=xTime-birthTime;
2 float relAge=age/maxAge;
为你自己象这样检查材质:当粒子是新的,xTime将会被作为birthTime,relAge将会是0。当这个粒子几乎结束,这年龄将几乎等于maxAge,这个relAge变量几乎等于1。2 float relAge=age/maxAge;
让我们首先根据其年龄调整的粒子的大小。你想每个粒子的开始大爆炸,然后变得越来越小,因为它变老了。然而,你不想要它完全衰退,所以,例如,您可以使用此代码:
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;
现在您已经减少规模因为粒子变老,下一个东西来计算在三维粒子的中心位置。你知道原来的三维位置(爆炸的中心),以及粒子必须移动的方向。所有您需要知道是多远粒子已经在这个方向上。当然,这也是相应粒子的年龄。一个简单的办法是假定粒子不断移动以一定的速度,但实际上这样的速度过一会是减少的。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变量将包含当前时间以毫秒为单位,这条线每一秒将粒子拉下来一个单位。2 billboardCenter+=age*float3(0,-1,0)/1000.0f;
提示如果您想爆炸移动物体,如飞机,您只需添加一行,然后将所有的粒子的方向移动的物体。您需要通过这个方向作为额外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结构。2 float4 finalPosition4=float4(finalPosition,1);
3 float4*4 preViewProjection=mul(xView,xProjection);
4 Output.Position=mul(finalPosition4,preViewProjection);
这是它的位置。这应该已经提供一个不错的结果,当然事情会看起来好当您混合您的粒子,因为它们距离的附近的结束。当粒子仍然是新的,你希望它是完全可见。当它到达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;
象素着色器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值来符合当前的粒子的年龄。2 {
3 ExpPiexlToFrame Output=(ExpPixelToFrame)o;
4 Output.Color=tex2D(textureSampler,PSIn.TexCoord)*PSIn.Color;
5 return Output;
6 }
这是微不足道的技术定义:
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中设置技术效果参数2 {
3 pass Passo
4 {
5 VertexShader=compile vs_1_1 ExplosionVS();
6 PixelShader=compile ps_1_1 ExplosionPS();
7 }
8 }
在你的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节。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
在这种情况下,您要使用的添加剂混合的颜色,使所有爆炸图像加在一起在对方的顶部。在实际上使三角形之前你可以设置绘制的状态来实现这个:
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*destColor2 device.RenderState.SourceBlend=Blend.SourceAlpha;
3 device.RenderState.DestinationBlend=Blend.One;
请注意,您的图形卡将使每个像素被绘制很多次,因为你是渲染很多附近爆炸图像的彼此对方的顶部(你会禁止向深度缓冲写数据) 。因此,当您的图形卡已经绘制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轴缓冲写作可用,然后再开始下一帧渲染:
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();
1 device.RenderState.DepthBufferWriteEnable=true;
调用CreateExplosionVertices方法不管什么时候你想开始你的新爆炸!在这个例子里,当用户按下空格键这方法被调用:
1 if((keyState.IsKeyDown(keys.Space)))
2 CreateExplosionVertices((float)gameTime.TotalGameTime.TotalMilliseconds);
代码2 CreateExplosionVertices((float)gameTime.TotalGameTime.TotalMilliseconds);
参看上面的代码。