第一章 Introducing Direct 3D
翻译:clayman
创建设备
Device 类是 DirectX 里的所有绘图操作所必须的。可以把这个类假想为真实的图形卡。场景里所有其他图形对象都依赖于 device 。你的计算机里可以有一个到几个 device ,在 Mnaged DirctX3D 里,你可以控制任意多个 device 。
Device 共有三个构造函数,我们现在只讨论其中的一个,但会在后边的内容里讨论其他的。先来看看具有如下函数签名的构造函数:
** public Device(int adapter,DeviceType deviceType,Control renderWindow,CreateFlags behaviorFlags, PresentParameters[] presentationParameters); **
** (构造函数的第二种重载类似于上边这个,但它接受来自非托管(或者非 ** ** windows form ** ** )的窗口句柄作为 ** ** renderWindow ** ** 。而只接受一个 ** ** IntPtr ** ** 参数的重载是非托管 ** ** com ** ** 组建指向 ** ** Idirect3Ddevice9 ** ** 的接口。当你的代码需要和非托管的程序协作时则应用它) ** ** **
好了,这些参数是什么意思,以及我们怎样来使用呢?呵呵,参数 adapter 表示我们将要使用哪个物理图形卡。计算机里的所有图形卡都有一个唯一的适配器标识符(通常是 0 到你的图形卡数量- 1 ),默认的显卡总是标识为 0 的图形卡。
下一个参数, DeviceType ,告诉了 DirectX3D 你要创建哪种类型的 device 。这里最常用的值是 DeviceType.Hardware ,表示你将创建一个硬件设备。另一个选项 DeviceType.Reference ,这种设备允许你使用“参考光栅器”( reference rasterizer ),所有的效果由 DirectX3D 运行时来实现,以很慢、很慢、很慢的速度运行 ^_^ 。应该仅在调试或测试你的显卡不支持的特性时使用这个选项。
** (注意参考光栅器只包含在 ** ** DirectX SDK ** ** 里, ** ** so DirectX ** ** 运行时是不能使用这个特性的。最后一个为 ** ** DeviceType.Software ** ** 的值允许使用用户自定义的软件光栅器( ** ** custom software rasterizer ** ** )在不确定是否有这样一个光栅器存在时,忽略这个选项吧 ** ** ^_^ ** ** 。) ** ** **
rendrWindow 表示把设备绑定到的窗口。因为 windows form 控件类都包含了一个窗口句柄( windows handle ),所以很容易把一个确定的类作为渲染窗口。可以使用 form 、 panel 或其他任意的控件作为这个参数的值。但现在,我们只用 form 。
下一个参数用来描述设备创建之后的行为。大部分 CreateFlags 枚举的成员都能组合起来使用,使设备具有多种行为。但有一些 flag 是相互排斥的,稍后讨论它们。我们现在只使用 SoftwareVertexProcessing 标志。这个标志适合于所有顶点处理都用 CPU 计算的情况。因此,这自然比所有点都用 GPU 处理要慢,因为我们不确定你的显卡是否支持所有特性。 So ,安全第一,假设你的 CPU 能完成现在的任务。
最后一个参数,它表示你的设备把数据呈现到显示器的方式。 Presentation Parameter 类的外观都可以由这个类来控制。我们过后再来深入讨论它的构造函数,现在,我们只关心“ Windowed ”成员和“ SwapEffect ”成员。
Windowed 成员是一个布尔类型的值,决定设备是全屏还是窗口模式。
SwapEffect 成员用于控制缓存交换的行为。如果选择了 SwapEffect.Flip ,运行时会创建额外的后备缓冲( back buffer ),并且在显示时拷贝 front buffer 。 SwapEffect.Copy 与 Flip 相似,但要求你把后备缓冲设为 1 。我们将要选择的 SwaoEffect.Discard ,如果缓冲没有准备好被显示,则会丢弃缓冲中的内容( which simply discards the contents of the buffer if it isn’t ready to be presented )。
学了这么多,现在来创建一个设备吧。回到代码上来,首先为我们的程序将创建一个 device 对象:
(代码略,参见 DirectX sdk Tutorial 1: Create a Device )
现在让我们来重写 Paint ()函数:
** protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) **
** { **
** device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f , 0); **
** device.Present(); **
** } **
** ** 我们使用 Clear ()方法把窗口填充为实心的颜色。它的第一个参数指定了我们要填充的对象;在例子里,我们填充的即是目标窗口。稍后再来讨论 ClearFlags 枚举的其它成员。第二个参数是我们所要填充的颜色。其他的两个参数先暂时忽略。在 device 被填充之后,我们必须更新显示: Present 方法会为我们完成这个任务。这个方法也有几个重载的类型;上边使用的方法会显示 device 的整个区域。同样稍后再讨论。
看的有些枯燥了吗,好吧,现在我们来真正绘制一些图形
三维图形世界里最基本的图形就是三角形。使用足够的三角,我们可以呈现出任何东西,甚是是平滑的曲面。没有什么比画一个简单的三角形更好的了。为了使过程尽可能的简单,我们先避开“ world space ”以及各种变换(当然,我们马上就会提到他们),使用屏幕坐标来绘制一个简单的三角。再绘制我们迷人的三角前,我们必须做 2 件事。 1 ,需要一些数据结构来保存三角的信息。 2 ,告诉 device 来绘制它。
很幸运, DirectX 已经有这样一个数据结构来保存三角了。 Direct3D 名称空间里叫做 CustomVertex 的类可以用来储存大多数 Direct3D 中用到的“顶点格式”数据结构( vertex format )。
一个顶点格式结构把数据保存为一种 DirectX3D 认识并可以使用的格式。我们将讨论很多这种结构,但先看看我们即将用来创建三角的 TransformedColored 结构。这个结构告诉 DirectX3D 运行时我们的三角不需要进行坐标变换(比如旋转或移动),因为我们已经指定了使用屏幕坐标系。它也包含了每一个点(顶点)的颜色的信息。回到重写的 OnPaint 方法添加如下代码:
**CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3]; **
** Verts[0].SetPosition(new Vector4(this.Width/ 2.0f , 50.0f , 0.5f , 1.0f ); **
** Verts[0].Color = System.Drawing.Color.Aqua.ToArgb(); **
** Verts[1]````````` **
** Verts[2]````````` **
( 参见 DirectX sdk Tutorial 2: Rendering Vertices )
数组里的每一个元素表示三角的一个顶点,所以我们创建了 3 个元素。然后使用新创建的 Vector4 结构为每一个成员调用 SetPositin 方法。变换过的顶点坐标包含了在屏幕上 x 和 y 的坐标(相对于屏幕的( 0 , 0 )点而言),当然也包括 z 坐标和 rhw 成员( reciprocal of homogenous w 三维齐次坐标)。先忽略后边两个参数。 Vector4 结构(注: Vector4 其实就是( x,y,z,w )经过变换后成为( x/w,y/w,z/w ))是保存这种信息最方便的方式。然后我们设置了点的颜色。注意,我们使用了标准颜色的 ToArgb 方法。 DirectX3D 假设所接收的颜色为 32 位 int 。
既然有了数据就可以告诉 DirectX 我们需要绘制这个三角形,并且绘制它。在重写的 OnPaint 里添加如下代码
**device.BeginScene(); **
** device.VertexFormat = CustomVertex.TransformedColored.Format; **
** device. ** ** DrawUserPrimitives (PrimitiveType.TriangleList,1 ** ** , ** ** verts); ** ** 注意和 ** ** sdk ** ** 中的示例有区别 ** ** **
** device.EndScene(); **
** ** 好了,这几行代码是什么意思呢?其实很简单。 BefinScene 方法告诉 DirectX3D 我们即将绘制一些东西,为绘制做好准备。现在我们已经告诉了 DirectX3D 要绘制一些东西,接下来就必须告诉它画什么。这就是 VertexFormat 属性的作用。它决定了 DirectX3D 运行时使用哪种“固定功能管道”( fixed function pipline )格式。在我们的例子里使用变换过的,着色过的顶点管道。
不用担心你现在不明白确定的功能管道是什么意思,我们会很快来讨论它。
DrawUserPrimitives 函数是真正发生绘图的地方。 So ,他的参数是什么意思呢?第一个参数是我们要绘制的初等几何体的类型。有很多种可用的类型, but now ,我们只是画一系列的三角形。所以选择了 PrimitiveType.TriangleList 类型。第二个参数是我们要绘制的三角形的数量。对于一个三角形的集合来说,这个值应该是你的顶点数量除以 3 。我们只画一个三角,所以设为 1 。最后一个参数则是 DirectX3D 用来绘图的数据。最后一个 EndSence 方法通知 DirectX3D 我们不再绘图了。你必须再每次调用 BeginSence 之后都调用这个方法。
如果现在编译运行程序,你会发现移动或重置窗口大小之后,并不会更新显示。原因是当我们需要重绘整个窗口时, Windows 并不会每一次都计算窗口的收缩情况。因此,你只是移除了显示过的数据,但并没有删除已经显示的内容。很幸运,有个简单的方法解决这个问题,我们可以告诉 Windows 窗口总是需要被整个的重绘。在 OnPaint 的最后加上一下代码:
this.Invalidate();
呵呵,现在再来试试看,哦,看起来我们破坏了程序!现在只能显示一片空白了,并且我们的三角还在不停的闪烁,尤其是当调整窗口大小时。我们都干了些什么呢?原来“聪明”的 Windows 总是尝试在 Invalidate() 方法后来绘制当前的窗口(即空白的这个窗口)。在我们的 OnPaint 方法之外还存在其他的绘制过程!能容易的通过改变窗口的“ style ”属性来解决。在构造函数里加上如下代码
** this.SetStyle(ControlStyles.AllPaintingInWmPaint | ConstolStyles.Opaque, true); **
哦~,好了,终于 erying works as expected 。我们所做的就是告诉 Windows 一切绘图过程都在 OnPaint 里完成。
** 三维化三角形 ** ** **
** ** 再看看我们的程序,它看起来并不那么“三维”。而且我们所做的都能用 GDI +轻易完成。 So ,我们应该怎样在 3 维空间里绘图,并且给人留下深刻的印象呢?实际上,简单的修改就能达到这样的效果。
如果你还记得,先前在我们创建第一个三角形的时候,我们使用了一个叫做“经过变换的”( transformed )坐标系统。这种坐标是显示器的屏幕区所使用的坐标,也是最容易定义的。如果我们使用未变换过的坐标系统会怎样呢?实际上,未变换过的坐标系统被广泛的用于现代游戏场景。
与屏幕坐标( screem space )相比我们定义这些坐标时,还应在世界坐标( world space )里定义每一个顶点。你可以把世界坐标设想为一个无限大的三维笛卡儿坐标。你可以把你的对象放到这个“世界”的任意位置。现在来修改我们的程序,绘制一个未经过世界坐标变换的三角形。
首先使用未变换顶点格式类型中的一种来改变三角形的数据。在这里我们只关心顶点的位置,以及颜色,因此使用 CustomVertex.PositionColored 。
** CustomVertex.positionColored[] verts = new CustomVertex. positionColored[3]; **
** Verts[0].SetPosition(new Vector3( 0.0f , 1.0f , 1.0f )); **
** Verts[0].Color = System.Drawing.Color.Aqua.ToArgb(); **
** Verts[1]````````` **
** Verts[2]````````` **
( 参见 DirectX sdk Tutorial 3: Using Matrices )
同样改变 VertexFormat 属性:
** device.VertexFormat = CustomVertex.PositionColored.Format; **
** ** 好了,现在运行程序:什么也没有发生,仅获得一个填充过的窗口。在讨论为什么之前,来看看我们作了些什么。如你看到的,我们选择了 PositonColored 结构来保存数据。这个结构用世界坐标保存了顶点的位置,也保存了它的颜色。因为为顶点是没有变换过的,所以我们使用 Vector3 类来代替 Vector4 类,没有变换过的顶点是没有 rhw 值的。 Vector3 结构的成员直接映射为世界坐标系里 x,y,z 的值。同时,我们需要确定 DirectX3D 知道所做的改变,所以我们通过更新 VertexFormat 属性来让固定功能管道使用新的未变换但填充过颜色的顶点。
<SPAN style="mso-tab-count: