原文地址:http://directx4vb.vbgamer.com/DirectX4VB/Tutorials/DirectX8/GR_Lesson01.asp#2
译文地址:http://www.cnitblog.com/-AntialiasingMan-/archive/2008/02/21/40014.html
原文作者:Jack Hoxley
译文作者:AntialiasingMan
原文写于:2000年11月30日
译文写于:2008年02月19日
作者联系:Jollyjeffers@Greenonions.netscapeonline.co.uk
译者联系:AntialiasingMan@gmail.com
本文附件:Graph_01.Zip [8 Kb]
内容概要:
1. 引言
2. 入门指南
3. 渲染
4. 主循环
5. 清除
6. 使用全屏模式(扩展这个例子)
1.引言
欢迎光临DirectX8-Graphics系列的第一课,这里有三个可能的原因来使你阅读此教程:①你是一个DirectX和VB新手。②你对升级到DirectX 7感兴趣,当你准备为DirectX 7升级而去看DirectX 8 SDK时,被弄得很困惑。③上述两个原因都有。
DirectX 8图形的实现与先前的版本完全不同,即便你是一位使用以前版本的老手。主要的不同之处就是DirectDraw接口已经消失了,在DirectX 7中,使用3D硬件加速功能产生2D图形是可能的——虽然这并不容易;而在DirectX 8中,这是唯一产生2D图形的方法。如果你完全是个DirectX新手,也不必担心这部分内容
DirectX 8提供给我们众多难以置信的特性。但遗憾的是大部分都极端复杂,除了专业人员任何人都不可能予以使用。例如:可编微程序单一像素处理和顶点处理的体系架构将使游戏变得更逼真;网格蒙皮将使角色变得更逼真等等。此教程将尽可能的试着解释它们。
如果你是一位老练的DirectX 7程序员,可以直接跳到第二部分;而对DirectX还不太熟悉的人来说——简要说明如下。DirectX是一个类与接口的集合,它允许你从底层的来访问硬件。使用它们得到的速度几乎完全是实时的,并且非常的简单。调用DirectX访问硬件的速度比调用复杂的Windows API函数还要快许多。DirectX几乎支持每个可想象得到的特性,从最新的3D显卡特性,声卡特性,因特网连接特性,输入设备特性到下一版本的特性……以一个程序员眼光来看的话,我们创建了一个DirectX组件的实例,它们都由DirectX产生,Direct3D,DirectSound,DirectMusic。一旦应用程序可以访问它们,我们就可以使用了,首先设置硬件——在图形方面设置显示模式和任何渲染选项,加载纹理和几何体。如果完成的话就会进入一个死循环(大多数DirectX应用程序都这么做),在这里我们将更新一些任何你需要的东西和渲染屏幕的下一帧——这就是帧速率的基础,70帧的速率简要说明一下就是每秒钟循环体被执行了70次……
但愿你能够继续学习接下来的部分
2.入门指南
DirectX图形系列的最初部分就是学习怎样建立一个基本的框架。你应该记住在这里学习的DirectX代码并经常性的去使用,最后你或许能够在睡觉时背诵(依赖于你是个什么样的人!)。如果现在不好好学习这些代码,在后续的课程中你就会落下,重复上述学习过程直到你搞懂为止——后续课程都将假定你能够自己建立这个基本的框架。
2a.创建工程并引用DirectX 8库文件
新建一个Visual Basic标准EXE工程,你会看到一个窗体被添加上了——这应该是一个标准,请不要因为你有一点使用VB的经验而盲目的向DirectX进军。选择"工程"->"引用",移动滚动条直到你看到一个叫做"DirectX 8 for Visual Basic Type Library"的项目,勾选并单击确定按钮。如果你愿意的话可以保存一下工程。现在此工程就能够使用DirectX 8的了。你需要注意可能发生的问题,这里有几种解决方法:
1.你已经安装了DirectX 8了吗?如果没有,上面需要勾选的项目就不会出现在列表框中。
2.你已经安装了Visual Basic 5或更高的版本了吗?DirectX只能在5或后续版本中使用组件对象模型(COM)
3.如果上述两个条件你都具备,但需要勾选的项目仍然没有出现。请点击"浏览"按钮指向C:\Windows\System\dx8vb.dll文件后按确定按钮——这样应该可以了。
4.上面的说明对VB5和VB6具有适用性,但正在写本文的时候VB7(或VB.Net)还没有放出正式版本——可能界面略做改变,对此也同样具有适用性的。
2b.变量
这部分代码在窗体中声明:
'// 必需的变量
|
|
Dim Dx As DirectX8
|
'// 主对象,一切都始于这里
|
Dim D3D As Direct3D8
|
'// 控制一切3D事物
|
Dim D3DDevice As Direct3DDevice8
|
'// 实际上代表了硬件的渲染
|
Dim bRunning As
Boolean
|
'// 控制程序是否运行
|
|
|
'// 这些都不是必须的——仅用于显示帧速率
|
|
Private
Declare
Function GetTickCount Lib "kernel32"() As Long
|
'// 用于获取帧速率(Windows API)
|
Dim LastTimeCheckFPS As
Long
|
'// 上次检查帧速率是在什么时候
|
Dim FrameDrawn As
Long
|
'// 已经画了多少帧
|
Dim FrameRate As
Long
|
'// 当前的帧速率是……
|
2c.初始化
之后,你需要初始化对象并设置一些基本的参数。没有什么复杂的,不过现在我们将坚持使用窗口模式(非全屏模式,被大多数游戏使用)
'// 初始化:这个步骤开始整个过程
|
|
'// 返回值True为成功,False为发生错误
|
|
Public Function Initialise() As Boolean
|
|
On Error Goto ErrHandler:
|
|
|
|
Dim DispMode As D3DDISPLAYMODE
|
'// 描述我们的显示模式
|
Dim D3DWindow As D3DPRESENT_PARAMETERS
|
'// 描述我们的视图
|
|
|
Set Dx = New DirectX8
|
'// 创建我们的主对象
|
Set D3D = Dx.Direct3DCreate()
|
'// 让我们的主对象创建的Direct3D界面
|
|
|
D3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT,DispMode
|
'// 检索当前的显示模式
|
|
|
D3DWindow.Windowed = 1
|
'// 告诉它使用窗口模式
|
D3DWindow.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC
|
'// 让显示器自己刷新
|
D3DWindow.BackBufferFormat = DispMode.Format
|
'// 检索并使用这个格式……
|
|
|
'// 这行代码将在一分钟之内详细解释
|
|
Set D3DDevice = D3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, FrmMain.Hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DWindow)
|
|
|
|
Initialise = True
|
'// 我们成功了
|
Exit Function
|
|
'// 我们失败了,但现在不必担心是何原因
|
|
Initialise = False
|
|
End Function
|
|
如果一切按计划进行,这个函数会创建一切所需对象——准备让我们使用。但是,这里有一些是需要说明的东西,因为此函数可能已经有问题了……
主要导致有问题的原因是函数CreateDevice的调用。这里有许多调用此函数时的参数,而很多参数都依赖于硬件。让我们分析下面的函数原型:
object.CreateDevice(Adapter
As Long, DeviceType
As CONST_D3DDEVTYPE, hFocusWindow
As Long, BehaviorFlags
As Long, PresentationParameters
As D3DPRESENT_PARAMETERS) As Direct3DDevice8
|
Adapter:无论是主适配器还是副适配器,D3DADAPTER_DEFAULT总是代表主适配器。我们将在后续课程中涵盖如何使用副适配器。基本上,它们做的是在主显示卡(一个标准的2D/3D显卡)和副显示卡(如只有3D功能的voodoo 1和voodoo 2的显卡)之间切换。
DeviceType:这基本上是让你在HAL(Hardware Accelerator,硬件加速)和REF(Reference Rasterizer,一调试工具)之间选择。这里有第三个选项,软件渲染,作用是设计能支持自定义渲染的插件。DirectX DDK(驱动程序开发工具包)就能做到,但如果你能自己写出3D渲染器的话,是不太可能使用VB的J……请指定参数D3DDEVTYPE_HAL或D3DDEVTYPE_REF。如果不支持硬件加速,调用此函数就会失败,你就不能创建设备。
hFocusWindow:这只是一个让DirectX跟踪你应用程序状态的值。这个值来自于有效的Form.hWnd(窗口句柄),窗口句柄创建窗体就可以得到,没有什么奇特的——请不要使用圆形的窗体或MDI窗体J。
BehaviorFlags:仅仅是设置Direct3D如何处理纹理,顶点,灯光等。最好选择使用D3DCREATE_PUREDEVICE——但是只有极少数显卡支持(即使是相对较新的tnl的GeForce 256显卡),使用此选项将意味着3D显卡几乎要做所有的事情——变换,明暗处理,灯光,纹理和光栅化。如果你的硬件不支持这个参数,最好是使用D3DCREATE_HARDWARE_VERTEXPROCESSING——将尽可能多的使用硬件加速,大多数最近的显卡都支持。如果还失败的话试试D3DCREATE_MIXED_VERTEXPROCESSING参数,它将尽可能的使用硬件,但如果你的硬件不能处理那么就用软件组件来仿真。最后一个是无格式的软件光栅,在没有3D硬件可用时这可能是唯一的选择。但速度几乎总是很慢,并且使用它也不是件美差事——如果你想请这样做:D3DCREATE_SOFTWARE_VERTEXPROCESSING。
PresentationParameters:描述你想使用的显示模式。传递当前的显示模式就可以使用窗口模式;改变这些设置(后面会提到),就可以进入全屏模式。
现在你应该明白,这里有多个依赖于硬件的变量。为了解决这个问题,我们将使用一个叫做枚举的过程,此过程将具体得到你的电脑可以做什么。不过此部分内容在后续的课程中,本课程仅仅是个开头。
3.渲染
这最终将成为你程序中的主要部分,写在这里的代码将会被多次循环的执行,不简洁的代码意味着低下的性能,简洁而高效的代码将导致非常高的帧速率和平滑的游戏运行效果。之后,如何整理并设置初始化过程使它按你的要求工作正是本节的重点。
渲染几乎总是始于一个过程,其它过程调用此过程,但此过程通常有很长的趋势,它几乎总是按照同样的模式进行的:
1.更新任何改变的顶点,摄像机,纹理等……
2.清除屏幕——移除最后工作帧
3.画出新的一帧——这将是个很长的部分
4.更新任何最终变量
5.拷贝渲染好的图像到屏幕(有经验的DirectX 7程序员使用Primary.Flip)
至此,对这个简单的课程,我们将创建上述过程的基本框架。
Public Sub Render()
|
'// 1.首先我们需要在画任何东西之前先清除渲染器(渲染的画面)
|
'// 这通常发生在你要开始渲染新的画面时
|
D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET, &HCCCCFF, 1#, 0
|
'// 中间的十六进制数值与你在HTML中的使用颜色的方法是一致的——如果你熟悉的话
|
|
'// 2.其次要渲染任何一切东西。这堂课不需要这么做,但如果要做的话看起来像这样:
|
D3DDevice.BeginScene
|
'// 所有的渲染调用都在这两行之间进行
|
D3DDevice.EndScene
|
|
'// 3.更新帧画面到屏幕
|
'// 等同于在DirectX 7中使用Primary.Flip方法
|
'// 下面的这些数值在大多数情况下都可以正常工作
|
D3DDevice.Present ByVal 0, ByVal 0, 0, Byval 0
|
End Sub
|
引用的这段代码很简单,并未画出任何东西,也未更新任何变量。但是,在后续的课程中,你可以看出这里是如何改变的。
值得一提的是这里的多个参数使用了"ByVal 0"。当你输入"D3DDevice.Present"后,就会注意到提示的参数是SourceRect,而参数DestRect和DirtyRegion都被定义成"Any"类型。这就意味着理论上可以传递任何数据类型作为其参数。倘若我们只把0作为参数,Visual Basic就会将它解释为"无效/空(Nothing)"——这并不是我们想要的,实际上,我们想要把数值0传递出去。如果加上ByVal部分,Visual Basic就能确保传递的是一个数值,这样做显然好于传递"Nothing"。
4.主循环
主循环基本上是执行非常紧密循环的一小块代码,每次循环我们都将更新图形,人工智能,声音,物理(或任何游戏所需要做的事情),运行越高效的代码则帧速率越高——这是很简单的道理。由于我们改变了循环操作的方式,就可以在同一过程中包含初始化代码和终止代码。它看起来像这样:
Private Sub Form_Load()
|
|
Me.Show
|
'// 确保窗体可见
|
|
|
bRunning = Initialise()
|
|
Debug.Print "Device Creation Return Code: ", bRunning
|
'// 你可以看看会发生什么
|
|
|
Do While bRunning
|
|
Render
|
'// 更新这帧……
|
DoEvents
|
'// 给Windows思考的时间,否则你将会得到一个真正不透气的循环
|
|
|
'// 计算帧速率,如何工作并不是很重要
|
|
'// 所以请不必担心一时不能理解……
|
|
If GetTickCount – LastTimeCheckFPS >= 1000 Then
|
|
LastTimeCheckFPS = GetTickCount
|
|
FrameRate = FramesDrawn
|
'// 储存当前帧数
|
FramesDrawn = 0
|
'// 重设计数器
|
Me.Caption = "DirectX-Graphics: Lesson 01 {" & FrameRate & "fps}"
|
'// 在屏幕窗体标题上显示
|
End If
|
|
FramesDrawn = FramesDrawn +1
|
|
Loop
|
|
|
|
'// 如果到了这里,循环必须被终止
|
|
'// 因此我们需要自己来清除,虽不是必需的,但是个良好的编码实践
|
|
|
|
On Error Resume Next
|
'// 如果对象没有被创建(初始化过程失败),释放变量时就会得到错误……我们必须好好处理,但反正我们正在关闭程序……
|
Set D3DDevice = Nothing
|
|
Set D3D = Nothing
|
|
Set Dx = Nothing
|
|
Debug.Print "All Objects Destroyed."
|
|
|
|
'// 最后终止
|
|
Unload Me
|
|
End
|
|
End Sub
|
|
你可能已经注意到所有这些代码都在窗体的Load过程中,这就意味着当应用程序开始加载的时候,DirectX就已经被初始化并被设置好了,然后直接进入游戏主循环。其中一个务必要记住的是你必须在最开始的一行代码中包含"Me.Show"这个方法。在一个正常的VB应用程序中,当Form_Load过程完成后,窗体才会被显示出来——但我们的情况是,当应用程序关闭后窗体才会显示出来。窗体中包含的"DoEvents"这行代码的作用最终被证明,除此以外都很有条理。
主循环体结构的运行与否完全基于一个布尔变量,当这个变量为"True"时主循环开始被执行,改变为"False"时则离开循环体继续执行Form_Load下面的代码。正如你看到的那样,这部分代码会尽快清除DirectX(在后面解释)并关闭应用程序。你可以把那个布尔变量想象成一个开关——且这个循环体工作时响应的非常之快。1秒60帧的速率相当于在1/60秒里完成一次循环。
另外在这个循环例子中,还有两件事要思考。其中之一便是此过程被调用时的次序——在这里并不重要(只有一个过程),但在一个完整的游戏结构中每次循环需要处理多个过程。很好的例子是当你在绘图时,绘图引擎是依赖于键盘输入的——也就是说,如果你先调用绘图引擎画图,然后再调用输入引擎的话,那么得到的图像总是比用户输入刚完成时慢一帧;若先调用输入引擎后调用绘图引擎,那么结果就同步了。最后是帧速率计算,实时知道你应用程序运行时的速度是相当有用的,所要做的是:每次循环计数器增加1,每秒钟拷贝这个持续变化的变量,最后将计数器清0(周而复始)。
最后要注意的是关于主循环的"DoEvents"这行代码。没有这个简单调用的话会运行的飞起来。这行代码允许Windows在说话时"呼吸",没有它,几乎一切都将停止——窗体不能显示,鼠标/键盘的输入不能被响应,设置的任何属性都将被延迟(例如在这个例子中修改的窗体标题),最坏的情况是当你需要用键盘/鼠标事件来终止这个主循环时——没有任何键盘/鼠标事件能被响应,显而易见,你不可能终止掉此循环,不能终止的话那就只好继续等待——直到程序自己崩溃为止(呆会儿就会发生)。为了保险起见请把这行代码留在那里——不要去想它,请记得这点。
5.清除
最后要做的事情是清除工作,如果你忘记了,也不要紧,因为最后这部分不是必须的,但这是个很好的练习。清除时,你需要以创建对象时相反的次序来释放它们。实际上你已经看到代码了:
On Error Resume Next
|
Set D3DDevice = Nothing
|
Set D3D = Nothing
|
Set Dx = Nothing
|
真的很简单,要释放一个对象,你仅需像这样编码"Set <对象名称> = Nothing"即可,我在这里包含了on error resume next部分,因为当你在释放一个从未被创建的空对象时会得到一个错误——这将发生在此例初始化失败时。
5.使用全屏模式(扩展这个例子)
如果现在你运行这个示例就会清楚的发现它运行在窗口模式中(我在前面已经讲过)。虽然大多数游戏都有一个窗口模式的选项,但默认是使用全屏模式的。这是有很多原因的,不过基本上是为了看起来感觉更好——游戏看起来更具临场感,你不会在后面的背景中看到"我的文档"或者开始菜单栏。
要切换到全屏模式不是很复杂,仅仅是稍微重新设计一下我们的结构体:
DispMode.Format = D3DFMT_X8R8G8B8
|
|
DispMode.Width = 640
|
|
DispMode.Height = 480
|
|
|
|
D3DWindow.SwapEffect = D3DSWAPEFFECT_FLIP
|
|
D3DWindow.BackBufferCount = 1
|
'// 背景(次)缓冲区
|
D3DWindow.BackBufferFormat = DispMode.Format
|
'// 等会儿再说明
|
D3DWindow.BackBufferWidth = 640
|
|
D3DWindow.BackBufferHeight = 480
|
|
D3DWindow.hDeviceWindow = frmMain.hWnd
|
|
真的不复杂,上面的代码仅仅是替换先前已存在的"D3DWindow"初始化代码而已。
我将假设你知道什么是分辨率(640×480,1024×768等),在这里就不再说明了,但使用上述代码时,有一点是需要你注意的。
那就是显示模式的格式——DispMode.Format——你看到上面的代码将它设为"D3DFMT_X8R8G8B8"。 哎呀,这个是什么意思?呵呵,所有的纹理和表面(次缓冲区,主缓冲区,深度缓冲等……)都是以一种特定的格式储存在内存中的。你可能在使用分辨率时会用到位深度——8位,16位,24位,32位,这些数值告诉你每个像素需要用多少位来储存,8位是一个字节,因此当为32位色时,每个像素的储存需要占用4个字节的内存空间。越高的位深度需要越多的内存空间(不过画面看起来更好J)。那么有相关这方面的资料吗?"D3DFMT_X8R8G8B8" 标记指明如何设置内存——这里的情况是8888格式 = 32位色。稍后将会在后面碰到其它一些格式,但现在我们保持这个值。上面的代码需要你有一台支持分辨率为640×480,色彩深度为32位色的计算机,如果没有,那么代码执行就会失败,同时你需要牢记实际屏幕的宽度/高度——你知道如果硬件不支持指定的分辨率就会出错……后续的课程中将讨论枚举——在此过程中能知道你的硬件支持什么特性。
你可以在这篇教程的顶端下载附带的源代码。
假如你能理解所有这些知识,就可以继续学习第二课:枚举显示设备可用的显示模式。
翻译这篇教程花了我整整2天的时间,现在终于完成了,不过这并没有结束——是时候应该耐心的阅读此教程来理解和学习了,虽然这么做比较辛苦,但也是没有办法的,因为国内关于游戏开发方面的教程非常之少,况且这个还是用VB6来做游戏开发的(过时了吗?),这样的话此类教程就更少了。我想大家在看这篇译文的时候,可能会感到写这个教程的老外比较罗嗦吧,但其实他讲的非常的细,这样能让你理解的更为透彻些。就像我玩过的《英雄传说6:空之轨迹》这个游戏一样,做的也比较细,同一个场景,一些无关紧要的地方如果换不同的人来对话,会有不同的结果——虽然很少有人会这么无聊来专门测试对话,抱歉,跑题了,就此打住。最后我想说的是教程里提到的一些观点现在看来简直不可思议(如这句话:即使是相对较新的tnl的GeForce 256显卡。话说这个是什么卡呀,估计现代人都没有听说过),但也不必太在意,原文写于8年前!看到的话一笑则已。
【难译语句中英对照表】:
原文
|
译文
|
such as the microprogrammable architecture for per-pixel processing and vertex processing will make games more lifelike
|
可编微程序单一像素处理和顶点处理的体系架构将使游戏变得更逼真
|
Mesh skinning will make the representations of players more realistic and various other features
|
网格蒙皮将使角色变得更逼真等等
|
They get their speed almost entirely from the fact that they are very simple and very "thin"
|
使用它们得到的速度几乎完全是实时的,并且非常的简单
|
With the "DoEvents" line included the form will eventually be shown, but it wont be very tidy.
|
窗体中包含的"DoEvents"这行代码的作用最终被证明,除此以外都很有条理。
|
Without this simple call things would go pair shaped very quickly
|
没有这个简单调用的话会运行的飞起来。
|
although it's not always the end of the world if you forget it's good practise to do it
|
如果你忘记了,也不要紧,因为最后这部分不是必须的,但这是个很好的练习。
|