[翻译]XNA 3.0 Game Programming Recipes之eighteen

PS:自己翻译的,转载请著明出处
                   3-11 让您的场景更令人印象深刻的 Billboarding :绘制二维图像的三维世界 因此,他们总是面相机
问题
    寻找令人印象深刻的,一个三维世界需要包含大量的对象,尤其是在处理户外场景。例如,您将需要几百人,如果不是数以千计的树木,否则您的森林不看真实的。然而,正是出于对这一问题,您将会使绘制联合100 树木的3D模型,因为这将使你的程序变的很慢。
    您也可以小批量使用billboarding,例如,绘制激光束或子弹。一般的做法是使用粒子引擎(见3-12节)
解决方案
    您可以解决这个问题,将二维图像替换三维物体。然而,当摄像头的位置在二维图像的旁边时,viewer当然会通知它只是一个简单的2D 图象,因为你可以看到在图3-16的左侧部分,在3D世界里有五个二维图像中的位置。

    为了解决这个问题,对每幅图像,您需要定义两个三角形,在三维空间,将显示的图像,您将要旋转,使这些三角形的图象面对摄像机。 这是显示在图3-16的右边,在相同的五个二维图像也被旋转所以他们面对着照相机。如果将包含图像的树木和它们的边界将是透明的不是黑色的,这已经是一个不错的结果,而左边的图像不会。
    XNA框架包含计算出每副图象的两个三角形的6个角顶点的旋转后的位置的功能,通过Matrix.CreateBillboard 方法,但是你可以得到速度显著的提高是靠用顶点着色器计算执行的,这节的第2节会详细解释。
它是如何工作的
    作为一个森林或粒子引擎的接口,您要确定唯一的位置在三维世界里,你想你的二维图像的中心位置和在3D世界里的2D图象的大小。所以,你想保存一张包含3D位置和大小的列表为每一张2D图象:
1 List<Vector4> billboardList=new List<Vector4>();
2 Texture2D myTexture;
3 VertexPositionTexture[] billboardVertices;
    您看到的billboard将存储作为Vector4:三维位置中心的三个浮点数和一个额外的浮点数保存billboard的大小。
    对于每个billboarded二维图像,您将需要计算三角形的6个角顶点,将在三维空间中保持这个图象。这些顶点将储存在最后一行的变量上。myTexture变量存有的纹理(或纹理s)显卡将使用样品着色器。
提示 3-4节有说明,最快的方法,使来自多个纹理是通过储存在一个大纹理文件,以便显卡在工作的时并不需要转化纹理。
    在LoadContent方法中加载纹理到变量中,3-1节中有介绍:
1 myTexture=content.Load<Texture2D>("billboardtexture");
    下一步,你将要添加一个简单的方法,使您可以方便地添加到您的billboard到你的场景:
1 private void AddBillboards()
2 {
3      billboardList.Add(new Vector4(-20,-10,0,10));
4      billboardList.Add(new Vector4(0,0,0,5));
5      billboardList.Add(new Vector4(20,10,0,10));
6 }
   现在,这种方法增加了只添加3个billboard到屏幕。作为简要说明的第四个组成部分,你想要的大小,将中等的billboard的4倍小于其他两个,因为它的长度是2倍小。不要忘记从Initialize方法中调用这个方法:
1 AddBillboards();
      接下来,您需要计算两个三角形的6个角顶点的位置它们将显示在三维场景的图象。 Billboarding有两种主要形式:球形billboarding和圆柱billboarding 。球形billboarding主要用于粒子发动机,而圆柱billboarding用于渲染树木,人,更多的,学习了解这个原理。
计算球形Billboarding的6个角顶点
提示如果您在学习HLSL ,你可以跳过的“performance Considerations:第2部分”的这一节,
     因为在GPU上执行billboarding计算可以让您的程序有一个巨大的速度提升。 本节所谓的“performance Considerations:第1部分”也值得一读。   
    现在,您已经确定了billboards的中心位置,就看您去计算在中心点周围的三角形的6个角顶点:
 1 private void CreateBBVertices()
 2 {
 3     billboardVertices=new VertexPositionTexture[billboardList.Count*6];
 4     int i=0;
 5     foreach(Vector4 currentV4 in billboardList)
 6     {
 7        Vector3 center=new Vector3(currentV4.X,currentV4.Y,currentV4.Z);
 8        float scaling=currentV4.W;
 9        //add rotated vertices to the array
10     }
11 }
    代码之前将首先创建一个数组,它能够存储每个billboards的两个三角形的6个角顶点。接下来,每个billboards将保存在billboardList中,你会发现该中心和大小。环路的其余部分将成为代码,实际计算六个顶点的位置。
    对于每一个二维图像,您需要定义两个三角形,使您可以定义一个四方格或矩形在三维空间,它拥有二维图像。这意味着你将需要确定6 顶点,其中只有四个是独特的。因为它们必须表现出一个纹理,你想VertexPositionTexture保存它们中的每一个。
    直到billboardList包含每个方格的中心位置,你想要所有顶点到中心是同样的距离。在这个例子中,你要增加/减少0.5f偏移到X和Y组成部分的中心,以获取角顶点的位置。例如,下面的3个位置将保持偏移量从一个中心位置确定一个三角形,DL是DownLeft和UR是UpRight:
1 Vector3 posDL=new Vector3(-0.5f,-0.5f,0);
2 Vector3 posUR=new Vector3(0.5f,0.5f,0);
3 Vector3 posUL=new Vector3(-0.5f,0.5f,0);
    如果您增加bullboard的中心位置,以每一个职位,你得到一个三角形的角的位置在三维空间里。然而,这些职位将保持不变,无论相机的位置在哪。所以,如果你移动你的相机靠近三角形,您会看到三角的一个边,如图3-16的左边 。您想要的三角形始终面对摄像头,所以你需要以这些偏移执行各种的旋转, 根据摄像头的位置。这需要相当的数学知识,但幸运的XNA 可以为您立即生成矩阵,您可以使用它把这些偏移。在下面的循环中添加这些代码:
1 Matrix bbMatrix=Matrix.CreateBillboard(center,quatCam.Position,quatCam.UpVector,quatCam.Forward); 
    能够创造这个billboard矩阵旋转的位置,因此三角形形象正面临着相机,XNA需要知道方格和摄像机的位置, 以及相机向前的向量。因为您希望球形billboarding旋转你的图象以至于它能面对摄象机,同样你要指定向上的向量。
注意:当您旋转倒置相机,将在方格旋转以至于他们相对相机仍然向上。为了证明这一点,代码示例使用一个简单的箭头,点向上的纹理。此外,为了使您能够不受限制地旋转相机在示列中,在2-4节中的四元数相机代替2-3节振动的相机 。
    一旦你有了这个矩阵,你可以靠这个矩阵转化旋转偏移量
1 Vector3 posDL=new Vector3(-0.5f,-0.5f,0);
2 Vector3 billboardedPosDL=Vector3.Transform(posDL*scaling,bbMatrix);
3 billboardVectices[i++]=new VertexPositionTexture(billboardedPosDL,new Vector2(1,1));
    第一行定义了静态的偏移量,不需要考虑相机的位置。同样指定了方格的大小,以至现在可以使用这个值。用标准值来乘以偏移量来得到四方格的大小用你指定的AddBillboards方法。现在,你知道中心到角的偏移量,您实际上是靠矩阵来变换的。最后,你保存变换位置,加上正确的纹理坐标,到顶点数组。
    如果这样做的所有一个三角形的三个位置,由此产生的三角将将面对摄像头。
注意:您应用这个转变仅是偏移量:顶点的转化朝着三维空间的中心位置是自动执行的矩阵变换!这就是你的其中一个原因,你必须指定四方格的中心位置在你建立bbMatrix时。
   您需要实现这个转变为每一个偏移量。因为这两个三角形共享两个顶点,您需要执行只有四个转变。这就是CreateBBVertics方法看起来是这样的:
 1 private void CreateBBVertices()
 2 {
 3    billboardVertices=new VertexPositionTexture[billboardList.Count*6];
 4    int i=0;
 5    foreach(Vector4 currentV4 in billboardList)
 6    {
 7       Vector3 center=new Vector3(currentV4.X,currentV4.Y,currentV4.Z);
 8       float scaling=currentV4.W;
 9       Matrix bbMatrix=Matrix.CreatBillboard(center,quatCam.Position,quatCam.UpVector,quatCam.Forward);
10       //first triangle
11       Vector3 posDL=new Vector3(-0.5f,-0.5f,0);
12       Vector3 billboardedPosDL=Vector3.Transform(posDL*scaling,bbMatrix);
13       billboardVertices[i++]=new VertexPositionTexture(billboardedPosDL,new Vector2(1,1));
14       Vector3 posUR=new Vector3(0.5f,0.5f,0);
15       Vector3 billboardedPosUR=Vector3.Transform(posUR*scaling,bbMatrix);
16       billboardVertices[i++]=new VertexPositionTexture(billboardedPosUR,new Vector2(0,0));
17       Vector3 posUL=new Vector3(-0.5f,0.5f,0);
18       Vector3 billboardedPosUL=Vector3.Transform(posUL*scaling,bbMatrix);
19       billboardVertices[i++]=new VertexPositionTexture(billboardedPosUL,new Vector2(1,0));
20       //second triangle:2of3 corner points already calculated!
21       billboardVertices[i++]=new VertexPositionTexture(billboardedPosDL,new Vector2(1,1));
22       Vector3 posDR=new Vector3(0.5f,-0.5f,0);
23       Vector3 billboardPosDR=Vector3.Transform(posDR*scaling,bbMatrix);
24       billboardVertices[i++]=new VertexPositionTexture(billboardedPosDR,new Vector2(0,1));
25       billboardVertices[i++]=new VertexPositionTexture(biilboardedPosUR,new Vector2(0,0));
26    }
27 }
       为每个billboardList的入口,此方法将计算出四点在三维空间,因此,四方格面对的摄像机。
    确保每次你调用这个方法摄象机的位置或旋转的位置都会改变,因为你的billboards的旋转必须作出相应的调整!为了安全起见,你在Update方法结束的时候调用它。
绘制顶点数组
    一旦你所有顶点包含正确的三维位置和纹理坐标,您可以用Draw方法绘制它们到屏幕上,如5-1节所解释说明:
 1 //draw billboards
 2 basicEffect.World=Matrix.Identity;
 3 basicEffect.View=quatMousCam.ViewMatrix;
 4 basicEffect.Projection=quatMousCam.projectionMatrix;
 5 basicEffect.TextureEnabed=true;
 6 basicEffect.Texture=myTexture;
 7 basicEffect.Begin();
 8 foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
 9 {
10     pass.Begin();
11     device.VertexDeclaration=new VertexDeclaration(device,VertexPositionTexture.VertexElements);
12     device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList,billboardVertices,0,billboardList.Count*2);
13     pass.End();
14 }
15 basicEffect.End();
    你想从billboardVertices数组里绘制,它里面的顶点在三角形的表单里。直到每个2D图象成为一个四方格,它由两个三角形绘制组成,因此你要渲染总数为billboardList.Coount*2个三角形。由于你调用了TriangleList,这个数量相当于billboardVertices.Length/3
Performance Considerations: Part 1
    当你使用billboards时,你可以很容易地得到自己的情况,你必须使成千上万的billboards。例如,您可以确定几千个billboards象这样的:
1 private void AddBillboards()
2 {
3    int CPUpower=10;
4    for(int x=-CPUpower;x<CPUpower;x++)
5       for(int y=-CPUpower;y<CPUpower;y++)
6          for(int z=-CPUpower;z<CPUpower;z++)
7              billboardList.Add(new Vector4(x,y,z,0.5f));
8 }
    这将完全填补立方体从(-10,-10,-10)至(9,9,9)与billboards的一方长度是0.5f,每个都在1个单位的距离,总额已经高达8000个billboards!如果在你电脑上不能顺利的执行,尝试降低CPU的功率值,那么少数billboards会被绘制。
    您的电脑能够提供这么多的billboards不仅是因为您使用的是一个大的顶点数组来存储他们所有。这样,您的显卡可以处理绘制16000个三角形整个任务,单运行,这使您的图形卡很轻松。
    代码示例还包括一个小的变化于以前的代码提交相比。在这代码中,为每个billboard,一个新的顶点数组被创建并且填满了billboard的6个顶点,与以前一样以相同的方式显示。然后,对每一个billboard,图形卡从6个顶里绘制两个三角形,整个过程的再次开始。
    几乎与CPU相同的工作量,图象卡接收8000次调用去绘制两个三角形,所以在这次任务里GPU被打扰7999次,所以它非常难受。
    使用这种方法,我可能设置CPUpower只有在3之前,我有一些严重的中断在帧速率中。这相当于只有216个billboard被绘制!
    因此,底线是,一如既往,保持您的图形卡轻松,让它做的工作一个大的运行。总是试图绘制你的场景尽可能用越少的顶点纹理/缓冲。
为圆柱Billboarding计算六个角顶点
    在某些情况下,你不想四方格旋转它已经面对了相机。例如, 如果您要利用二维图像的树木建立森林,你想每个图像都围绕着树干为轴旋转。以前使用球billboarding为您完成,billboards围绕三个轴旋转,所以他们完全面对摄像头。
    想象一下,如果你有某些图像的树木和您移动相机都在他们上面。使用圆柱billboarding,你会得到一个如图3-17左上的结果,在同一图的左下,为了让四方格面对摄象机,树被用了十分不自然的方法旋转。这会得到一些相当奇怪的效果,当相机移开森林时。
    你想要的是,四方格保持树的形象只有在沿着树的向上的方向旋转。如3-17图的右下部分所示。在3D视景中,得到一个结果如图象的右上部分所显示的。
注意:此种billboarding标示圆柱形,因为当你的相机内置了很多这种billboarded图像,他们将创造一个圆筒形隧道。这是所造成的事实,他们被允许旋转沿只有一个方向。图3-18显示出这样一个隧道。   
     代码来计算每个billboard的角落点几乎是一样与这节的一部分。你只需要使用另一个billboarding矩阵,使用Matrix.CreateConstrainedBillboard方法创建
1 Matrix bbMatrix=Matrix.CreateConstrainedBillboard(center,quatCam.Position,new Vector3(0,1,0),quatCam.Forward,null);
    作为额外限制转换,你可以指定billboard被允许的旋转的单一方向。树的上部方向的坐标是(0,1,0)。

     使旋转更准确当相机非常接近的对象时,您还可以指定相机的Up向量。

Performance Considerations: Part 2
    这一节的第一部分显示了如何创建一个billboarding引擎用XNA代码。虽然,这个方法给你的CPU造成了很大的压力而且使你的图象卡的效率很低。
    说你要提供1000 billboarded二维图象在你的三维场景中。每当相机改变立场,则需要重新计算每个图象的四个角顶点的位置您的图片,使他们再次面临相机。目标是更新率60次更新每秒,这也就是说每一秒计算4000个3D位置60次,这些都在CPU里完成,而且,每一祯一个6000个顶点的完整的数组需要被创建去保存需要显示每个billboard的两个三角形。
    中央处理器是通用处理器,也没有真正适合这种计算。这一项削弱了CPU,严重限制了你的需要 ,例如一般游戏逻辑。
    此外,每一祯这个被更新的顶点缓冲需要被送到图形卡中!这要求图形卡把这些数据用nonoptimal内存和增加了大量的通讯在你的PCI-express总线里。
    那岂不是不可思议的,如果你刚才已经存储三维中心位置和每一个billboard只有一个时间在快速静态存储在您的图形卡和您的GPU (计算单位在您的图形卡上)执行所有的计算不是billboarding代替了CPU?请记住,您的GPU是优化顶点的操作,
使它成为一个数量级的速度在做这些计算比你的CPU更快。这个结合了以下重要的好处:
   1。在你的PCI扩展总线上通讯一次。
   2。计算时间非常短(因为GPU在这方面计算做了优化)
   3。绝对没有billboarding计算在CPU上完成,让CPU去做别的事情。
   并介绍了没有缺点。这几乎开始听起来像一个廉价的商业信息!
准备代码为了HLSL Billboarding
   可以使用一个清单来跟踪你的billboards,在没有修改的情况下使用AddBillboards方法。CreateBBVertices方法,然而,这样会很简单,因为所有的billboarding计算将会被实现在GPU的顶点渲染器上。
   billboard将被使用两个方格来绘制,因此为每个billboard你会需要传递六个顶点到GPU中,所以,你实际上想做的顶点渲染器都帮你做了。
   1。你想要顶点渲染器去计算billboard周围每六个顶点的3D旋转坐标,以至于billboard面对摄象机。
   为了实现这个,顶点渲染器需要知道每个顶点以下的信息:
   1。billboard中心到所属的顶点的3D位置。
   2。某种形式的信息确定哪些角落点,是当前billboard的顶点,以至于顶点渲染器可以计算相应从中心的偏移量。
   3。一般的纹理坐标,他们能传递到象素着色器里,使用它们从纹理到样本的正确位置。
注意:很显然,您的顶点着色,还需要知道摄象机在3D空间的位置在它可以旋转billboards之前,使他们面对的相机。这台相机的位置与所有顶点相同, 然而,因此应该被设置作为XNA-to-HLSL的变量,而不是在每个顶点中存储他们的位置。
   这意味着,属于billboard的六个顶点这部门信息是相同的:billboard的中心的3D的位置。确定了四个角顶点之一的顶点信息,例如,你可以传递一个介于0到3之间的一个数 ,其中0意味着左上角点和3意味是右下角点。
   不过,您已经传递这种信息到顶点着色器中靠纹理坐标!事实上,(0,0)纹理坐标表明目前的顶点是左上角点,和(1,0)纹理坐标显示右上角点.
   总结:每个billboard你想被绘制,您需要提供两个三角形,所以您需要传递6个顶点到顶点着色中。这6个顶点将进行同样的位置数据:billboard的中心。六个顶点还将带有其具体纹理坐标,两个三角形带有需要正确纹理,但在这种情况下,纹理协调也将使用顶点着色器,以确定目前顶点是哪些角落点,这样的顶点着色可以计算偏移量当前顶点到billboard中心。
   所以在XNA代码中,调整CreateBBVertices方法以至于产生他们的顶点同时保存它们到一个数组中:

 1 private void CreateBBVertices()
 2 {
 3     billboardVertices=new VertexPositionTexture[billboardList.Count*6];
 4     int i=0;
 5     foreach(Vector4 currentV4 in billboardList)
 6     {
 7        Vector3 center=new Vector3(currentV4.X,currentV4.Y,currentV4.Z);
 8        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(0,0));
 9        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(1,0));
10        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(1,1));
11        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(0,0));
12        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(1,1));
13        billboardVertices[i++]=new VertexPositionTexture(center,new Vector2(0,1));
14     }
15 }
    对于在你清单中的每一个billboard,您添加您的六个顶点到数组中。每一个顶点包含有billboard的中心位置,以及正确的纹理坐标。
    好消息是,你必须调用此方法在你程序的开始时只有一次,因为该数组的内容并不需要更新,当相机位置改变时!例如,从Initialize方法中,这个方法在AddBillboards方法后:
1 AddBillboards();
2 CreateBBVertices();
   现在你的顶点在GPU中已经被转变了,可以开始编写顶点着色器了。让我们先开始圆柱billboarding情况 ,因为它比球形billboarding更容易一点。
注意:保存这个数据到图象卡的内存里有明显的优点,直到你不能更新它的内容。你可以实现它从billboardVertices数组中得到VertexBuffer.参看5-4节如何建立一个VertexBuffer.

圆柱Billboarding的顶点着色器
     如同所有的HLSL代码在这本书,让我们开始,这个变量从你的XNA程序中传到你的着色器中,纹理阶段,顶点/像素着色器输出的结构:

 1 //--------XNA interface----
 2 float4*4 xView;
 3 float4*4 xProjection;
 4 float4*4 xWorld;
 5 float3 xCamPos;
 6 float3 xAllowedRotDir;
 7 //--------Texture Samplers-------
 8 Texture xBillboardTexture;
 9 sampler textureSampler=sampler_state{texture=<xBillboardTexture>;magfilter=LINEAR;minfilter=LINEAR;mipfilter=LINEAR;AddressU=CLAMP;AddressV=CLAMP;};
10 struct BBVertextToPixel
11 {
12     float4 Position:POSITION;
13     float2 TexCoord:TEXCOORD0;
14 };
15 struct BBPixelToFrame
16 {
17    float4 Color:COLOR0;
18 }
     一如既往地当您需要提供一个三维世界的2D画面,你需要世界,视景和投影矩阵。要执行billboarding计算,您还需要知道当前相机的位置。既然你开始圆柱billboarding ,您需要确定围绕这个轴心的图象可以旋转。
    您只需要一个纹理阶段,把实际图像放在两个三角形上。对于每个顶点,顶点着色器将产生强制性的2D画面坐标,以及相应的纹理坐标。每个像素的像素着色器将输出它的颜色。
    billboarding的情况下,顶点着色负责计算到中心的偏移量。让我们先从一些着色器的代码开始,增加了只有到顶点的偏移量;真正billboarding 将建立在此代码创建
 1 //------Technique:CylBillboard-------
 2 BBVertexToPixel CylBillboardVS(float3 inPos:POSITIONo,float2 inTexCoord:TEXCOORDo)
 3 {
 4      BBVertexToPixel Output=(BBVertexToPixel)o;
 5      float3 center=mul(inPos,xWorld);
 6      float3 upVector=float3(0,1,0);
 7      float3 sideVector=float(1,0,0);
 8      float3 finalPosition=center;
 9      finalPosition+=(inTexCoord.x-0.5f)*sideVector;
10      finalPosition+=(0.5f-inTexCoord.y)*upVector;
11      float4 finalPosition4=float4(finalPosition,1);
12      float4*4 preViewProjection=mul(xView,xProjection);
13      Output.Position=mul(finalPosition4,preViewProjection);
14      Output.TexCoord=inTexCoord;
15      return Output;
16 }

     对于每一个顶点,在你的XNA程序中顶点着色器接收位置和纹理坐标。请注意,这个位置是在billboard的3D位置的中心。乘法与世界矩阵可以定义一个全球性的世界矩阵,因此您可以一次性的旋转/缩放/把所有的billboards。最后中心的位置是存储在一个称为center变量中,这将是属于一个billboard相同的六个顶点。
    接下来,一个静态的大小向量和Up向量被定义。这些向量是billboard的中心到个别顶点的偏移量。看看您要如何从中心到六个顶点的偏移量如图3-19,图里的一个billboard的两个三角形被显示。注意四个纹理坐标以外的两个三角形和6个顶点indices在三角形的角中。

    下一个代码块的计算具体顶点的最终的三维位置。首先,你开始从中心位置开始。接下来,您想知道当前的向量是否需要偏移到(-1,0,0)的左边方向或者(1,0,0)右边方向。在纹理的X坐标图3-19节你可以查到:一个顶点的纹理坐标(0,0)是左上角顶点,因此,需要转移到了左侧。顶点的纹理坐标(1,0)是右上角顶点,因此,需要转移到右侧。
    所有这些都被编写成简单的一行:首先你减去0.5f从X纹理的坐标。如果是中心的左顶点将得到-0.5f,如果是中心的右顶点得到0.5f。乘以(+1,0,0)向量,你会得到(-0.5f,0,0)作为左顶点(+0.5f,0,0)作为右顶点!
    同样的做法保存Y的纹理坐标 :(0,0)纹理坐标意味着左上角的顶点,和(0,1)指左下角。因此,如果在Y纹理坐标为0 ,你有一个顶端顶点;如果是1 ,你有一个底部顶点.
    你添加两个正确的Side和Up偏移量到中心位置,结束与指定顶点的正确3D位置。
    这是这一节的特殊的代码。您现在要做的是把这个三维能够转换到二维屏幕空间像往常一样通过改变它用ViewProjection矩阵。前您可以把这个float3用4 *4矩阵转变,你需要使它成为一个float4 ,增加了1作为第四个坐标。
    当您使用的Pixel Shader属于这种技术,所有的billboards将成为很好看的四方格,但他们都互相平行。这是因为您还没有使用摄像机位置的代码,计算偏移量,你需要使用旋转您的三角形,使他们面对的相机。
    这点很明显不能用静态UpSide向量来完成在顶点着色器中。圆柱billboarding有一点容易,因为你已经定义了billboard的Up向量:它允许旋转方向,传递到shaderXNA应用在xAllowedRotDir变量中:
    例如,两棵树,显示在图3-20的左侧的一部分。因为您指定允许旋转方向的在XNA程序中作为树的Up向量,你已经知道Up向量,会在顶点着色器中使用它。

    billboardSide向量是事先不知道的。但是,与往常一样,您可以找到这一点。 为此,你需要知道Eye向量,这向量是从眼睛朝向billboard的中心,并显示为虚线如图3-20。有一件事关于Side向量的是它垂直于Eye向量和Up向量,如图3-20右边部分所示,是左边的图象从一个不同的角度看得出的。
   您可以随时找到向量是垂直于另外两个向量,采取十字相交的向量(如5-7节),所以这是如何找到Side向量。
   翻译HLSL代码,顶点着色器的内部时钟机构成为如下:

1 float3 center=mul(inPos,xWorld);
2 float3 eyeVector=center-xCamPos;
3 float3 upVector=xAllowedRotDir;
4 upVector=normalize(upVector);
5 float3 sideVector=cross(eyeVector,upVector);
6 sideVector=normalize(sideVector);
7 float3 finalPosition=center;
8 finalPosition+=(inTexCoord.x-0.5f)*sideVector;
9 finalPosition+=(0.5f-inTexCoord.y)*upVector;
      你可以找到你Eye向量正如将找到任何向量从A点到B点:由B-A作为讨论,你的billboardUp向量是允许旋转方向的,被你的XNA代码所指定。直到Side向量是垂直于Eye向量和Up向量,你可以用两个向量的十字坐标找到它。
    确保两个Side和UP向量有统一的长度(你不能控制billboard的最终的大小),所以你normalize两个向量。
    一旦你知道billboard的Up和Side向量,你可以重新使用相同的代码转换3D的位置到2D屏幕坐标。现在你的顶点着色器利用相机的当前的位置来计算最后的顶点的三维位置,所以您的顶点会改变他们的位置当相机被移动的时候!

 Finishing Off the Billboarding Technique: Pixel Shader and Technique Definition
    现在,您的顶点着色能够正确计算三维坐标的顶点, 所有您需要做的就是把纹理转成三角形在像素着色器里:

1 BBPixelToFrame BillboardPS(BBVertexToPixel PSIn):COLORo
2 {
3     BBPixelToFrame Output=(BBPixelToFrame)o;
4     Output.Color=tex2D(textureSampler,PSIn.TexCoord);
5     return Output;
6 }
    当然,这里的定义是技术:
1 technique CylBillboard
2 {
3     pass Passo
4     {
5         VertexShader=compile vs_1_1 CylBillboardVS();
6         PixelShader=compile ps_1_1 BillboardPS();
7     }
8 }
    这将是所有的时刻。所有您需要做的是导入HLSL文件(我称之为bbEffect.fx)到您的项目和加载它变成一个变量在您LoadContent方法:
1 bbEffect=content.Load<Effect>("bbEffect");
    现在在您的Draw方法中,您可以设置所需要的XNA-to-HLSL变量和最后绘制billboards
 1 bbEffect.CurrentTechnique=bbEffect.Techniques["CylBillboard"];
 2 bbEffect.Parameters["xWorld"].SetValue(Matrix.Identity);
 3 bbEffect.Parameters["xProjection"].SetValue(quatMousCam.ProjectionMatrix);
 4 bbEffect.Parameters["xView"].SetValue(quatMouCam.ViewMatrix);
 5 bbEffect.Parameters["xCamPos"].SetValue(quatMousCam.Position);
 6 bbEffect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0,1,0));
 7 bbEffect.Parameters["xBillboardTexture"].SetValue(myTexture);
 8 bbEffect.Begin();
 9 foreach(EffectPass pass in bbEffect.CurrentTechnique.Passes)
10 {
11    pass.Begin();
12    device.VertexDeclaration=new VertexDeclaration(device,VertexPositionTexture.VertexElements);
13    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList,billboardVertices,0,billboardList.Count*2);
14    pass.End();
15 }
16 bbEffect.End();
    注意:您所表明的,例如,(0,1,0)Up方向作为您的billboards旋转方向。
    如果您的电脑配备了独立的显卡,你应该能够使billboards比现在的XNA版本绘制的更多。
Vertex Shader for Spherical Billboarding  
     圆柱和球形billboarding之间的差异是,在球形billboarding 每个billboard已完全面对摄像头。你的顶点着色器代码,两个Up和Side向量必须完美的垂直于Eye向量(在圆柱billboarding,只有Side向量是垂直于Eye向量)
    在球形的billboarding的挑战是你也必须找到Up向量,因为这不再是优先的情况在此圆柱billboarding 。为了帮助你解决这个问题,你需要知道相机的Up向量。
     既然你想要Billboard完全面对摄像机,你可以说billboard的Side向量将会需要垂直于Eye向量和相机的Up向量。证明这一点,我已经给平面的眼睛和CamUp向量着色了如图3-21左侧部分,您可以看到,Side向量垂直于这个平面。自从它们是垂直的,你已经知道如何找到Side向量:它在Eye向量和CamUp向量之间是十字的:
1 float3 sideVector=cross(eyeVector,xCamUp);
2 sideVector=normalize(sideVector);


   现在你知道了Side向量了,你同样可以找到billboard的Up向量:这个向量垂直于Side向量和Eye向量。在图3-21的右边我已经把Side的平面和Eye向量已经着色了,这样你就可以再次尝试想象这一点。这个意思是billboard的Up向量比Eye和Side向量的十字更没有什么:

1 float3 upVector=cross(sideVector,eyeVector);
2 upVector=normalize(upVector);
   这是所有需要更改时,从圆柱到球形billboarding !只要确定您定义的xCamUp变量。 。 。
float3 xCamUp;
   ......和一个新的技术效果:
1 technique SpheBillboard
2 {
3     pass Passo
4     {
5         VertexShader=compile vs_1_1 SpheBillboardVS();
6         PixelShader=compile ps_1_1 BillboardPS();
7     }
8 }

    ....直到你能使用同样的象素着色器,您准备好去做!
    请务必正确调用技术要求从您的XNA项目,并设置xCamUp参数:

1 bbEffect.CurrentTechnique=bbEffect.Techniques["SpheBillboard"];
2 bbEffect.Parameters["xWorld"].SetValue(Matrix.Identity);
3 bbEffect.Parameters["xProjection"].SetValue(quatMousCam.ProjectionMatrix);
4 bbEffect.Parameters["xView"].SetValue(quatMouCam.ViewMatrix);
5 bbEffect.Parameters["xCamPos"].SetValue(quatMousCam.Position);
6 bbEffect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0,1,0));
7 bbEffect.Parameters["xBillboardTexture"].SetValue(myTexture);
代码
    参看上面的代码。
原文地址:https://www.cnblogs.com/315358525/p/1532004.html