[翻译]XNA 3.0 Game Programming Recipes之twenty-nine

我与影子孤独终老i 提交于 2020-03-22 11:23:58
PS:自己翻译的,转载请著明出处格
                                             5-7 用一个VertexBuffer计算所有顶点的法线
问题
              当渲染你自定义的结构到3D世界中,你注意到它不能正确的发亮。
              这是因为你不能指定正常的法向矢量为结构的每一个顶点。一个法线要求用每个顶点,所以你的图形卡能测定多少光线撞击到三角形。参看第6章有更多的信息关于法线和为什么需要它们。
              计算法向量为每个顶点看起来有点复杂,因为在所有结构大多数顶点被多个三角形共享。因此,你需要一个方法,自动计算出法线为对象的所有顶点。
解决方案
              如果每一个顶点被一个三角形使用,所有您需要做的是找出三角形的法线向量(换句话说,这个向量垂直于三角形)分配这个向量到这个顶点。
              但是,对于一个结构,将所有顶点之间共享几个三角形。以至获得平稳的影响,每个顶点需要存储法线,这是所有围绕三角形的法线的平均值。
它是如何工作的
              使用此位的伪代码,您可以找到每个顶点的正确的法线:
              1。每一个顶点在你的结构中,用这个顶点找到所有三角形。
              2。计算法线向量为这些三角形。
              3。这些法线向量的平均值。
              4。保存这个平均的法线向量在顶点中。
              这些步骤是需要的,因为你总是希望法线向量保存在你的顶点被规格化(换句话说,有一个长度正好是1).
注意:你希望你的法线向量的长度正好为1,因为这个向量将会被用来用顶点和像素着色器计算照明因素。一个大的法线向量将导致一个大光照因子比小的向量,同时你希望光照因子依靠这个角度在射入光线和法线之间的,如第6章所解释的。
              你可以继续转变这些成为真实的代码,但是如果你交换1和2的顺序,你可以使它容易些:
             1。在你结构的每一个三角形,计算法线向量。
             2。添加这个向量到三角形的三个顶点的法线中。之后,为所有的三角形,做到以下几点:
             3。在你结构里的每一个顶点,规格化它的法线向量。
Calculating the Normal of a Triangle: Definition of the Cross Product
             在继续之前,你应该知道正常的发现是什么样的。很简单,它是垂直于三角形的方向。这也就是说用三角形的任何顶点,法线是相同的。因为法线是垂直于三角形平面,它同样是垂直于任何在三角形内的顶点。
             所以,你如何计算一个单独三角形的法线向量?你使用一个十字坐标系做这个,由于两个顶点的十字坐标返回向量垂直于两个顶点的平面。
             在你的三角形的情况下,你会采取三角形的两个边作为向量。使用它们的十字坐标,你将获得向量垂直于三角形,在图5-13虚线所表示。然而,这个法线的长度基于在两条边之间夹住的这个角度,最后,你需要规格化你的顶点到统一的长度。

             注意Vector3.Cross(Vec1,Vec2)不是和Vector3.Cross(Vec2,Vec1)一样。两个结果顶点将会在同一条直线上,但是它们是相反的方向。这是因为一个平面,因此先前的三角形有两个垂直的方向:一个指向这个页面和别的也指向这个页面。
The GenerateNormalsForTriangleList Method
             当定义一个大的对象时,你主要是想确定你的结构使用这些指数,因为这是唯一的方法一个顶点能被多个三角形使用,它允许光滑的光线在你的对象上(6-2节),因此,这节将根据计算两个数组包含的顶点和结构指数: 1 private VertexPositionNormalTexture[] GenerateNormalsForTriangleList(VertexPositionNormalTexture[] vertices,int[] indices)
2 {
3 }             这个方法接收顶点数组,这还不包含任何法线的数据。它将存储正确的法线的信息,每个顶点将返回数组。基于指数,它可以判断哪些顶点用于创建三角形。不过,根据你是否绘制三角形作为一个TrianleList或者TrianleStrip,indices数组的内容将会是不同的,所以用这个方法的相同的直线的两种情况下是不同的。
Calculating Normals for a Structure Rendered As a TriangleList
             如果顶点已经包含了任何关于它们法线的数据,重置它们为0: 1 for(int i=0;i<vertices.Length;i++)
2       vertices[i].Normal=new Vector3(0,0,0);             接下来,正如前伪代码,你会翻阅所有的三角形的结构和计算其法线。在一个TriangleList,每一个三角形是定义用三个连续的指数。也就是说三角形的数量在你的结构中等于indices.Length/3。
             这是for循环通过每一个三角形定义在indices数组中:  1 for(int i=0;i<indices.Length./3;i++)
 2 {
 3      Vector3 firstVec=vertices[indices[i*3+1]].Position-vertices[indices[i*3]].Position;
 4      Vector3 secondVec=vertices[indices[i*3+2]].Position-vertices[indices[i*3]].Position;
 5      Vector3 normal=Vector3.Cross(secondVec,firstVec);
 6      normal.Normalize();
 7      vertices[indices[i*3]].Normal+=normal;
 8      vertices[indices[i*3+1]].Normal+=normal;
 9      vertices[indices[i*3]+2].Normal+=normal;
10 }             每一个三角形,你定义两个顶点如图5-13所示。至于任何在点p0和p1之间的顶点,你可以用P1减去P0得到。这个实现在代码第一行,第二行计算从P0到P2之间的向量。
             下一步,你找到垂直于这些顶点的向量,用十字坐标。不要忘记规格化这个结果,这样它的长度变成了1了。
注意:根据你定义你的指数,你需要使用Vector3.Cross(secondVec,firstVec)代替;如前所述,这个将导致顶点反方向。如果你定义你的顶点用顺时针顺序(5-6节),这个代码就完成了。
              现在你知道三角形的法线了,你很容易的添加它到三角形的每个顶点的法线。当第一for循环完成,每一个顶点将会保存三角形的所有法线的总和。
              这也就是说你必须把这些大的向量的长度规格化到1: 1 for(int i=0;i<vertices.Length;i++)
2        vertices[i].Normal.Normalize();
3 return vertices;              随着所有法线保存在顶点数组中,你返回这些数组到调用代码。
Calculating Normals for a Structure Rendered As a TriangleStrip
              一个TriangleStrip,事情稍有不同,因为每个指数在indices数组中,创建一个新的三角形,基于指数和先前的两个指数: 1 for(int i=2;i<indices.Length;i++)
2 {
3     Vector3 firstVec=vertices[indices[i-1]].Position-vertices[indices[i]].Position;
4     Vector3 secondVec=vertices[indices[i-2]].Position-vertices[indices[i]].Position;
5     Vector3 norma=Vector3.Cross(firstVec,secondVec);
6     normal.Normalize();
7 }              从第3个指数,每一个指数你阻止建造一个三角形基于指数i,i-1和i-2.先前的代码查阅所有的三角形被indices数组定义,创建两个向量响应三角形的两边。
              虽然,当你定义指数为你的TriangleStrip,你自动交换缠绕顺序在每个三角形后(参看5-1节的注意)。作为一个结果,你调用firstVec和secondVec在每个三角形后改变它的边。这将会是同样的效果作为改变firstVec和secondVec在Cross方法为每下一个三角形,它将会每一次交换结果法线的方向。
              你不能做任何关于它的事,但是你可以很容易的补偿它。只要掌握一个布尔型,在每个三角形之后选择它的值。如果它的值是true,你再一次交换法线的方向:  1 bool swappedWinding=false;
 2 for(int i=2;i<indices.Length;i++)
 3 {
 4     Vector3 firstVec=vertices[indices[i-1]].Position-vertices[indices[i]].Position;
 5     Vector3 secondVec=vertices[indices[i-2]].Position-vertices[indices[i]].Position;
 6     Vector3 normal=Vector3.Cross(firstVec,secondVec);
 7     normal.Normalize();
 8     if(swappedWinding)
 9         normal*=-1;
10     vertices[indices[i]].Normal+=normal;
11     vertices[indices[i-1]].Normal+=normal;
12     vertices[indices[i-2]].Normal+=normal;
13     swappedWinding=!swappedWinding;
14 }               其余的代码是于上一节的方法相同的,所以不要忘记关于重置代码在方法的开始的时候,和在代码的最后规格化。
Making the Method Fail-Safe
               Vector3.Cross方法早些时候使用时可能会得到一个错误,如果firstVec和secondVec是相同的方向时。这种情况下的三角形可能会是一条直线,而不是一个三角形,被称为ghost triangle(参看5-8节的例子)
               在这种情况下,Vector3.Cross方法将会返回一个Vector3包含三个Not-a-Number(NaN)值。如果是这种情况,不要添加向量到任何顶点,否则它将会成为被恶化的: 1 if(!float.IsNaN(normal.X))
2 {
3     vertices[indices[i]].Normal+=normal;
4     vertices[indices[i-1]].Normal+=normal;
5     vertices[indices[i-2]].Normal+=normal;
6 }Starting from a VertexBuffer and an IndexBuffer
               先前的代码开始于两个数组包含顶点和索引。如果你已经保存了它们在Buffers在录象RAM(5-4节),你不能保存一个本地的副本,你只有得到数据的返回。这是它如何做到的: 1 int numberOfVertices=myVertexBuffer.SizeInByte/VertexPositionNormalTexture.SizeInBytes;
2 VertexPositionNormalTexture[] vertices=new VertexPositionNormalTexture[numberOfVertices];
3 myVertexBuffer.GetData(vertices);
4 int numberOfIndices=myIndexBuffer.SizeInBytes/4;
5 int[] indices=new int[numberOfIndices];
6 myIndexBuffer.GetData(indices);               你找到你顶点的数量在你的VertexBuffer,通过考虑它占据了多少字节。直到你知道一个顶点占据多少字节,你能找到多少个顶点适合这VertexBuffer.
               你按同样的方法可以找到一定数量的indices在IndexBuffer内,因为你知道一个整形占据4个字节。
注意:5-4节作为解释说明,使用GetData在一个VertexBuffer或者一个IndexBuffer肯定不被推荐。甚至,如果你已经使用了BufferUsage.WriteOnly
标志去创建一个buffer,编译器将会抛出一个错误在GetData方法中。
代码
               下面的方法增加了法线数据到顶点保存在顶点数组中,如果indices绘制三角形作为一个TriangleStrip:
 1 private VertexPositionNormalTexture[] GenerateNormalsForTrianleStrip(VertexPositionNormalTexture[] vertices,int[] indices)
 2 {
 3       for(int i=0;i<vertices.Length;i++)
 4              vertices[i].Normal=new Vector3(0,0,0)
 5       bool swappedWinding=false;
 6       for(int i=2;i<indices.Length;i++)
 7       {
 8           Vector3 firstVec=vertices[inices[i-1]].Position-vertices[indices[i]].Position;
 9           Vector3 secondVec=vertices[inices[i-2]].Position-vertices[indices[i]].Position;
10           Vector3 normal=Vector3.Cross(firsVec,secondVec);
11           normal.Normalize();
12           if(swappedWinding)
13               normal*=-1;
14           if(!float.IsNaN(normal.X))
15           {
16               vertices[indices[i]].Normal+=normal; 
17               vertices[indices[i-1]].Normal+=normal;
18               vertices[indices[i-2]].Normal+=normal;
19           }
20           swappedWinding=!swappedWinding;
21       }
22        for(int i=0;i<vertices.Length;i++)
23               vertices[i].Normal.Normalize();
24        return vertices;
25 }
26 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!