[翻译]XNA 3.0 Game Programming Recipes之thirty
PS:自己翻译的,转载请著明出处格
5-8 创建一个地形基于一个VertexBuffer和IndexBuffer
问题
基于一个2D高度的地图,你想创建一个地形和绘制它用一个有效的方式。
解决方案
首先你需要有一个高度地图,包含所有的高度数据,来自于你想定义的地图。这个高度地图会有一个确定数量的数据点在两个方面;让我们称它们为你未来地形的宽度和高度。
显然,如果你想建立一个地形数据,在此基础上,您的地形将绘制由宽*高独特的顶点,如图5-14右上所显示(请注意,计数为0的)
要完全覆盖整个网格用三角形,你需要去绘制两个三角形在网格的每四个点,如图5-14三角形用实心和虚线绘制的那样。你需要(width-1)*2的三角形为一行,(height-1)*(width-1)*2的三角形为整个地形。
如果你想确定你是否应该使用indiecs去绘制你的地形,使用5-3节中的规则。在这种情况下,(一些独特的顶点)除以(一些三角形)小于1,所以你应该一定要使用indices.所有顶点没有边界被共享不少于6个三角形!
此外,由于所有的三角形互相共享至少一条边,应该绘制整个一包三角形作为一个TriangleStrip而不是一个TriangleList.
它是如何工作的
Defining the Vertices
开始确定你的顶点。接下来的方法假定你有权使用一个heightData变量,它是一个2D数组包含地形的所有顶点的高度。如果你没有这样一个数组变量,LoadHeightData方法在这一节的结束时创建这样一个数组基于2D图象的。
使用这个循环机制,你可以立即使用这个循环计算器作为X和Z的坐标。Z的值是负值,所以地形是建筑在一个(-z)的方向上。高度值是取自heightData数组。
目前,你给所有顶点一个默认的正常方向,它很快被正确的方向所取代,使用前面章节的方法。因为你可能想要你的地形是有质感的,你需要去指定一个有效的纹理坐标。依靠你的纹理,你想要控制它的大小在地形上。这个例子用30去除 ,这表明纹理将重复,每30个顶点。如果你想将纹理延伸到地形的大面积,除以一个较大的数。
现在你已经集合了所有这些数据 ,你已经可以创建你的新的顶点和保存它到数组中。
Defining the Indices
随着你顶点的定义,你已经准备去定义如果使你的三角形被创建用定义的indices数组(参看5-3节)。你准备去定义三角形作为一个TriangleStrip,每个index表明一个新的三角形,基于这个index和前面的两个indices。
图5-15表明你如何能绘制你的地形作为一个TriangleStrip.头两个数组的indices将指向顶点0和W。下一步,这一行的每一个顶点,你添加这个顶点和相应下一行的顶点,直到你到达最下一行的结尾。在这个时候,你将会定义2*width indices,相应(2*width-2)三角形,这正是足以覆盖整个底部行的连续三角形。
虽然,你已经定义第一行。你不能使用相同的手法去绘制第二行,因为每一个index你添加定义一个新三角形基于最后的三个indices.在这一点上,最后的index你定义的点到顶点(2*W-1)。如果你再一次开始第二行,你可以开始先添加一个index到顶点W,作为表示在图5-15左部分。然而,这会定义一个三角形基于顶点W,(2*W-1),和(W-1)!这个三角形可能将跨越整个第一行的长度,你就不会喜欢这个最终结果。
你可以解决这个用定义的第二行,从右边开始。虽然,简单开始从index的结束不是一个好想法,由于两行的三角形的长的一边将会有一个不同的方向。你想要你的三角形用一个统一方向,如5-9节。
图5-16显示如何解决这个问题。你的index立即指向到顶点(2*W-1),你添加另外一个index指向这同样的顶点!这将添加一个三角形基于顶点(W-1)和两次的顶点(2*W-1),它将导致一条直线在顶点(W-1)和(2*W-1)之间。这条直线已经被最后的三角形绘制,所以这个三角形没有可见的结果,它被称为ghost triangle。接下来,你添加一个index指向到顶点(3*W-1),导致一条直线而不是一个三角形,因为它是基于两个indices指向到同样的顶点,(2*W-1)。注意,如果你定义你的第二行从右边开始,那里有两个顶点你可能会开始,只需要记住,实际上你绘制的是两个看不见的三角。
注意:你可以说不是增加第二个index指向到(2*W-1),你可能立即添加一个index到(3*W-1)。虽然,这额外index指向到顶点(2*W-1)被要求两个原因。第一,如果你还没有添加它,只有一个单一三角形可能被添加,你可能被打断缠绕交换顺序被TriangleStrips所要求。第二,这可能添加一个三角形基于(3*W-1),(2*W-1)和(W-1),这将是明显的,如果这里有个高度差异。
这是一个方法生成indices定义三角形作为一个TriangleStrip为一个三角形基于一个网格:
z变量在前面的代码表明当前的行。你创建第一行从左向右。接下来,你增加Z,指示你已经选择到下一行。第二行是创建从右到左,如图5-16所显示,Z同样被增加。该程序将通过while循环,直到所有的连贯的行被创建从左到右和所有不连贯的行从右到左创建。
这时Z达到height-1,whileloop停止,作为结果的数组被返回。
Normals, VertexBuffer, and IndexBuffer
这是所有这一节的新的代码;现在所有你要做的是生成法线的数据,发送这个数据到你的图形卡中,创建一个VertexBuffer和一个IndexBuffer,当然,绘制三角形。
添加这些代码到你的LoadContents方法中
随着你的数据上传到拟订图形卡,这是最后的时间去绘制你的地形。这是代码的第一部分,正确设置你的BasicEffect的变量(包括光源;看6-1节),所以添加它到你的Draw方法中:
随着效果建立,你已经准备去绘制三角形。下面代码绘制三角形从一个indexed TriangleStrip,5-3节作为一个解释。
代码
略,参看前面的代码!
5-8 创建一个地形基于一个VertexBuffer和IndexBuffer
问题
基于一个2D高度的地图,你想创建一个地形和绘制它用一个有效的方式。
解决方案
首先你需要有一个高度地图,包含所有的高度数据,来自于你想定义的地图。这个高度地图会有一个确定数量的数据点在两个方面;让我们称它们为你未来地形的宽度和高度。
显然,如果你想建立一个地形数据,在此基础上,您的地形将绘制由宽*高独特的顶点,如图5-14右上所显示(请注意,计数为0的)
要完全覆盖整个网格用三角形,你需要去绘制两个三角形在网格的每四个点,如图5-14三角形用实心和虚线绘制的那样。你需要(width-1)*2的三角形为一行,(height-1)*(width-1)*2的三角形为整个地形。
如果你想确定你是否应该使用indiecs去绘制你的地形,使用5-3节中的规则。在这种情况下,(一些独特的顶点)除以(一些三角形)小于1,所以你应该一定要使用indices.所有顶点没有边界被共享不少于6个三角形!
此外,由于所有的三角形互相共享至少一条边,应该绘制整个一包三角形作为一个TriangleStrip而不是一个TriangleList.
它是如何工作的
Defining the Vertices
开始确定你的顶点。接下来的方法假定你有权使用一个heightData变量,它是一个2D数组包含地形的所有顶点的高度。如果你没有这样一个数组变量,LoadHeightData方法在这一节的结束时创建这样一个数组基于2D图象的。
1 private VertexPositionNormalTexture[] CreateTerrainVertices()
2 {
3 int width=heightData.GetLength(0);
4 int height=heightData.GetLenght(1);
5 VertexPositionNormalTexure[] terrainVerticess=new VertexPositionNormalTexture[width*height];
6 int i=0;
7 for(int z=0;z<height;z++)
8 {
9 for(int x=0;x<width;x++)
10 {
11 Vector3 position=new Vector3(x,heightData[x,z],-z);
12 Vector3 normal=new Vector3(0,0,1);
13 Vector3 texCoord=new Vector2((float)x/30.0f,(float)z/30.0f);
14 terrainVectices[i++]=new VertexPositionNormalTexture(position,normal,texCoord);
15 }
16 }
17 return terrainVertices;
18 }
首先你找到你地图的一个宽度和高度,基于heightData数组的大小。接下来,你创建一个数组,它能保存所有顶点你需要的为你的地形。作为早期的讨论,你的地形要求width*height顶点。2 {
3 int width=heightData.GetLength(0);
4 int height=heightData.GetLenght(1);
5 VertexPositionNormalTexure[] terrainVerticess=new VertexPositionNormalTexture[width*height];
6 int i=0;
7 for(int z=0;z<height;z++)
8 {
9 for(int x=0;x<width;x++)
10 {
11 Vector3 position=new Vector3(x,heightData[x,z],-z);
12 Vector3 normal=new Vector3(0,0,1);
13 Vector3 texCoord=new Vector2((float)x/30.0f,(float)z/30.0f);
14 terrainVectices[i++]=new VertexPositionNormalTexture(position,normal,texCoord);
15 }
16 }
17 return terrainVertices;
18 }
使用这个循环机制,你可以立即使用这个循环计算器作为X和Z的坐标。Z的值是负值,所以地形是建筑在一个(-z)的方向上。高度值是取自heightData数组。
目前,你给所有顶点一个默认的正常方向,它很快被正确的方向所取代,使用前面章节的方法。因为你可能想要你的地形是有质感的,你需要去指定一个有效的纹理坐标。依靠你的纹理,你想要控制它的大小在地形上。这个例子用30去除 ,这表明纹理将重复,每30个顶点。如果你想将纹理延伸到地形的大面积,除以一个较大的数。
现在你已经集合了所有这些数据 ,你已经可以创建你的新的顶点和保存它到数组中。
Defining the Indices
随着你顶点的定义,你已经准备去定义如果使你的三角形被创建用定义的indices数组(参看5-3节)。你准备去定义三角形作为一个TriangleStrip,每个index表明一个新的三角形,基于这个index和前面的两个indices。
图5-15表明你如何能绘制你的地形作为一个TriangleStrip.头两个数组的indices将指向顶点0和W。下一步,这一行的每一个顶点,你添加这个顶点和相应下一行的顶点,直到你到达最下一行的结尾。在这个时候,你将会定义2*width indices,相应(2*width-2)三角形,这正是足以覆盖整个底部行的连续三角形。
虽然,你已经定义第一行。你不能使用相同的手法去绘制第二行,因为每一个index你添加定义一个新三角形基于最后的三个indices.在这一点上,最后的index你定义的点到顶点(2*W-1)。如果你再一次开始第二行,你可以开始先添加一个index到顶点W,作为表示在图5-15左部分。然而,这会定义一个三角形基于顶点W,(2*W-1),和(W-1)!这个三角形可能将跨越整个第一行的长度,你就不会喜欢这个最终结果。
你可以解决这个用定义的第二行,从右边开始。虽然,简单开始从index的结束不是一个好想法,由于两行的三角形的长的一边将会有一个不同的方向。你想要你的三角形用一个统一方向,如5-9节。
图5-16显示如何解决这个问题。你的index立即指向到顶点(2*W-1),你添加另外一个index指向这同样的顶点!这将添加一个三角形基于顶点(W-1)和两次的顶点(2*W-1),它将导致一条直线在顶点(W-1)和(2*W-1)之间。这条直线已经被最后的三角形绘制,所以这个三角形没有可见的结果,它被称为ghost triangle。接下来,你添加一个index指向到顶点(3*W-1),导致一条直线而不是一个三角形,因为它是基于两个indices指向到同样的顶点,(2*W-1)。注意,如果你定义你的第二行从右边开始,那里有两个顶点你可能会开始,只需要记住,实际上你绘制的是两个看不见的三角。
注意:你可以说不是增加第二个index指向到(2*W-1),你可能立即添加一个index到(3*W-1)。虽然,这额外index指向到顶点(2*W-1)被要求两个原因。第一,如果你还没有添加它,只有一个单一三角形可能被添加,你可能被打断缠绕交换顺序被TriangleStrips所要求。第二,这可能添加一个三角形基于(3*W-1),(2*W-1)和(W-1),这将是明显的,如果这里有个高度差异。
这是一个方法生成indices定义三角形作为一个TriangleStrip为一个三角形基于一个网格:
1 private int[] CreateTerrainIndices()
2 {
3 int width=heightData.GetLength(0);
4 int height=heightData.GetLength(1);
5 int[] terrainIndices=new int[(width)*2*(height-1)];
6 int i=0;
7 int z=0;
8 while(z<height-1)
9 {
10 for(int x=0;x<width;x++)
11 {
12 terrainIndices[i++]=x+z*width;
13 terrainIndices[i++]=x+(z+1)*width;
14 }
15 z++;
16 if(z<height-1)
17 {
18 for(int x=width-1;x>=0;x--)
19 {
20 terrainIndices[i++]=x+(z+1)*width;
21 terrainIndices[i++]=x+z*width;
22 }
23 }
24 z++;
25 }
26 return terrainIndices;
27 }
你开始创建一个数组,保存所有地形要求的顶点。如图5-16所示,每一行你需要去定义width*2三角形。例如,你有三行的顶点,但是你已经绘制只有三角形的两行。这个结果整个的width*2*(height-1) indices所要求的。2 {
3 int width=heightData.GetLength(0);
4 int height=heightData.GetLength(1);
5 int[] terrainIndices=new int[(width)*2*(height-1)];
6 int i=0;
7 int z=0;
8 while(z<height-1)
9 {
10 for(int x=0;x<width;x++)
11 {
12 terrainIndices[i++]=x+z*width;
13 terrainIndices[i++]=x+(z+1)*width;
14 }
15 z++;
16 if(z<height-1)
17 {
18 for(int x=width-1;x>=0;x--)
19 {
20 terrainIndices[i++]=x+(z+1)*width;
21 terrainIndices[i++]=x+z*width;
22 }
23 }
24 z++;
25 }
26 return terrainIndices;
27 }
z变量在前面的代码表明当前的行。你创建第一行从左向右。接下来,你增加Z,指示你已经选择到下一行。第二行是创建从右到左,如图5-16所显示,Z同样被增加。该程序将通过while循环,直到所有的连贯的行被创建从左到右和所有不连贯的行从右到左创建。
这时Z达到height-1,whileloop停止,作为结果的数组被返回。
Normals, VertexBuffer, and IndexBuffer
这是所有这一节的新的代码;现在所有你要做的是生成法线的数据,发送这个数据到你的图形卡中,创建一个VertexBuffer和一个IndexBuffer,当然,绘制三角形。
添加这些代码到你的LoadContents方法中
1 myVertexDeclaration=new VertexDeclaration(device,VertexPositionNormalTexture.VertexElements);
2 VertextPositionNormalTexture[] terrainVertices=CreateTerrainVertices();
3 int[] terrainIndices=CreateTerrainIndices();
4 terrainVertices=GenerateNormalsForTriangleStrip(terrainVertices,terrainIndices);
5 CreateBuffers(terrainVertices,terrainInices);
第一行是必要的,这样你的图形卡知道每个顶点将会被保存位置,发现,和纹理坐标数据。我已经讨论过下面两个方法;她们生成所有的顶点和indices要求去绘制地形。GenerateNormalsForTriangleStrip方法在5-7节被解释;它添加法线数据到你的地形顶点,这样你的地形能被正确射到。最终的方法发送你的数据到你的图形卡中:
2 VertextPositionNormalTexture[] terrainVertices=CreateTerrainVertices();
3 int[] terrainIndices=CreateTerrainIndices();
4 terrainVertices=GenerateNormalsForTriangleStrip(terrainVertices,terrainIndices);
5 CreateBuffers(terrainVertices,terrainInices);
1 private void CreateBuffers(VertexPositionNormalTexture[] vertices,int[] indices)
2 {
3 terrainVertexBuffer=new VertexBuffer(device,VertexPositionNormalTexture.SizeInBytes*vertices.Length,BufferUsage.WriteOnly);
4 terrainVertexBuffer.SetData(vertices);
5 terrainIndexBuffer=new IndexBuffer(device,typeof(int),indices.Length,BufferUsage.WriteOnly);
6 terrainIndexBuffer.SetData(indices);
7 }
你可以找到一个所有方法的解释,在5-3节论据将会在这使用。2 {
3 terrainVertexBuffer=new VertexBuffer(device,VertexPositionNormalTexture.SizeInBytes*vertices.Length,BufferUsage.WriteOnly);
4 terrainVertexBuffer.SetData(vertices);
5 terrainIndexBuffer=new IndexBuffer(device,typeof(int),indices.Length,BufferUsage.WriteOnly);
6 terrainIndexBuffer.SetData(indices);
7 }
随着你的数据上传到拟订图形卡,这是最后的时间去绘制你的地形。这是代码的第一部分,正确设置你的BasicEffect的变量(包括光源;看6-1节),所以添加它到你的Draw方法中:
1 int width=heightData.GetLength(0);
2 int height=heightData.GetLength(1);
3 basicEffect.World=Matrix.Identity;
4 basicEffect.View=fpsCam.ViewMatrix;
5 basicEffect.Projection=fpsCam.ProjectionMatrix;
6 basicEffect.Texture=grassTexture;
7 basicEffect.TextureEnabled=true;
8 basicEffect.EnableDefaultLighting();
9 basicEffect.DirectionalLight0.Direction=new Vector3(1,-1,1);
10 basicEffect.DirectionalLight0.Enable=true;
11 basicEffect.AmbientLightColor=new Vector3(0.3f,0.3f,0.3f);
12 basicEffect.DirectionalLight1.Enable=false;
13 basicEffect.DirectionalLight2.Enable=false;
14 basicEffect.SpecularColor=new Vector3(0,0,0);
一如往常当绘制3D场景到2D场景中,你会需要设置世界,视景,和投影矩阵(参看2-1和4-2)。接下来,你表明哪个纹理你想用来添加一些颜色到你的地形中。第二个批设立一个光源方向,如6-1节有解释。镜面的部分被关闭(参看6-4节),由于一个草地地形很难有金属材质的光泽。2 int height=heightData.GetLength(1);
3 basicEffect.World=Matrix.Identity;
4 basicEffect.View=fpsCam.ViewMatrix;
5 basicEffect.Projection=fpsCam.ProjectionMatrix;
6 basicEffect.Texture=grassTexture;
7 basicEffect.TextureEnabled=true;
8 basicEffect.EnableDefaultLighting();
9 basicEffect.DirectionalLight0.Direction=new Vector3(1,-1,1);
10 basicEffect.DirectionalLight0.Enable=true;
11 basicEffect.AmbientLightColor=new Vector3(0.3f,0.3f,0.3f);
12 basicEffect.DirectionalLight1.Enable=false;
13 basicEffect.DirectionalLight2.Enable=false;
14 basicEffect.SpecularColor=new Vector3(0,0,0);
随着效果建立,你已经准备去绘制三角形。下面代码绘制三角形从一个indexed TriangleStrip,5-3节作为一个解释。
1 basicEffect.Begin();
2 foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
3 {
4 pass.Begin();
5 device.Vertiecs[0].SetSource(terrainVertexBuffer,0,VertexPositionNormalTexture.SizeInBytes);
6 device.Indices=terrainIndexBuffer;
7 device.VertexDeclaration=myVertexDeclaration;
8 device.DrawIndexdPrimitives(PrimitiveType.TriangleStrip,0,0,width*height,0,width*2*(height-1)-2);
9 pass.End();
10 }
11 basicEffect.End();
你首先设置你的VertexBuffer和IndexBuffer作为活动的buffers在图形卡中。这VertexDeclarationx显示到GPU,哪种数据是希望的哪里可以找到,所有必要的信息在大的数据流里面。DrawIndexedPrimitives绘制你的TriangleStrip.这要求所有的width*height顶点被处理,为一个大量的width*2*(height-2)-2的三角形被绘制。为了找到最后的值,查找indices的总数在你的index数组里。由于你已经从一个TriangleStrip绘制了,绘制的顶点的总数等于这个值减去2。2 foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
3 {
4 pass.Begin();
5 device.Vertiecs[0].SetSource(terrainVertexBuffer,0,VertexPositionNormalTexture.SizeInBytes);
6 device.Indices=terrainIndexBuffer;
7 device.VertexDeclaration=myVertexDeclaration;
8 device.DrawIndexdPrimitives(PrimitiveType.TriangleStrip,0,0,width*height,0,width*2*(height-1)-2);
9 pass.End();
10 }
11 basicEffect.End();
代码
略,参看前面的代码!