Direct3D教材.docx
- 文档编号:24051090
- 上传时间:2023-05-23
- 格式:DOCX
- 页数:211
- 大小:3.36MB
Direct3D教材.docx
《Direct3D教材.docx》由会员分享,可在线阅读,更多相关《Direct3D教材.docx(211页珍藏版)》请在冰豆网上搜索。
Direct3D教材
第一章Direct3D入门
创建设备
Device类是DirectX里的所有绘图操作所必须的。
可以把这个类假想为真实的图形卡。
场景里所有图形对象都依赖于device。
一台计算机里可以有一个到几个device,在MnagedDirctX3D里,你可以控制任意多个device。
Device共有三个构造函数,我们现在只讨论其中的一个,但会在后边的内容里讨论其他的。
先来看看具有如下函数签名的构造
函数:
publicDevice(intadapter,DeviceTypedeviceType,ControlrenderWindow,CreateFlagsbehaviorFlags,PresentParameters[]
presentationParameters);
(构造函数的第二种重载类似于上边这个,但它接受来自非托管(或者非windowsform)的窗口句柄作为renderWindow。
而
只接受一个IntPtr参数的重载是非托管com组建指向Idirect3Ddevice9的接口。
当你的代码需要和非托管的程序协作时则应用它)
好了,这些参数是什么意思,以及我们怎样来使用呢?
呵呵,参数adapter表示我们将要使用哪个物理图形卡。
计算机里的所
有图形卡都有一个唯一的适配器标识符(通常是0到你的图形卡数量-1),默认的显卡总是标识为0的图形卡。
下一个参数,DeviceType,告诉了DirectX3D你要创建哪种类型的device。
这里最常用的值是DeviceType.Hardware,表示你将
创建一个硬件设备。
另一个选项DeviceType.Reference,这种设备允许你使用“参考光栅器”(referencerasterizer),所有的效果由
DirectX3D运行时来实现,以很慢、很慢、很慢的速度运行^_^。
应该仅在调试或测试你的显卡不支持的特性时使用这个选项。
(注意参考光栅器只包含在DirectXSDK里,soDirectX运行时是不能使用这个特性的。
最后一个为DeviceType.Software的值
允许使用用户自定义的软件光栅器(customsoftwarerasterizer)在不确定是否有这样一个光栅器存在时,忽略这个选项吧^_^。
)
rendrWindow表示把设备绑定到的窗口。
因为windowsform控件类都包含了一个窗口句柄(windowshandle),所以很容易把一
个确定的类作为渲染窗口。
可以使用form、panel或其他任意的控件作为这个参数的值。
但现在,我们只用form。
下一个参数用来描述设备创建之后的行为。
大部分CreateFlags枚举的成员都能组合起来使用,使设备具有多种行为。
但有一
些flag是相互排斥的,稍后讨论它们。
我们现在只使用SoftwareVertexProcessing标志。
这个标志适合于所有顶点处理都用CPU计
算的情况。
因此,这自然比所有点都用GPU处理要慢,因为我们不确定你的显卡是否支持所有特性。
So,安全第一,假设你的CPU
能完成现在的任务。
最后一个参数,它表示你的设备把数据呈现到显示器的方式。
PresentationParameter类的外观都可以由这个类来控制。
我们过
后再来深入讨论它的构造函数,现在,我们只关心“Windowed”成员和“SwapEffect”成员。
Windowed成员是一个布尔类型的值,决定设备是全屏还是窗口模式。
SwapEffect成员用于控制缓存交换的行为。
如果选择了SwapEffect.Flip,运行时会创建额外的后备缓冲(backbuffer),并且在
显示时拷贝frontbuffer。
SwapEffect.Copy与Flip相似,但要求你把后备缓冲设为1。
我们将要选择的SwaoEffect.Discard,如果缓冲
没有准备好被显示,则会丢弃缓冲中的内容(whichsimplydiscardsthecontentsofthebufferifitisn’treadytobepresented)。
学了这么多,现在来创建一个设备吧。
回到代码上,首先为我们的程序创建一个device对象:
(代码略,参见DirectXsdkTutorial1:
CreateaDevice)
现在让我们来重写Paint()函数:
protectedoverridevoidOnPaint(System.Windows.Forms.PaintEventArgse)
{
device.Clear(ClearFlags.Target,System.Drawing.Color.Blue,1.0f,0);
device.Present();
}
我们使用Clear()方法把窗口填充为实心的颜色。
它的第一个参数指定了我们要填充的对象;在例子里,我们填充的即是目
标窗口。
稍后再来讨论ClearFlags枚举的其它成员。
第二个参数是我们所要填充的颜色。
其他的两个参数先暂时忽略。
在device被
填充之后,我们必须更新显示:
Present方法会为我们完成这个任务。
这个方法也有几个重载的类型;上边使用的方法会显示device
的整个区域。
同样稍后再讨论。
看的有些枯燥了吗,好吧,现在来真正绘制一些图形
三维图形世界里最基本的图形就是三角形。
使用足够的三角,我们可以呈现出任何东西,甚是是平滑的曲面。
没有什么比画一
个简单的三角形更好的了。
为了使过程尽可能的简单,我们先避开“worldspace”以及各种变换(当然,我们马上就会提到他们),
使用屏幕坐标来绘制一个简单的三角。
再绘制我们迷人的三角前,我们必须做2件事。
1,需要一些数据结构来保存三角的信息。
2,
告诉device来绘制它。
很幸运,DirectX已经有这样一个数据结构来保存三角了。
Direct3D名称空间里叫做CustomVertex的类可以用来储存大多数
Direct3D中用到的“顶点格式”数据结构(vertexformat)。
一个顶点格式结构把数据保存为一种DirectX3D认识并可以使用的格式。
我们将讨论很多这种结构,但先看看我们即将用来创建三角
的TransformedColored结构。
这个结构告诉DirectX3D运行时我们的三角不需要进行坐标变换(比如旋转或移动),因为我们已经指
定了使用屏幕坐标系。
它也包含了每一个点(顶点)的颜色的信息。
回到重写的OnPaint方法添加如下代码:
CustomVertex.TransformedColored[]verts=newCustomVertex.TransformedColored[3];
Verts[0].SetPosition(newVector4(this.Width/2.0f,50.0f,0.5f,1.0f);
Verts[0].Color=System.Drawing.Color.Aqua.ToArgb();
Verts[1]`````````
Verts[2]`````````
(参见DirectXsdkTutorial2:
RenderingVertices)
数组里的每一个元素表示三角的一个顶点,所以我们创建了3个元素。
然后使用新创建的Vector4结构为每一个成员调用SetPositin
方法。
变换过的顶点坐标包含了在屏幕上x和y的坐标(相对于屏幕的(0,0)点而言),当然也包括z坐标和rhw成员(reciprocalof
homogenousw三维齐次坐标)。
先忽略后边两个参数。
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);
device.EndScene();
好了,这几行代码是什么意思呢?
其实很简单。
BeginScene方法告诉DirectX3D我们即将绘制一些东西,为绘制做好准备。
现
在已经告诉了DirectX3D要绘制一些东西,接下来就必须告诉它画什么。
这就是VertexFormat属性的作用。
它决定了DirectX3D运行时
使用哪种“固定功能管道”(fixedfunctionpipline)格式。
在我们的例子里使用变换过的,着色过的顶点管道。
不用担心你现在不明白确定的功能管道是什么意思,我们会很快来讨论它。
DrawUserPrimitives函数是真正发生绘图的地方。
So,他的参数是什么意思呢?
第一个参数是我们要绘制的初等几何体的类型。
有很多种可用的类型,butnow,我们只是画一系列的三角形。
所以选择了PrimitiveType.TriangleList类型。
第二个参数是我们要绘制
的三角形的数量。
对于一个三角形的集合来说,这个值应该是你的顶点数量除以3。
我们只画一个三角,所以设为1。
最后一个参数
则是DirectX3D用来绘图的数据。
最后一个EndSence方法通知DirectX3D我们不再绘图了。
你必须再每次调用BeginSence之后都调用
这个方法。
如果现在编译运行程序,你会发现移动或重置窗口大小之后,并不会更新显示。
原因是当我们需要重绘整个窗口时,Windows
并不会每一次都计算窗口的收缩情况。
因此,你只是移除了显示过的数据,但并没有删除已经显示的内容。
很幸运,有个简单的方
法解决这个问题,我们可以告诉Windows窗口总是需要被整个的重绘。
在OnPaint的最后加上一下代码:
this.Invalidate();
呵呵,再试试看,哦,看起来我们破坏了程序!
现在只能显示一片空白了,并且我们的三角还在不停的闪烁,尤其是当调整窗
口大小时。
我们都干了些什么呢?
原来“聪明”的Windows总是尝试在Invalidate()方法后来绘制当前的窗口(即空白的这个窗口)。
在我们的OnPaint方法之外还存在其他的绘制过程!
能容易的通过改变窗口的“style”属性来解决。
在构造函数里加上如下代码
this.SetStyle(ControlStyles.AllPaintingInWmPaint|ConstolStyles.Opaque,true);
哦~,好了,终于eryingworksasexpected。
我们所做的就是告诉Windows一切绘图过程都在OnPaint里完成。
三维化三角形
再看看我们的程序,它看起来并不那么“三维”。
而且我们所做的都能用GDI+轻易完成。
So,我们应该怎样在3维空间里绘图,
并且给人留下深刻的印象呢?
实际上,简单的修改就能达到这样的效果。
如果你还记得,先前在我们创建第一个三角形的时候,我们使用了一个叫做“经过变换的”(transformed)坐标系统。
这种坐
标是显示器的屏幕区所使用的坐标,也是最容易定义的。
如果我们使用未变换过的坐标系统会怎样呢?
实际上,未变换过的坐标系
统被广泛的用于现代游戏场景。
与屏幕坐标(screemspace)相比我们定义这些坐标时,还应在世界坐标(worldspace)里定义每一个顶点。
你可以把世界坐标
设想为一个无限大的三维笛卡儿坐标。
可以把你的对象放到这个“世界”的任意位置。
现在来修改我们的程序,绘制一个未经过世
界坐标变换的三角形。
首先使用未变换顶点格式类型中的一种来改变三角形的数据。
在这里我们只关心顶点的位置,以及颜色,因此使用
CustomVertex.PositionColored。
CustomVertex.positionColored[]verts=newCustomVertex.positionColored[3];
Verts[0].SetPosition(newVector3(0.0f,1.0f,1.0f));
Verts[0].Color=System.Drawing.Color.Aqua.ToArgb();
Verts[1].SetPosition(newVector3(-1.0f,-1.0f,1.0f));
Verts[1].Color=System.Drawing.Color.Black.ToArgb();
Verts[2].SetPosition(newVector3(1.0f,-1.0f,1.0f));
Verts[2].Color=System.Drawing.Color.Purple.ToArgb();
(参见DirectXsdkTutorial3:
UsingMatrices)
同样改变VertexFormat属性:
device.VertexFormat=CustomVertex.PositionColored.Format;
好了,现在运行程序:
什么也没有发生,仅获得一个填充过的窗口。
在讨论为什么之前,来看看我们作了些什么。
如你看到的,
我们选择了PositonColored结构来保存数据。
这个结构用世界坐标保存了顶点的位置,也保存了它的颜色。
因为为顶点是没有变换过
的,所以我们使用Vector3类来代替Vector4类,没有变换过的顶点是没有rhw值的。
Vector3结构的成员直接映射为世界坐标系里x,y,z
的值。
同时,我们需要确定DirectX3D知道所做的改变,所以我们通过更新VertexFormat属性来让固定功能管道使用新的未变换但填
充过颜色的顶点。
So,为什么程序运行时没有正确的显示呢?
问题在于,我们只是在世界坐标里绘图,但并没有给DirectX3D任何关于如何来显
示它们的信息。
我们需要为场景添加一个摄像机来确定如何观看我们的顶点。
在经过变换的坐标系统里不需要摄像机的原因是:
DirectX3D已经知道在屏幕的哪个位置来显示顶点。
在device上通过两个不同的变换来控制摄像机。
每一种变换都被定义为一个4×4的矩阵传递给DirectX3D。
(eachtransformis
definedasa4*4matrixthatyoucanpassintoDirectX3D)
投影变换定义了场景被怎样投影到显示器。
最简单的产生投影矩阵的方法就是使用Matrix类的PerspectiveFovLH方法。
它将会
使用左手坐标系创建一个正对场景的透视投影变换。
(关于左右手坐标系的详细内容请参见sdk,或你的高等数学、高等物理教材^_^)
DirectX3D通常使用左手坐标系。
以下是投影函数的签名:
publicstaticMatrixPerspectiveFovLH(floatfieldOfViewY,floataspectRatio,floatznearPlane,floatzfarPlane);
投影变换描绘了场景的视见体(注:
即可见部分)。
视见体是由可视角度和前裁剪面(NearPlane)与后裁剪面(FarPlane)
定义的一个平截头体(注:
比如四棱锥横截面与底面之间的部分,上帝保佑,你还记得高中几何),在这个平截头体之内的即是可见
部分。
函数签名里的nearPlane和farPlane两个参数,描绘了锥体的边界:
farPlane就是锥体的底面,而nearPlane则是横截面。
fieldOfView参数描绘了锥体的角度。
aspectRatio类似于电视的高宽比,比如,宽银幕电视的高宽比是1.85。
你可以用可视区域的宽度
比上高度得出这个值。
DirectX3D只绘制在这个平截头体中的物体。
既然我没从来没有进行过投影变换,也就根本不存在一个视见体,因此DirectX3D什么也没有绘制。
但是,就算我们进行了投
影变换,我们还没有进行设置摄像机信息的观察变换viewtransform。
可以用以下函数完成这个任务:
publicstaticMatrixLookAtLH(MatrixpOut,Vector3cameraPosition,Vector3cameraTarget,Vector3cameraUpVector);
仅仅通过各变量的名字你就可以知道如何使用这个函数。
其中三个是用来描述摄像机的属性:
它的位置、它观察点的位置以及
一个被参考为“up”的方向。
有了投影变换和观察变换的帮助,DirectX3D已经有足够的信息来绘制三角了。
添加代码:
(参见DirectX
sdkTutorial3:
UsingMatrices中的SetupMatrices()函数)
再运行一次试试,哦,我们已经有一个三角了,不过它完全是黑色的!
问题在哪呢?
在没有经过变换的环境里,DirectX3D默
认使用灯光来计算场景中几何体每一个像素的颜色,我们没有定义灯光,也没有额外的光照在三角上,So,它完全是黑色的。
既然我们已经为每一个点定义过了颜色,现在,可以安全并且简单的把场景里的灯关了。
加上如下代码:
dev.RenderState.Lighting=false;
再试一次,终于,我们回到了未变换坐标前的样子。
做了这么多改变到底有什么好处呢?
和在屏幕上直接绘制相比最大的好处
就在于获得了一个三维空间里的三角形——迈向伟大三维作品的第一步!
^_^
既然有了三维空间里的三角,怎样做才能让他看起来确实是一个空间里的三角呢?
最简单的事就是让它旋转起来。
如何来做
呢?
很简单,只需更改世界坐标就可以了。
Device的世界坐标变换会把每一个用局部坐标定义的顶点位置转换为用世界坐标定义的顶点位置。
(theworldtransformon
thedeviceisusedtotransformtheobjectsbeingdrawnfrommodelspace,whiceiswhereeachvertexisdefinedwith
respecttothemodel,toworldspace,whereeachvertexisactuallyplacedintheworld.)Matrix对象的很多方法能完成
这种变换:
device.Transform.World=Matrix.RotationZ((float)Math.PI/6.0f);
它告诉DirectX3D除非指定一个新的世界坐标变换,否则在这段代码之后所有绘制的对象都将进行这种变换。
以上的世界坐标
变换是根据所给的弧度旋转Z轴。
注意这里的参数必须是弧度而不是角度。
有规律的改变参数值就能让三角形平滑的转动起来了(以
下代码略,参考sdk中的示例)。
我们旋转的三角并不能给人留下深刻的印象。
来试试让他变得特别一点,同时旋转多个轴。
很幸运,恰好有这样一个方法,好
了,更新代码:
device.Transform.World=Matrix.RotationAxis(newVector3(angle/((float)Math.PI*2.0f),angle/((float)Math.PI*4.0f),
angle/((float)Math.PI*6.0f)),angle/((float)Math.PI);
这里使用了RotationAxis函数,通过这个函数,我们先定义了旋转轴,并在每一维上用一个简单的式子不停改变轴的位置,然
后再传入三角形围绕着轴旋转的角度,就像先前做的一样。
再次运行程序,哦,我们确实得到了一个围绕着一条旋转的轴转动的三角形,但似乎三角形会有规律的消失一阵,然后再显示
出来。
好了,还记得我们先前提到的背面剔除(backfaceculling)吗?
这就是背面剔除在起作用的最好例子。
当DirectX3D渲染物
体的时候,如果它发现某一个面没有对着摄相机,就不会绘制它,这就叫做背面剔除。
那么程序在运行时,又是怎样知道某一个特
定的几何面是否对着摄像机呢?
快速看看DirectX3D中的裁剪选项或许能给你一点提示。
三种可用的剔除选项分别是:
none,
clockwise(顺时针)以及counterclockwise(逆时针)。
在clockwise以及counterclockwise的情况下,当简单几何体的顶点排列顺序与剔
除模式相反时,它就不会被绘制。
看看我们的三角形,它的顶点是按逆时针顺序来排列的(注:
有关顶点的排列顺序,可参考sdk文档FaceandVertexNormal
Vectors)。
DirectX3D默认的剔除模式就是逆时针模式。
你可以简单在顶点集合中把第一个和第三个元素交换一下,看看会有什么不同。
现在我们知道背面剔除是怎样工作的,很显然,我们简单的程序并不需要剔除功能。
有一个简单的renderstate来控制剔除
模式,添加如下代码:
Device.RenderStates.CullMode=Cull.None;
再一次,eryingworksasexpected,试试拖放窗口的大小会怎样?
?
拖放窗口时自动重置Device
任何曾经使用C++或VB开发DirectX3D的人都知道,在改变窗口大小时,需要重新设置devicd,否则,DirectX3D会按原来
的分辨率继续渲染场景,并且把结果拷贝到(通过拉伸)新的窗口。
当通过WindowsForm控件创建device时,聪明的MamagedDirectX
能发现你改变了窗口的大小,并且重置device。
毫无疑问,程序总是能在正常的行为下运行,同时,你也能方便的自己重置device。
在自动重置device之前,会引发一个叫做DeviceResizing的事件。
捕获这个事件,把EventArgs类的Cancel成员设置为true,
就能回到默认的行为,在创建device之后加上如下代码
privatevoidCancelResize(objectsender,CancelEventArgse)
{
e.Cancel=true;
}
如你所见,这个方法只是简单的sayyes,我们确实想要取消这个操作。
现在订阅事件处理程序,让device知道不进行这种
操作:
device.DeviceResizing+=newCancelEventHandler(this.CancelResize);(注:
CancelEventHandle委托在
System.ComponentModel名称空间)
运行程序,最大化窗口。
三角的位置还和原来一样,不过这次看起来可怕极了。
边缘都是锯齿,看起来糟糕透了。
可以删除我
们刚添加的代码了。
ManagedDirectX默认操作已经帮我们完成了这个任务,可以直接利用它。
我说:
“要有光”,于是就有了光
我们绘制了三角形并且让他转起来了,怎样才能让他更好呢?
当然是灯光。
在前面曾简要的提到过它,事实上,那个时候我们
完全关闭了灯光。
首先要做的就是先回到那个黑暗的场景:
deviceRenderState.Lighting=true;
其实你甚至可以
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Direct3D 教材
![提示](https://static.bdocx.com/images/bang_tan.gif)