[翻译]XNA 3.0 Game Programming Recipes之thirty-seven
PS:自己翻译的,转载请著明出处格
6-1 定义法线和使用BasicEffect
问题
没有相应的光照,你的场景缺少点真实感。在某些情况下,3D效果将会完全失去当对象没有正确的被照亮时。
作为一个例子,考虑一个范围有一个实心的颜色。没有任何的光照,范围内所有的象素将有一个同样的颜色,使在屏幕上的结果看起来平板的光盘。当存在正确的光照时,范围内有部分面对光照会有一个明亮的颜色比起其他的部分,使这个范围出现一个真实的3D对象。
解决方案
在电脑的图象中,所有3D对象都由三角形组成。你想这些三角形中的每一个都被正确的照亮通过相应的入射光。图6-1显示了一个单一方向的光从左到右,它影响了六个不同的位置的四方格,每一个都由两个三角形组成。
仅仅定义光源的位置和你的对象的位置是不够的,你的图象卡添加正确的光到一个对象。你3D对象的每一个三角形,需要添加一些信息,允许你的图形卡去计算大量光撞击这个表面。
为此,你可以指定法线向量在你的对象的每一个顶点,它刺穿三角形的角如图6-1所显示。一旦你已经指定正确的法线在每个顶点,这个BasicEffect能绘制你的对象用恰当的光照。
它是如何工作的
在图6-1中,多个正方形(每个由两个三角形组成)被绘制,并且它们的颜色被大量的入射光所描绘。更多的正方形垂直于光的方向,更多的光它能接收。最后的正方形完全垂直于光的方向,所以被完全照亮。那第一个四方形,虽然,沿着光的方向布置,因此,根本就接收不到光。
法线的定义
图形卡是如何知道三角形能接收多少光照?在每个三角形的角,你定义垂直于三角形的方向。这个方向被称为法线。这个法线的方向已经被定义在图6-1的每个顶点中作为一个spike(译者:长钉)。由于那里只有一个方向垂直于一个表面,所有同样四方格的所有顶点有相同的法线方向。
这个法线方向允许你的图形卡去计算多少个光撞击到三角形上。如6-5节有详细的说明,通过投影那个法线在光照的方向上。如图6-2所示。光照的方向被作为张黑箭头显示,如图6-1所描绘的四方格。每个方格的法线投影在光照方向作为厚黑的砖块显示出来在光照的方向上。大的黑块,你的三角形更应该被照亮。
在左边的三角形的法线垂直于光照的方向,所以投影是0,三角形不会被照亮。这在右边的三角形有一个法线平行于光照的方向,所以它的投影会是最大,并且这一片将会完全照亮。
鉴于光照的方向和法线在一个顶点内,你的图形卡能很容易计算投影的长度(厚黑块)。这是图形卡如何计算正确的光照。
运用阴影到你的屏幕中
每个象素,图形卡将会计算大量的光照,它被运用在象素中,在前面我们提到过。接下来,图形卡乘以这个数量的光照通过象素颜色的初始值。
添加法线数据到你的顶点中
前面的段落已经解释了,接近3D位置和颜色,每一个顶点同样保存它的法线方向。
XNA伴随一个预定义顶点格式发生,它允许你保存一个法线在每个顶点:VertexPositionNormalTexture结构。这个格式允许你保存3D位置,法线方向,和纹理坐标为每一个顶点。参看5-2节在纹理三角形和5-13节了解你如何能定义你的自己的顶点格式。
下面的方法创建一个数组去保存六个这样的顶点,它定义两个三角形,创建一个四方格。这个四方格平放在地面上,由于顶点的Y坐标是0。因此,法线保存在每个顶点是(0,1,0)Up方向,因为这方向垂直于四方格。这顶点格式同样期望你添加纹理坐标,这样图形卡知道哪里从一个图象上取样颜色(参看5-2节)。
提示:在这种情况下的三角形平放在地面上,它很容易的计算出它的法线。参看5-7节去了解你如何能自动的计算法线为一更为复杂的对象。
正负法线
一个三角形有一个实在的垂直方向。如果三角形平放在地面上作如前面的代码,你能定义一个法线作为指向上方或者下方。所以,哪一个应该使用?这非常重要,因为一个错误的选择将导致不正确的光照和根本没有光照。
作为一个规则,你需要选择法线,它指向对象的外部,三角形是其中一部分。
一般来讲,三角形的一个面由3D对象的"inside"部分所组成,它是其中一部分,同时另一面将会是对象的"outside"的一部分。在这样的情况下,你想选择法线指向对象的外部。
设置BasicEffect参数
随着你的顶点定义,你已经准备绘制你的三角形。这一章的第二部分解释你如何能编写你自己的HLSL效果,但是在第一节,你简单使用这BasicEffect.这BasicEffect是一个预先确定的效果,它被使用绘制的材料,使用基础光照效果。确保你有一个BasicEffect变量,循环每一祯,由于它过于昂贵,创建一个新的BasicEffect对象每一祯。添加这个变量到你的类的上部:
接下来,你配置一般的光照设置通过第一有利光照和定义一个周围的颜色。这是大量的光你的对象总是点亮的,不管它们的方位相对你的光源。你定义一个巨大灰色阴影,所以即使在没有光照,你的对象也会显示非常微弱!
如果你的图形卡能处理预先象素光照,你能使用这些,参看6-3节象素光照。
最后,你定义你的光源。使用BasicEfect,你能一次绘制你的场景被照亮从这三个光源。这样做,你需要设置光照的方向,和光的颜色。最后但是不重要,你应该使光照随时能用。在这个简单的例子里,你仅仅使用了一个光照。
随着你的BasicEffect设置,你已经准备绘制你的三角形,使用BasicEfect:
使用世界矩阵
在绘制你的三角形时,你能设置一个世界矩阵在这种效果。这允许你移动你的三角形到另一个位置在3D世界或者旋转或者缩放它们在它们被绘制到屏幕之前(参看4-2节)。Well-constructed效果(如BasicEffect)同样应用这个转换到法线数据在每个象素里面,以为是必须的。例如,如果正方形在前面的代码被旋转,法线自动旋转。
下面的代码显示这样一个例子。
规格化你的法线
注意:在你阅读这章之前,你需要知道一个法线和不定式到规格化之间的不同。正如前面谈论的,一个法线是垂直于一个三角形。为了规格化意思是使一个统一长度的向量。当你规格化一个向量,你减少(或者扩展)它,以便它的长度正好为1。
一个顶点的大量光照应该被定义只在通过光和它的法线之间的角度。虽然,这数量作为计算出通过图象卡同样依赖发现的长度,和光照方向的长度。因此,你需要确保两个法线和你定义的光照方向长度正好是1,它能通过规格化它们实现。
当它们都有统一的长度,大量的光照仅仅依靠它们之间的角度,它就是你想要的。
代码
参看上面的代码!
6-1 定义法线和使用BasicEffect
问题
没有相应的光照,你的场景缺少点真实感。在某些情况下,3D效果将会完全失去当对象没有正确的被照亮时。
作为一个例子,考虑一个范围有一个实心的颜色。没有任何的光照,范围内所有的象素将有一个同样的颜色,使在屏幕上的结果看起来平板的光盘。当存在正确的光照时,范围内有部分面对光照会有一个明亮的颜色比起其他的部分,使这个范围出现一个真实的3D对象。
解决方案
在电脑的图象中,所有3D对象都由三角形组成。你想这些三角形中的每一个都被正确的照亮通过相应的入射光。图6-1显示了一个单一方向的光从左到右,它影响了六个不同的位置的四方格,每一个都由两个三角形组成。
仅仅定义光源的位置和你的对象的位置是不够的,你的图象卡添加正确的光到一个对象。你3D对象的每一个三角形,需要添加一些信息,允许你的图形卡去计算大量光撞击这个表面。
为此,你可以指定法线向量在你的对象的每一个顶点,它刺穿三角形的角如图6-1所显示。一旦你已经指定正确的法线在每个顶点,这个BasicEffect能绘制你的对象用恰当的光照。
它是如何工作的
在图6-1中,多个正方形(每个由两个三角形组成)被绘制,并且它们的颜色被大量的入射光所描绘。更多的正方形垂直于光的方向,更多的光它能接收。最后的正方形完全垂直于光的方向,所以被完全照亮。那第一个四方形,虽然,沿着光的方向布置,因此,根本就接收不到光。
法线的定义
图形卡是如何知道三角形能接收多少光照?在每个三角形的角,你定义垂直于三角形的方向。这个方向被称为法线。这个法线的方向已经被定义在图6-1的每个顶点中作为一个spike(译者:长钉)。由于那里只有一个方向垂直于一个表面,所有同样四方格的所有顶点有相同的法线方向。
这个法线方向允许你的图形卡去计算多少个光撞击到三角形上。如6-5节有详细的说明,通过投影那个法线在光照的方向上。如图6-2所示。光照的方向被作为张黑箭头显示,如图6-1所描绘的四方格。每个方格的法线投影在光照方向作为厚黑的砖块显示出来在光照的方向上。大的黑块,你的三角形更应该被照亮。
在左边的三角形的法线垂直于光照的方向,所以投影是0,三角形不会被照亮。这在右边的三角形有一个法线平行于光照的方向,所以它的投影会是最大,并且这一片将会完全照亮。
鉴于光照的方向和法线在一个顶点内,你的图形卡能很容易计算投影的长度(厚黑块)。这是图形卡如何计算正确的光照。
运用阴影到你的屏幕中
每个象素,图形卡将会计算大量的光照,它被运用在象素中,在前面我们提到过。接下来,图形卡乘以这个数量的光照通过象素颜色的初始值。
添加法线数据到你的顶点中
前面的段落已经解释了,接近3D位置和颜色,每一个顶点同样保存它的法线方向。
XNA伴随一个预定义顶点格式发生,它允许你保存一个法线在每个顶点:VertexPositionNormalTexture结构。这个格式允许你保存3D位置,法线方向,和纹理坐标为每一个顶点。参看5-2节在纹理三角形和5-13节了解你如何能定义你的自己的顶点格式。
下面的方法创建一个数组去保存六个这样的顶点,它定义两个三角形,创建一个四方格。这个四方格平放在地面上,由于顶点的Y坐标是0。因此,法线保存在每个顶点是(0,1,0)Up方向,因为这方向垂直于四方格。这顶点格式同样期望你添加纹理坐标,这样图形卡知道哪里从一个图象上取样颜色(参看5-2节)。
1 private void InitVertices()
2 {
3 vertices=new VertexPositionNormalTexture[6];
4 int i=0;
5 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,1),new Vector3(0,1,0),new Vector2(1,1));
6 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,-1),new Vector3(0,1,0),new Vector2(0,1));
7 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,-1),new Vector3(0,1,0),new Vector2(0,0));
8 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,-1),new Vector3(0,1,0),new Vector2(0,0));
9 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,1),new Vector3(0,1,0),new Vector2(1,0));
10 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,1),new Vector3(0,1,0),new Vector2(1,1));
11 myVertexDeclaration=new VertexDeclaration(device,VertexPositionNormalTexture.VertexElements);
12 }
最后一行确保这VertexDeclaration(参看5-1节)一次成功创建,由于这个不会在被改变。2 {
3 vertices=new VertexPositionNormalTexture[6];
4 int i=0;
5 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,1),new Vector3(0,1,0),new Vector2(1,1));
6 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,-1),new Vector3(0,1,0),new Vector2(0,1));
7 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,-1),new Vector3(0,1,0),new Vector2(0,0));
8 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,-1),new Vector3(0,1,0),new Vector2(0,0));
9 vertices[i++]=new VertexPositionNormalTexture(new Vector3(1,0,1),new Vector3(0,1,0),new Vector2(1,0));
10 vertices[i++]=new VertexPositionNormalTexture(new Vector3(-1,0,1),new Vector3(0,1,0),new Vector2(1,1));
11 myVertexDeclaration=new VertexDeclaration(device,VertexPositionNormalTexture.VertexElements);
12 }
提示:在这种情况下的三角形平放在地面上,它很容易的计算出它的法线。参看5-7节去了解你如何能自动的计算法线为一更为复杂的对象。
正负法线
一个三角形有一个实在的垂直方向。如果三角形平放在地面上作如前面的代码,你能定义一个法线作为指向上方或者下方。所以,哪一个应该使用?这非常重要,因为一个错误的选择将导致不正确的光照和根本没有光照。
作为一个规则,你需要选择法线,它指向对象的外部,三角形是其中一部分。
一般来讲,三角形的一个面由3D对象的"inside"部分所组成,它是其中一部分,同时另一面将会是对象的"outside"的一部分。在这样的情况下,你想选择法线指向对象的外部。
设置BasicEffect参数
随着你的顶点定义,你已经准备绘制你的三角形。这一章的第二部分解释你如何能编写你自己的HLSL效果,但是在第一节,你简单使用这BasicEffect.这BasicEffect是一个预先确定的效果,它被使用绘制的材料,使用基础光照效果。确保你有一个BasicEffect变量,循环每一祯,由于它过于昂贵,创建一个新的BasicEffect对象每一祯。添加这个变量到你的类的上部:
1 BasicEffect basicEffect;
在你的LoadContent方法中的示例:
1 basicEffect=new BasicEffect(device,null);
下面的设置将绘制你的3D场景光照从一个光照的方向,象太阳一样:
1 basicEffect.World=Matrix.Identity;
2 basicEffect.View=fpsCam.ViewMatrix;
3 basicEffect.Projection=fpsCam.ProjectionMatrix;
4 basicEffect.Texture=myTexture;
5 basicEffect.TextureEnabled=true;
6 basicEffect.LightingEnabled=true;
7 basicEffect.AmbientLightColor=new Vector3(0.1f,0.1f,0.1f);
8 basicEffect.PreferPerPixelLighting=true;
9 basicEffect.DirectionalLight0.Direction=new Vector3(1,-1,0);
10 basicEffect.DirectionalLight0.DiffuseColor=Color.White.ToVector3();
11 basicEffect.DirectionalLight0.Enabled=true;
12 basicEffect.DirectionalLight1.Enabled=false;
13 basicEffect.DirectionalLight2.Enabled=false;
这上面的部分设置世界,视景,和投影矩阵,它是必须的转换你的3D场景到你的2D场景。参看2-1和4-2节获得更多的信息。由于你已经保存纹理坐标在每个顶点的内部,你能传递一个纹理到你的图形卡,并且能够使用纹理,这样你的三角形将从图象中得到颜色。参看5-2节得到更多关于纹理的信息。2 basicEffect.View=fpsCam.ViewMatrix;
3 basicEffect.Projection=fpsCam.ProjectionMatrix;
4 basicEffect.Texture=myTexture;
5 basicEffect.TextureEnabled=true;
6 basicEffect.LightingEnabled=true;
7 basicEffect.AmbientLightColor=new Vector3(0.1f,0.1f,0.1f);
8 basicEffect.PreferPerPixelLighting=true;
9 basicEffect.DirectionalLight0.Direction=new Vector3(1,-1,0);
10 basicEffect.DirectionalLight0.DiffuseColor=Color.White.ToVector3();
11 basicEffect.DirectionalLight0.Enabled=true;
12 basicEffect.DirectionalLight1.Enabled=false;
13 basicEffect.DirectionalLight2.Enabled=false;
接下来,你配置一般的光照设置通过第一有利光照和定义一个周围的颜色。这是大量的光你的对象总是点亮的,不管它们的方位相对你的光源。你定义一个巨大灰色阴影,所以即使在没有光照,你的对象也会显示非常微弱!
如果你的图形卡能处理预先象素光照,你能使用这些,参看6-3节象素光照。
最后,你定义你的光源。使用BasicEfect,你能一次绘制你的场景被照亮从这三个光源。这样做,你需要设置光照的方向,和光的颜色。最后但是不重要,你应该使光照随时能用。在这个简单的例子里,你仅仅使用了一个光照。
随着你的BasicEffect设置,你已经准备绘制你的三角形,使用BasicEfect:
1 basicEffect.Begin();
2 foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
3 {
4 pass.Begin();
5 device.VertexDeclaration=myVertexDeclaration;
6 DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList,vertices,0,2);
7 pass.End();
8 }
9 basicEffect.End();
注意:如果你关闭了光照,通过设置basicEffect.LightingEnabledt成false,你的屏幕将会绘制出充分的强度。如在"Apply Shading toyour Scene"节所说明的,这图形卡乘以每个象素的初始颜色,用大量光照在象素中。如果你关闭光照,图形卡简单绘制每个象素的初始颜色。这实际上相当于光照因子为1,意思是非常强烈。2 foreach(EffectPass pass in basicEffect.CurrentTechnique.Passes)
3 {
4 pass.Begin();
5 device.VertexDeclaration=myVertexDeclaration;
6 DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleList,vertices,0,2);
7 pass.End();
8 }
9 basicEffect.End();
使用世界矩阵
在绘制你的三角形时,你能设置一个世界矩阵在这种效果。这允许你移动你的三角形到另一个位置在3D世界或者旋转或者缩放它们在它们被绘制到屏幕之前(参看4-2节)。Well-constructed效果(如BasicEffect)同样应用这个转换到法线数据在每个象素里面,以为是必须的。例如,如果正方形在前面的代码被旋转,法线自动旋转。
下面的代码显示这样一个例子。
规格化你的法线
注意:在你阅读这章之前,你需要知道一个法线和不定式到规格化之间的不同。正如前面谈论的,一个法线是垂直于一个三角形。为了规格化意思是使一个统一长度的向量。当你规格化一个向量,你减少(或者扩展)它,以便它的长度正好为1。
一个顶点的大量光照应该被定义只在通过光和它的法线之间的角度。虽然,这数量作为计算出通过图象卡同样依赖发现的长度,和光照方向的长度。因此,你需要确保两个法线和你定义的光照方向长度正好是1,它能通过规格化它们实现。
当它们都有统一的长度,大量的光照仅仅依靠它们之间的角度,它就是你想要的。
代码
参看上面的代码!