|
|
深入Managed DirectX9(五) (续)
仅供个人学习之用,转载请注明作者
三维场景里的所有几何体都是由三角形组成,那么如何来渲染一个盒子或一个立方体呢?Well,每个立方体由六个正方形构成,而两个三角形可以构成一个正方形(呵呵,这个都要讲,看来老外的数学真的不行)实际上,我们只需要获得立方体8个顶点的坐标就可以了。添加代码:
CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[36];
// Front face
verts[0] = new CustomVertex.PositionColored(-1.0f, 1.0f, 1.0f, Color.Red.ToArgb());
verts[2] =````, verts[3] , verts[4], verts[5] =`````````
// Back face (remember this is facing *away* from the camera, so vertices should be clockwise order)
verts[6] = new CustomVertex.PositionColored(-1.0f, 1.0f, -1.0f, Color.Blue.ToArgb());
verts[7] , verts[8], verts[9], verts[10], verts[11]=````````
(注:详见附件中的源码,注意顶点申明的顺序)
正如前面提到的,盒子由12个三角形组成,每个三角形有三个顶点,构成一个顶点集合。还有几个需要修改的地方
vb = new VertexBuffer(typeof(CustomVertex.PositionColored),36,device,Usage.Dynamic | Usage.WriteOnly,CustomVertex.PositionColored.Format,Pool.Default);
evice.Transform.World = Matrix.RotationYawPitchRoll(angle/(float)Math.PI, angle/(float)Math.PI*2.0f, angle/(float)Math.PI);
device.DrawPrimitives(PrimitiveType.TriangleList,0,12);
这里最大的改变就是重新定义了顶点缓冲的大小。同时,我们也改变了盒子的旋转角度,让他转的更疯狂一点。最后改变所要渲染的图元数量。实际上,既然盒子完全是三维的,就没有必要看到他的背面。使用Direct3D里的默认剔除模式(逆时针):删除前面申明剔除模式的行。好了现在运行程序。
非常了不起,我们现在有了一个在屏幕中疯狂旋转的彩色盒子。但是如果需要渲染一系列盒子的话,没有人希望申明一系列顶点缓冲吧。有一个简单的方法可以做到这一点。
现在我们要肩并肩的绘制三个盒子。由于现在的摄像机设置让第一个盒子占慢了整个屏幕,我们需要把他摄像机稍稍往后移一点:
device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,18.0f),new Vector3(),new Vector3(0,1,0));
如你所见,我们只是把他往后移了一点点就可以看到更多场景。为了绘制更多的盒子,我们可以再次利用现有的顶点缓冲,只需要告诉Direct3D再次绘制同样的顶点就可以了。在device.DrawPrimitives之后添加一下代码:
device.Transform.World = Matrix.RotationYawPitchRoll(angle/(float)Math.PI, angle/(float)Math.PI/2.0f, angle/(float)Math.PI*4.0f) *Matrix.Translation(5.0f,0.0f,0.0f);
device.DrawPrimitives(PrimitiveType.TriangleList,0,12);
device.Transform.World = Matrix.RotationYawPitchRoll(angle/(float)Math.PI, angle/(float)Math.PI*4.0f, angle/(float)Math.PI/2.0f)*Matrix.Translation(-5.0f,0.0f,0.0f);
device.DrawPrimitives(PrimitiveType.TriangleList,0,12);
好了,这次我们又作了些什么呢?因为绘制第一个盒子时已经设置过VertexFormat属性,所以Direct3D知道我们将要绘制的顶点类型。同样,它也知道在哪里获得数据。那么绘制第二个盒子Direct3D还需要知道什么呢?只需要绘制的位置和绘制什么就可以了。
设置world transform可以把数据从局部坐标(object space)“移动”到世界坐标(world space),那么把什么用作变换矩阵呢?首先,使用类似SetupCamera函数里的方法;做一点点改变,让盒子以不同的角度旋转。然而 world transform里的另一半则是新内容:把一个Matrix.Translation与现有的旋转矩阵相乘。变换矩阵可以把空间中的一个点移动到另一个位置。我们的变换矩阵把第二个盒子向坐移动了5个单位,第三个盒子则向右移动了5个单位。
需要注意的是两个变换矩阵相乘得到的累积效果,是由相乘时矩阵的顺序来决定的。在这里,我们的先旋转盒子,然后再移动。如果先移动再选旋转,那么结果将有很大区别。记住变换时的顺序是很重要的。
为对象添加纹理
虽然使用颜色和灯光来渲染很有趣,但仅使用这样的技术,对象看起来并不真实。在非三维的程序里“纹理(texture)”通常用来描述对象的粗糙程度(roughness of an object)。三维场景里的纹理就是一张用来模拟几何图元纹理的2D位图。Direct3D可以同时为每一个图元渲染8层纹理,但现在,我们只解决每个图原一张纹理的情况。因为Direct3D使用普通的位图作为它的纹理格式,任何加载的位图都能当作纹理对象。 如何把2D的纹理映射到3D的对象上呢?绘制到场景中的每个对象都有一个可以在光栅化时把每个texel映射到屏幕特定位置的纹理坐标。textl是texture element的缩写,或者表示纹理中每个address的特定颜色值。Address可以想象为一个表示行和列的数字,分别称为U,V坐标。一般来说,这些值都是标量,取值范围从0.0到1.0 。(0,0)表示纹理的左上角,(1,1)表示右下角,中央的坐标为(0.5,0.5)。
为了使用纹理来渲染盒子,必须改变盒子的顶点格式,以及传递给图形卡的数据。使用纹理坐标来代替顶点数据中的“color”元素。虽然同时使用颜色和纹理都是有效的,当作为练习,我们只用纹理来定义图元的颜色。修改代码:
CustomVertex.PositionTextured[] verts = new Microsoft.DirectX.Direct3D.CustomVertex.PositionTextured[36];
verts[0] = new CustomVertex.PositionTextured(-1.0f,1.0f,1.0f,0.0f,0.0f);
vert[1]```````````(略)
显然,最大的改变就是储存顶点集合的数据类型。每个顶点中的最后两个float值储存了渲染图元所用的纹理U、V值。应为盒子每个面和纹理都为正方形,所以直接把纹理映射到每个面就可以了。注意,图元的左上角映射纹理的(0,0)textl,右下角映射到(1,1)textl。同时,我们还必须修改创建顶点缓冲的地方:
vb = new VertexBuffer(typeof(CustomVertex.PositionTextured),36,device,Usage.Dynamic|Usage.WriteOnly,CustomVertex.PositionTextured.Format,Pool.Default);
有如此多的重复代码,现在让我们用一个简单的方法来绘制盒子,添加一个函数完成这个任务。
private void DrawBox(float yaw, float potch, float roll, float x, float y,float z,texture t)
{
ngle += 0.01f;
device.Transform.World = Matrix.RotationYawPitchRoll(yaw,pitch,roll)*Matrix.Translation(x,y,z);
device.SetTexture(0,t);
device.DrawPrimitives(PrimitiveType.TriangleList,0,12);
}
前六个参数和我们之前使用的一样,最后一个新的参数表示渲染时所使用的纹理。我们还调用了SetTexture方告诉Direct3D渲染时使用哪个纹理。它的第一个参数是这张纹理的“层(stage)”。还记得先前我提过可以为一个图元渲染8层纹理吗,这个参数就是这些纹理的索引。因为只有一张纹理,我们使用第一个索引,0。同时应该注意到,我们修改了angle变量以及world transform,可以把SetupCamera里同样的几行删了。
在调用新方法渲染之前,先要申明一些将要使用的纹理,源码里附带了一个包含三张纹理的资源文件。分别为puck.bmp,ground.bmp,banana.bmp。添加如下代码:
private Texture tex = null;
private Texture tex1 = null;
private Texture tex2 = null;
这是我们即将使用的三张纹理。但是,还需要真正“装配”起作为资源嵌入的三张位图。在创建顶点缓冲之后添加如下代码:
tex = new Texture(device,new Bitmap(this.GetType(),"puck.bmp"),0,Pool.Managed);
tex1 =•••••(略)
Texture的构造函数接受四个参数。第一个是用于渲染纹理的device。场景里所有的资源(纹理,顶点缓冲,等等)都要和device发生联系。下一个参数Bitmap是我们获取纹理数据的地方。第三个参数Usage,先前已经讨论过它。最后一个参数是储存纹理的内存池位置。方便起见,现在使用托管的内存池。Texture其他的构造函数包括:
(注:此处略去一个在DX9c中已经不存在的构造函数)
public Texture( Device device, int width, int height, int numLevels,Usage usage, Format format, Pool pool);
public Texture( Device device, Stream data, Usage usage,Pool pool);
第一个方法允许我们从“空白”开始创建一张纹理,可以指定它的高度、宽度,细节程度(number of levels of detail)。最后一个和我们使用的很相似,但使用流而不是位图对象。当然,流中的数据要能被转换为位图。TextureLoad类中还有一些关于加载位图的有趣方法,我们将在下一张讨论。
好了,现在已经定义且加载了位图,是更新绘图代码的时候了,使用如下代码代替先前的绘图代码;
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f, tex);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f, angle / (float)Math.PI * 4.0f, 5.0f, 0.0f, 0.0f, tex1);
DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f, angle / (float)Math.PI / 2.0f, -5.0f, 0.0f, 0.0f, tex2);
~~~~~~~~~~~~~~~~~~第三章完~~~~~~~~~~~~~~~~~~~
下一章我们将要讨论各种图元类型,index buffer以及depth buffer
附上源码
在源码中,作者一共渲染了9个盒子,我删除其中的6个后,剩下三个的旋转速度却明显减慢了
,没想明白············· |
|