第8章,3d游戏编程基础

[size=small][color=red]书<<Beginning.XNA.3.0.Game.Programming.From.Novice.to.Professional>>第8章,,图片未贴出,原译文在附近中.[/color][/size]


[b]3d游戏编程基础[/b]

第2章,讨论2的坐标系统,包含一个特殊的例子:屏幕坐标系.当处理3d坐标系是,为了定义一个3d虚拟物体并将其转换到屏幕的2d坐标涉及到更多.
本章是3d编程基础.首先你将学习基本的概念,并学习怎样将其应用到简单xna项目.为创建一个3d游戏做准备.
注意 xna3.0 不支持在zune上创建3d游戏
3d坐标系和投影
当处理3维笛卡尔坐标系时,有2种不同的坐标系:左右手.这些名字参考了z轴位置与x,y轴的关系.为了检测这个位置,伸出一个手的手指,让它指向x轴的正方向并顺时针移动手到y轴的正方向.拇指所指就是z轴方向.见图8-1
不同之处,左手坐标系从屏幕外向内z值是增加(将屏幕想成屏幕的x,y轴).右手坐标系却正好相反,朝向你的方向是z增长.
Xna使用左手坐标系(与directx不同).表示负z值是可见的,负值越小物体离屏幕越远.正值不可见,除非你改变摄影机的位置,之后的章节会学到.

图 8-1 笛卡尔3d坐标系
现在你了解了3d坐标系,下一步来看下如何将3d物体从3d坐标系转到计算机(或xbox360)2d坐标系.
比较方便的是xna已经完成了繁杂的数学工作,但你还是需要了解投影的概念及xna中如何应用它将物体投影到屏幕.
像其他的游戏库一样,xna也提供了2个不同的投影:
透视投影:最常用的投影,透视投影根据物体来调整z距离.但物体越远时物体越小.根据位置物体也会出现变形,如现实世界.比如,立方体靠近屏幕的面看起来比对面的面大.图8-2图示了透视投影:

图8-2 透视投影
正交投影:此类投影,直接忽略z分量,靠近屏幕物体不会变化.常常用于2d游戏(俗称假3d,只是将图元覆盖其上),创建一个仪表(hud是2d元素,如血条,窗口等)或简单的3d游戏.图8-3显示了正交投影.

图8-3正交投影
待会你将看到如何在xna中使用每个投影.在开始代码之前,你需要明白3d物体是如何在游戏中进行显示,这是下节的主图.
顶点和基本体
最基础的3d物体是: 顶点.数学上顶点被它们3d坐标唯一定义(xna总是Vector3数据类型),但在xna中他们还有额外的信息-如颜色,纹理,法线向量信息.表8-1显示了xna框架默认顶点定义.这可以在游戏开发时进行继承.
表8-1 xna中定义的顶点格式结构定义
顶点格式
VertexPositionColor 用位置和渲染颜色定义顶点
VertexPositionTexture 用位置和纹理坐标定义顶点,纹理指定了在顶点上如何匹配一个纹理,左上角是(0,0),右下角是(1,1)
VertextPositionColorTexture 用位置,颜色,纹理定义顶点
VertexPositionNormalTexture 用位置,法线,纹理定义顶点
顶点的位置和额外信息,当创建3d物体是,你还需要根据基本体不同的绘制方式来指定xna如何连接这些顶点.
注意: 绘制基本体(Drawing Primitives)是xna渲染的三个基本几何(点,线,面)中的一个.不同的基本体,同样的顶点将有不一样的渲染.
三角被用来创建任何2d,3d物体.这是因为只有3个点可以确定一个单独的凸面.(连接三角内的任何2点,都处于三角形内,而4点的图形不一定这样.)这些特性显卡执行高速渲染的关键,几何都使用三角作为渲染的基本体.
如果,你想在屏幕上绘制一个平面,你将使用2个三角形,如果你想绘制一个立方体,你需要12个三角(每面2个).图8-4:

图8-4 三角形组成的立方体
在xna中,显卡设备对象有一个DrawPrimitives方法,被用来根据基本体类型绘制顶点缓冲,定义好的PrimaitiveType枚举:
PointList: 每个顶点彼此独立,因为你可以看到一些浮动的点,图8-5显示了用点集渲染的顶点.

图8-5 点集
LineList:顶点被成对渲染,用线将1对顶点连接起来.如何顶点缓冲中顶点的个数不是偶数此方法将调用失败.图8-6显示了线集的基本体.

图8-6 相同的顶点用,线集来渲染
LineStrip: 所有的顶点被渲染为单独的连接线.当调试时,这很有用,因为基本体类型允许你看到物体外面的线框而不管顶点的数量.图8-7显示了线条.

图8-7 LineStrip
TriangleList:以3个顶点为一组渲染一个三角形,可以完成复杂场景的渲染,但如果你想渲染连接在一起的三角时会有很多重复的顶点.图8-8使用了三角集来渲染顶点.

图 8-8 相同的顶点渲染三角集
TriangleStrip:渲染连接的三角.渲染场景时比较高效,因为不需要有重复的顶点.每个新的顶点和前面的2个顶点组成一个新的三角,图8-9显示了三角条.

图8-9 相同的顶点渲染三角条
TriangleFan:所有的三角共用第一个顶点.(缓冲中的第一个),每新加一个顶点则使用第一个顶点和最后一个顶点来创建一个新的三角.负8-10显示了三角扇.

图8-10 相同的顶点渲染三角扇
注意: 当渲染三角形时,如何你想让xna知道什么三角形面向摄影机你需要注意三角形顶点的顺序.当渲染复杂物体(如环形圈)时为了阻止背面物体显示这很重要,为了检测三角形的正面,根据顶点定义的顺序从第1个到最后1个,伸出右手,拇指方向就是三角形的正面.跟8-1图所示的右手坐标系一样.xna默认行为是只绘制正面的三角形.你可以设置GraphicsDevice.RenderState.CullMode属性来改变此行为.
顶点,矩阵与3d变换
准备写3d程序之前,你需要明白更多的概念.3d顶点和矩阵是3d游戏创建中最重要的观念.
向量
与储存位置值一起,向量提供了需要助手方法来方便游戏创建.Vector3是3d游戏中最常用的向量,下面是它最重要的方法:
1 Vector3.Distance:返回2个点间距离.
2 Vector3.Add和Vector3.Subtract:向量的加法和减法.也可以在Vector3中使用+,-符号来执行加法和减法.
3 Vector3.Multiply和Vector3.Divide:2向量的乘除,及向量和float值的乘除.
4 Vector3.Clamp:将向量的分量约束在一个给定范围内-当定义光线和矩阵的值需要在一定范围内时很有用.
5 Vector3.SmoothStep:根据一个给定比重在2个向量间进行插值.
另外,Vector3提供了一些了特殊向量的快捷方式,如Vector.Zero表示0向量,Vector3.Up是(0,1,0),Vector3.Right(1,0,0),Vector2,Vector4也提供相同的方式.这些方法和快捷方式在定义矩阵和执行3d运算时很常用.
矩阵
矩阵是在3d世界中旋转,缩放和平移物体的基础.因为矩阵被用来定义任意3d变形,也被用来定义需要模拟投影的操作及根据摄影机的位置和朝向变换3d场景.
当写开始程序时,你将看到这些情况的例子.现在我们看一些变换矩阵完成一个简单的平移,并推出更复杂的操作.这将帮你理解3d编程中矩阵的重要性.
假设你准备三角形朝y轴向上移动三角形,如图8-11.

图8-11 在y轴上移动三角形
假设三角形的顶点如下:
Vertex X Y Z
1 50 10 0
2 70 10 0
3 55 25 0
为了沿y轴正方向平移40单元.你需要做的是在每个顶点的y值上加上40,这样每个顶点就有了一个新的坐标:
Vertex X Y Z
1 50 50 0
2 70 50 0
3 55 65 0
下面步骤可以达到相同的目标,将顶点转换为1行4列的矩阵,前3列是顶点的坐标,最后一列是1.现在将顶点矩阵和特殊的平移矩阵相乘.如图8-12

图8-12 3d顶点和矩阵的乘法
稍微解析下矩阵的乘法.为了计算结果,你需要取第一个矩阵行的每个值,和第2个矩阵的符合列的每个值.并将它们求和.前面的例子,计算如下:
x’ = (50 × 1) + (10 × 0) + (0 × 0) + (1 × 0) = 50
y’ = (50 × 0) + (10 × 1) + (0 × 0) + (1 × 40) = 50
z’ = (50 × 0) + (10 × 0) + (0 × 1) + (1 × 0) = 0
简单来看,你可以将平移矩阵的最后一行放到xyz位置的上面.还可以改变对角线上的1来进行缩放,值小于1时所需,大于1时放大.在矩阵的特殊位置上组合正弦和余弦来实现绕任意轴的旋转.
使用矩阵要处理最大的事是什么?一个最大的便利似乎你可以通过乘以变换矩阵来执行复杂的操作.这时你可以将结果矩阵应用到3d模型的每个顶点上,因此你可以用一个矩阵来执行模型上的所有操作,来代替计算每个顶点的变形.举例来说你需在3d场景中进行旋转,缩放,平移,并根据物体的移动执行新的操作.在这种情况下,用矩阵来代替每个顶点的计算,乘上变形矩阵你只需为每个顶点进行一次操作:将顶点乘以结果矩阵.
更好的是,所有的显卡都内置了矩阵乘法算法,因此矩阵乘法消耗很小.复杂的3d物体有很多个顶点,矩阵可以用很少的消耗来完成变换.
方便的是,执行3d变换时你不需明白所有的数学细节.所有的游戏库(从opengl到directx)都准备好了数学操作方法,xna也一样.Matrix类有很多可用操作如:
1 Matrix.CreateRotationX,Matrix.CreateRotationY和Matrix.CreateRotationZ:每个创建一个绕轴旋转的矩阵.
2 Matrix.Translation: 创建平移矩阵(一个或多个轴)
3 Matrix.Scale: 创建缩放矩阵(一个或多个轴)
4 Matrix.CreateLookAt:创建一个摄影机位置的视野矩阵,设置摄影机的位置,它的朝向和哪个方向是向上.
5 Matrix.CreatePerspectiveFieldOfView:创建一个使用透视投影的矩阵,需要设置视野角度(field of view),分辨率(被用于匹配3d投影到屏幕坐标,同时屏幕的宽/屏幕的高),和进裁面,远裁面,它们用来限制3d场景的哪部分将被渲染.图8-13可以更好的理解这些概念.同时也有2个额外的方法,CreatePerspectiveOffCenter和Createperspective,它们使用不用的参数来创建透视投影矩阵.

图8-13 透视投影定义
注意: 当创建投影矩阵时,xna方法需要你将分辨率作为参加传递.分辨率是必须的因为像素不是正方形(往往是长大于宽),如果分辨率不正确球就会变成一个蛋.与分辨率相关的是viewport,它是3d场景的一部分被用来渲染和显示场景.因为viewport是设备的一个属性,xna的分辨率往往被定义为device.Viewport.Width/device.Viewport.Height;
6 Matrix.CreateOrthograhics:创建一个使用正交投影的矩阵,此方法接收宽,高,近裁面,远裁面来定义正交投影,并有一个类似的方法:CreateOrthographicsOffCenter,用它来创建3d场景中心不是屏幕中心的正交投影.
本章中将使用到一部分方法,其他的将在后面使用到.
光线,摄影机...效果!
如果你准备为复杂游戏来定义摄影机和光线.xan简化了摄影机,光线和特效的定义,但你还是需要了解它们的建立.毕竟,没有摄影机和光线,我们如何来观看3d场景?本节从顶层来看看这些特性.
Xna的BasicEffect类实现了你所有的需求,也可以用于一些复杂游戏中.此类提供属性和方法让你来定义3d场景的最终细节.下面是重要的属性:
1 View: 视野矩阵,它定义了摄影机的位置和方向,通常用Matrix.CreateLookAt创建.
2 Projection: 投影矩阵用于将3d场景投影到2d屏幕坐标.通常使用Matrix.CreatePerspective来创建
3 World: 世界矩阵,将所有物体变换到3d场景.
4 LigthingEnabled: 若false,场景渲染使用基本光,表示所有的边亮度都是一样的.若true,将使用BasicEffect的光线属性来照亮场景.
5 AmbientLightColor: 定义环境光的颜色,对于所有物体都相同.当LightingEnabled为true是生效.
6 DirectionalLight0,DirectionalLight1,DirectionalLight2:定义渲染时效果所使用的3个直射光.每个直射光有 镜面高光颜色(光线的颜色有着完美的镜面反射),反射颜色(反射时的颜色)和光线的方向.只用当LightingEnabled为true是生效.
7 FogColor,FogStart,FogEnd:让你定义场景中的雾,在雾中的物体可以看到稠密的烟.你可以指定雾的颜色,雾开始和结束间的距离.
在这些属性中,一个重要的方式是BasicEffect的EnableDefaultLighting,它允许开启一个单独的白色直射光而不需要其他的任何配置.
下面的代码显示了正确的渲染场景所需步骤.指定effect为BasicEffect并进行适当的初始化:
effect.Begin();
foreach(EffectPass CurrentPass in effect.CurrentTechnique.Passes)
{
CurrentPass.Begin();
// 在这里使用effect进行场景的绘制
CurrentPass.End();
}
effect.End();
在代码中,你通知effect开始处理,遍历当前技术(这里是一个技术集合)所有的EffectPass对象.你还需要控制每个技术的pass的开始和结束.最后,你需要通知effect结束处理.
首先简单看一下,对于一个简单的渲染来说可能代码比较多.你需要记住的是BasicEffect类是Effect类的一个实例,Effect很有用,给予程序员需要的所有控制.比如使用自定义的着色器.
因为BasicEffect很简单,但也是一个Effect,你必须在你程序中使用前面的代码.但不必担心在程序中使用哪个技术,或该使用哪个pass.只需将其作为一个代码框架,因为BasicEffect的重点是通过设置相关属性给程序员提供方法.
下章将创建简单的3d游戏,你将学到更多的效果,第9章详解着色器,技术和pass.
在xna中绘制3d轴
为了演示到目前为止所谈论的概念,我们将创建一些代码来绘制3d轴,和每个线周围的x,y,z字母,这样你可以看到自己创建并修改的3d场景.
Xna中创建并渲染3d场景的步骤汇总如下:
定义将使用的顶点类型(位置+颜色+纹理等).
创建一个顶点数组并用顶点数据来填充
创建顶点缓冲并用之前的顶点数组来填充
用投影,视野矩阵,光影等定义将使用的效果
通知设备你将使用哪种类型的顶点
使用效果,用指定的基本体类型来画出顶点缓冲
如果什么地方不明白,在写代码之前先后头看下之前的讨论.
为了更好的组织你的代码,创建一个名为cls3Daxis的类.此类有一些与Game1.cs同名的方法.如LoadContent,UnloadContent,和Draw注意你可以从游戏主类中调用.
创建一个新类并包含私有属性:device,vertexBuffer和effect,同时创建一个接受并储存图形设备(graphcis device)的构造器.渲染操作时将用到,你还需要在类级处创建顶点缓冲和效果.这样你可以在LoadContent方法创建它们并在UnloadContent方法释放他们,初始化的代码如下:
class cls3DAxis
{
private GraphicsDevice device;
private VertexBuffer vertexBuffer;
private BasicEffect effect;
public cls3DAxis(GraphicsDevice graphicsDevice)
{
device = graphicsDevice;
}
}
顶点和顶点缓冲的代码
现在你将为此类写一个助手方法,命名为Create3Daxis,用来创建3d轴并填充顶点缓冲.这将完成前面所列的创建并渲染物体的前3步.
下面的代码显示了此方法的第一个版本,只是创建3条线用来显示3d轴,每个轴从负axisLength开始到正axisLength.如x轴,axisLength为1,你将画一条从(-1,0,0)到(1,0,0)的线.
private void Create3DAxis()
{
// Size of 3D axis
float axisLength = 1f;
// Number of vertices we'll use
int vertexCount = 6;
VertexPositionColor[] vertices = new VertexPositionColor[vertexCount];
// X axis
vertices[0] = new VertexPositionColor(new Vector3(-axisLength, 0.0f, 0.0f),
Color.White);
vertices[1] = new VertexPositionColor(new Vector3(axisLength, 0.0f, 0.0f),
Color.White);
// Y axis
vertices[2] = new VertexPositionColor(new Vector3(0.0f, -axisLength, 0.0f),
Color.White);
vertices[3] = new VertexPositionColor(new Vector3(0.0f, axisLength, 0.0f),
Color.White);
// Z axis
vertices[4] = new VertexPositionColor(new Vector3(0.0f, 0.0f, -axisLength),
Color.White);
vertices[5] = new VertexPositionColor(new Vector3(0.0f, 0.0f, axisLength),
Color.White);
// Fill the vertex buffer with the vertices
vertexBuffer = new VertexBuffer(device,
vertexCount * VertexPositionColor.SizeInBytes,
BufferUsage.WriteOnly);
vertexBuffer.SetData<VertexPositionColor>(vertices);
}
这个例子中,使用位置和颜色的顶点定义,并将所有的顶点定义为白色.当画出这些顶点是,将使用lineList类型,这样每个顶点对将组成一条线.
上面代码的最后一部分是你创建了顶点缓冲,传递图形设备,顶点缓冲的尺寸(顶点个数和每个顶点的尺寸VertexPositionColor.SizeInBytes乘积),和顶点的行为.(BufferUsage.WriteOnly表示之后写入的这些顶点在缓冲中不会执行任何更新.)
创建顶点之后,最后一行调用缓冲的SetData方法.它接收一个顶点数组和顶点格式(也可称为自定义的顶点格式,因为程序员可以自己定义顶点格式).
为了将字母加入到每个轴正部的边上,你将创建新的线段用来组合每个字母.此例中,最简单的方式绘制一个简单的线条这样你可以为每条线的每个字母计算顶点的位置.看8-14所示的距离,并用下面的代码来简单的比较,它显示了完整的Create3Daxis方法.确保你明白如何来画出x,yz字母.


图8-14 创建每个字母草图
如果对于图8-14的值是如何来的感到不解,答案很简单:测试和修改!如果你不喜欢这样字,你可以自行修改直到最近满意.
private void Create3DAxis()
{
// Size of 3D axis
float axisLength = 1f;
// Number of vertices we'll use
int vertexCount = 22;
VertexPositionColor[] vertices = new VertexPositionColor[vertexCount];
// X axis
vertices[0] = new VertexPositionColor(
new Vector3(-axisLength, 0.0f, 0.0f), Color.White);
vertices[1] = new VertexPositionColor(
new Vector3(axisLength, 0.0f, 0.0f), Color.White);
// Y axis
vertices[2] = new VertexPositionColor(
new Vector3(0.0f, -axisLength, 0.0f), Color.White);
vertices[3] = new VertexPositionColor(
new Vector3(0.0f, axisLength, 0.0f), Color.White);
// Z axis
vertices[4] = new VertexPositionColor(
new Vector3(0.0f, 0.0f, -axisLength), Color.White);
vertices[5] = new VertexPositionColor(
new Vector3(0.0f, 0.0f, axisLength), Color.White);
// "X" letter near X axis
vertices[6] = new VertexPositionColor(
new Vector3(axisLength - 0.1f, 0.05f, 0.0f), Color.White);
vertices[7] = new VertexPositionColor(
new Vector3(axisLength - 0.05f, 0.2f, 0.0f), Color.White);
vertices[8] = new VertexPositionColor(
new Vector3(axisLength - 0.05f, 0.05f, 0.0f), Color.White);
vertices[9] = new VertexPositionColor(
new Vector3(axisLength - 0.1f, 0.2f, 0.0f), Color.White);
// "Y" letter near Y axis
vertices[10] = new VertexPositionColor(
new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
vertices[11] = new VertexPositionColor(
new Vector3(0.075f, axisLength - 0.2f, 0.0f), Color.White);
vertices[12] = new VertexPositionColor(
new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
vertices[13] = new VertexPositionColor(
new Vector3(0.1f, axisLength - 0.05f, 0.0f), Color.White);
vertices[14] = new VertexPositionColor(
new Vector3(0.075f, axisLength - 0.125f, 0.0f), Color.White);
vertices[15] = new VertexPositionColor(
new Vector3(0.05f, axisLength - 0.05f, 0.0f), Color.White);// "Z" letter near Z axis
vertices[16] = new VertexPositionColor(
new Vector3(0.0f, 0.05f, axisLength - 0.1f), Color.White);
vertices[17] = new VertexPositionColor(
new Vector3(0.0f, 0.05f, axisLength - 0.05f), Color.White);
vertices[18] = new VertexPositionColor(
new Vector3(0.0f, 0.05f, axisLength - 0.1f), Color.White);
vertices[19] = new VertexPositionColor(
new Vector3(0.0f, 0.2f, axisLength - 0.05f), Color.White);
vertices[20] = new VertexPositionColor(
new Vector3(0.0f, 0.2f, axisLength - 0.1f), Color.White);
vertices[21] = new VertexPositionColor(
new Vector3(0.0f, 0.2f, axisLength - 0.05f), Color.White);
// Fill the vertex buffer with the vertices
vertexBuffer = new VertexBuffer(device,
vertexCount * VertexPositionColor.SizeInBytes,
ResourceUsage.WriteOnly,
ResourceManagementMode.Automatic);
vertexBuffer.SetData<VertexPositionColor>(vertices);
}
你将需要在LoadContent方法里创建代码来调用Create3Daxis,并在cls3Daxis类的UnloadContent方法中清空顶点缓冲,下面是代码:
public void LoadContent()
{
// Create the 3D axis
Create3DAxis();
}
public void UnloadContent()
{
if (vertexBuffer != null)
{
vertexBuffer.Dispose();
vertexBuffer = null;
}
}
代码的结束部分清空画出3d轴顶点所使用的内存资源.但还不能运行.你还需要写basicEffect来定义如何渲染,并在程序的主类Game1中包含cls3Daxis类的调用.
下面我们完成cls3Daxis类,设置effect属性.
写BasicEffect并渲染3d场景
之前学到的BasicEffect是xna提供帮助创建渲染3d场景的类.BasicEffect包含很多属性用来定义摄影机的位置,使用的投影和使用的光源等.
下面是完整的LoadContent方法代码,包含创建并配置简单的基本效果.所有用到的属性和方法在之前已经讲过;因此这时你可能最好回头看一下投影类型和视野,投影矩阵.
public void LoadContent()
{
// Create the effect that will be used to draw the axis
effect = new BasicEffect(device, null);
// Calculate the effect aspect ratio, projection, and view matrix
float aspectRatio = (float)device.Viewport.Width / device.Viewport.Height;
effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 2.0f, 2.0f), Vector3.Zero,
Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
aspectRatio, 1.0f, 10.0f);
effect.LightingEnabled = false;
// Create the 3D axis
Create3DAxis();
}
在CreateLookAt方法中,你创建的摄影机距原点(0,0,0)y轴方向高2个单元(0,2,0),z轴方向向外向内2个单元(在轴方向向外是正);盯着Zero向量(0,0,0),将Vector3.Up作为向上的方向.
创建的投影矩阵,视角为45,渲染的范围是1-10(z轴从-1到-10).
最终,你关闭光源,整个场景由默认光来渲染不会产生任何渐变和阴影.
UnloadContent方法也需要完善,包含effect对象销毁:
public void UnloadContent()
{
if (vertexBuffer != null)
{
vertexBuffer.Dispose();
vertexBuffer = null;
}if (effect != null)
{
effect.Dispose();
effect = null;
}
}
现在你已经设定好了顶点缓冲和效果,你需要写程cls3Daxis的Draw方法了,此方法将使用effect来渲染场景.
在下个代码段,配置设备以使用你定义的顶点格式(由位置和颜色定义).这时将顶点缓冲流发送到顶点缓冲,定义在流内的开始点(开始读取的第一个顶点)和每个顶点的尺寸.一旦顶点被配置过,你进入绘制循环,并为当前技术的每个pass调用device.DrawPrimitives方法(像之前解释的那样),开始绘制11条线(由22个顶点组成).
public void Draw()
{
// 创建绘制顶点时使用的顶点声明
device.VertexDeclaration = new VertexDeclaration(device,
VertexPositionColor.VertexElements);
// 设置顶点源
device.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionColor.SizeInBytes);

// 画出3d轴
effect.Begin();
foreach(EffectPass CurrentPass in effect.CurrentTechnique.Passes)
{
CurrentPass.Begin();
// 我们画出11条线,由22个顶点组成
device.DrawPrimitives(PrimitiveType.LineList, 0, 11);
CurrentPass.End();
}
effect.End();
}
主程序调用的代码
前面一节,你创建的cls3Daxis类,它提供了与xna程序同名的方法:LoadContent,UnloadContent和Draw.
为了使用此类,现在我们创建一个新的空xna窗口游戏工程.Game1是自动生成的.你需要定义一个cls3Daxis类的对象,初始化它并在Game1类中调用.更新的方法如下:
GraphicsDeviceManager graphics;
// 3D objects
cls3DAxis my3DAxis;
protected override void Initialize()
{
my3DAxis = new cls3DAxis(graphics.GraphicsDevice);
base.Initialize();
}
protected override void LoadContent()
{
// Create the 3D axis
my3DAxis.LoadGraphicsContent();
}
protected override void UnloadContent()
{
// Free the resources allocated for 3D drawing
my3DAxis.UnloadContent();
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// Draw the 3D axis
my3DAxis.Draw();
base.Draw(gameTime);
}
运行此代码的结果如图8-15所示,虽然不好看.你只看到了x,y轴,看起来不像3d.只是因为摄影机的位置与z轴对其了,所有这个轴就隐藏了,并且字母z也没有显示,因为它在摄影机的后面.
你可以简单调整cls3Daxis类中摄影机的位置,现在解释新的概念:世界矩阵.
世界矩阵,在谈论效果时说过,是Effect类的一个属性,包含所有场景物体变换.
图8-15 3d轴
让我们使用世界矩阵让3d轴旋转,这样你就可以看到一个旋转的3d场景.步骤如下:
在cls3Daxis中创建一个新的属性来储存当前的世界矩阵,默认的是identity矩阵(此矩阵没有任何变换):
public Matrix worldMatrix = Matrix.Identity;
在类的Draw方法加入一新行用来更新效果的World属性,这样效果将收到更新的矩阵并用来变换轴的渲染:
effect.World = worldMatrix;
Update方法加入一新行来更新cls3Daxis的worldMatrix属性.每个更新都增加世界的旋转角度:
My3DAxis.worldMatrix *= Matrix.CreateRotationY(0.01f) * Matrix.CreateRotationX(0.01f);
现在运行,将看到旋转的3d轴.如图8-16.
图8-16 旋转的3d轴
模型和网格
玩一下顶点及绘制基本体有点酷,并且它帮助你了解在场景中渲染3d模型时发生了什么.但如果你想创建一个复杂的3d对象,这种做法不是最好的选择.
在第1章,你学习了xna内容管道支持很多文件类型,包含3d物体定义的x和fbx文件.这些文件储存3d物体(称为3d模型或简称模型)的定义.
简单来说,你可以认为模型是一网格(可以被单独渲染)的层次,网格是互相连接的顶点的一个集合,并含有一些渲染信息.
Xna提供特殊类来操控模型和网格:Model和ModelMesh.这些类将运行你在游戏载入并操纵3d模型如建模工具Maya或3dmax创建的模型.这样你可以使用更酷更复杂的模型.这些模型可以储存额外的信息如颜色,纹理甚至xna可以使用的动画.
为了创建一个操作模型的程序,你必须首先要载入一个模型.
为了做到这,右键项目的Content目录,选添加->存在的内容,并选择一个x或fbx文件.本节中选择Cube.x文件,它是用directx软件开发包(sdk)定义的一个简单文件,可以从这里下载http://www.microsoft.com/directx
注意: 你可以使用其他的模型,但你需要根据模型来调整视野和投影矩阵;或执行一些变换以在场景中看到完成的模型.本章的例子完成之后,尝试载入别的模型并执行必要的模型,这样在玩转一个完整游戏前你将更好的理解视野,投票和变换矩阵.
一旦模型内容处于你的工程中,你必须在Game1的类级定义一个模型变量来持有此内容的引用.
Model myModel;
在LoadContent方法,你需要加入这行:
myModel = Content.Load<Model>("Cube");
最后,你必须在Draw方法中变量模型的所有网格并画出.虽然只有只用一个网格.
//变量模型的每个网格
foreach(ModelMesh mesh in myModel.meshes){
//画出当前的网格
mesh.Draw();
}
如果现在运行的话,将看到载入的模型和旋转的3d轴.如图8-17.
图8-17不是很酷,因为2个细节导致图形看起来不像立方体.摄影在立方体的垂直方向,这样看起来就像平面.还有光线被禁用,每个面看起来都一样.没有着色器来处理一个面和另一个面之间的不同颜色.为了解决此问题,你需要你需要将立方体旋转到一个更好的位置(并做一些缩放,这样就不会隐藏住轴)并为模型渲染加入光线.
图8-17 立方体的第一眼
还记得之前的BasicEffect类吗?使用BasicEffect可以应用物体的变换(通过World属性),设置Projection和View矩阵,并开启默认光源,如之前讨论的那样,你可以为cls3Daxis使用投影矩阵和视野矩阵.x,y轴方向分别旋转45度将使你更容易看到它的三个面.
记住,模型是由很多的网格组成.为了是效果来渲染3d模型,你需要遍历所有的网格并对他们使用效果.
另外,一个网格有一个效果集合,因此可以使用不同的效果来渲染网格的不同部分-对于复杂网格非常有用.因为一个网格有多个效果,你还需要进行第二次遍历,遍历每个网格的所有效果,确保你将网格的所有部分都应用的相同的效果.
在一个简单的模型(如立方体)中,你只有一个网格和一个效果,但是你还是需要使用通用的代码.
下面是效果创建和使用的最终的代码,将其放如到LoadContent方法中:
// Calculate the aspect ratio for the model
float aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width /
graphics.GraphicsDevice.Viewport.Height;
// Configure basic lighting and do a simple rotation for the model
// (so it can be seen onscreen)
foreach (ModelMesh mesh in myModel.Meshes)
foreach (BasicEffect effect in mesh.Effects)
{
// Rotate and make the model a little smaller (50%)
effect.World = Matrix.CreateScale(0.5f) *
Matrix.CreateRotationX(MathHelper.ToRadians(45.0f)) *
Matrix.CreateRotationY(MathHelper.ToRadians(45.0f));
// Set the projection matrix for the model
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
aspectRatio, 1.0f, 10.0f);
effect.View = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 3.0f),
Vector3.Zero, Vector3.Up);
effect.EnableDefaultLighting();
}
图 8-18 显示了新效果运行后的结果
注意: 你不需要同时载入模型使用的纹理.模型文件已经包含了所使用纹理的信息.因为这些信息包含了纹理存放路径,你需要知道这些路径并将纹理拷贝到相应的路径.你可以通过查看模型文件来获取纹理路径(如用文本编辑器)或将模型包含在工程中并编译它.当模型寻找路径时xna将显示内容管道路径错误信息.

图 1-18 旋转,缩放,有光照的立方体
总结
本章,你需也了3d图形编程的基础.虽然xna提供了很多内置的类和方法来减少程序的复杂性,但仍需要明白很多的概念.
继续之前确保你明白下面的所有概念:
1 什么的顶点及定义顶点可以使用的类型信息
2 什么顶点缓冲及如何使用
3 什么是视野矩阵及在xna中如何创建
4 什么是世界矩阵,如何在3d场景中执行操作
5 什么是模型和网格,在xna中如何载入并渲染他们
下一章,你将创建一个完整的游戏,因此你最好多练习及浏览这个概念.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gamebox1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值