现下主要讨论Managed DirectX,以后会讨论.NET 2.0和Longhorn.
[转]3D Engine 的设计架构
(作者置顶)
虽然里面有些外文翻译的不是很准确,但是幸好有原文可以让我们对照,已经是一篇翻译的相当好的文章了...
本人对3D也不甚了解,译文动机一则是内容所致兴致昂然,二则锻炼英译中技能。由本人水平及经验有限,文中绝对不乏大量误解与误译,亦恳请读者指出,得以一同提高。佳文须共赏,也欢迎大家自由转载 :)
Introduction (简介)
让咱们谈谈你如何撰写一份提供优雅性能的3D引擎。你的引擎需要提供的包括:曲面(curved surfaces)、动态光线(dynamic lighting)、体雾(volumetric fog)、镜面(mirrors)、入口(portals)、天空体(skyboxes)、节点阴影(vertex shaders)、粒子系统(particle systems)、静态网格模型(static mesh models)、网格模型动画(animated mesh models)。假如你已经知道如何以上所述的所有功能顺利工作,你也许便能将那些东东一起置入到一个引擎当中。
等等!在你开始撰写代码前你必须先构思一下如何去架构你的引擎。多数来讲,你一定是迫切地渴望去制作一个游戏,但如果你立即投入便开始为你的引擎撰写代码后,你一定会觉得非常难受,开发后期你可能会为置入新的特效与控制而不得不多次重写大量的局部代码,甚至以失败而放弃告终。花一点时间好好地为你引擎深谋远虑一番,这将会为你节省大量时间,也少一点头痛。你一定不会急切地去架构一个巨型的工程;或许你也会在引擎未完成时而干脆放弃它,然后去干的别的什么事儿。好了,当你掌握学习你所需知识的方式之前,也许你还不能完成那些事儿。将设计真正地完成确实是件美事,为之你会感觉更好,你将为之而耀眼!
让我们分析一下具备完整功能的3D游戏引擎的需要哪些基本部件。首先,这为具有相应3D经验但且还需一些指引的开发者提供了一些信息。这是一些并不难且能快速掌握但是你必须应用的内容条目。为将你的工作更好地进行下去,这里将对关于“把多大的工作量”与“多少部分”置入一个游戏引擎给出一个总概。我把这些成分称为 系统(System)、控制台(Console)、支持(Support),渲染/引擎 内核(Renderer/Engine Core)、游戏介质层(Game Interface)、以及工具/数据(Tools/Data)。
Tools/Data (工具/数据)
在开发过程中,你总是需要一些数据,但不幸的是这并不象写文本文件或是定义一个立方体那么简单。至少,你得需要3d模型编辑器,关卡编辑器,以及图形程序。你可以通过购买,也可以在网上找一些免费的程序满足你的开发要求。不幸的是你可能还需要一些更多的工具可你却根本无法获得(还不存在呢),这时你只得自己动手去写。最终你很可能要自行设计编写一个关卡编辑器,因为你更本不可能获得你所需。你可能也会编写一些代码来为大量的文件打个包,整天面对应付成百上千个文件倒是非常痛苦的。你还必须写一些转换器或是插件将3d模型编辑器的模型格式转换成你自己的格式。你也需要一些加工游戏数据的工具,譬如可见度估算或是光线贴图。
一个基本的准则是,你可能要为设计工具而置入比游戏本身等量甚至更多的代码。开始你总能找到现成的格式和工具,但是经过一段时间以后你就能认识到你需要你的引擎有很大的特性,然后你就会放弃以前的撰写方式。
也许目前非常流行利用的第3方工具辅助开发,所以你必须时刻注意你的设计。因为一旦当你将你的引擎发布为opensouce或是允许修改,那也许在某天中会有某些人来应用你的开发成果,他们将其扩展或者做某些修改。
或许你也应该花大量时间去设计美术,关卡,音效,音乐和实体模型,这就和你设计撰写游戏,工具以及引擎一样。
System (系统)
系统(system)是引擎与机器本身做通信交互的部件。一个优秀的引擎在待平台移植时,它的系统则是唯一需要做主要更改(扩加代码)的地方。我们把一个系统分为若干个子系统,其中包括:图形(Graphics)、输入(Input)、声音(Sound)、记时器(Timer)、配置(Configuration)。主系统负责初始化、更新、以及关闭所有的子系统。
图形子系统(Graphics Sub-System)在游戏里表现得非常直观,如果想在屏幕上画点什么的话,它(图形子系统)便干这事儿。大多数来讲,图形子系统都是利用OpenGL、Direct3D, Glide或是软件渲染(software rendering)实现。如果能更理想一些,你甚至可以把这些API都给支持了,然后抽象出一个“图形层”并将它置与实现API之上,这将给了客户开发人员或是玩家更多的选择,以获取最好的兼容性、最佳的表现效果。
输入子系统(Input Sub-System)需要把各种不同输入装置(键盘、鼠标、游戏板[Gamepad],游戏手柄[Joystick])的输入触发做统一的控制接收处理。(透明处理) 比方说,在游戏中,系统要检测玩家的位置是否在向前移动,与其直接地分别检测每一种输入装置,不如通过向输入子系统发送请求以获取输入信息,而输入子系统才在幕后真正地干活(分别检测每一种输入装置),这一切对于客户开发人员都是透明的。用户与玩家可以非常自由地切换输入装置,通过不同的输入装置来获取统一的行为将变的很容易。
声音子系统(sound system)负责载入、播放声音。该子系统功能非常简洁明了,但当前很多游戏都支持3D声音,实现起来会稍许复杂一些。
3D游戏引擎中很多出色的表现都是基于“时间系统”(time)的。因此你需要一段时间来为时间子系统(Timer sub-system)好好构思一番。即使它非常的简单,(游戏里)任何东西都是通过时间触发来做移动变化,但一份合理的设计将会让你避免为实现而一遍又一遍地撰写大量雷同的控制代码……
配置系统(Configuration)位于所有子系统的顶端。它负责读取配置记录文件,命令行参数,或是实现修改设置(setup)。在系统初始化以及运行期间,所有子系统都将一直与它保持通讯。切换图象解析度(resolution),色深(color depth),定义按钮(key bindings),声音支持选项(sound support options),甚至包括载入游戏,该系统将这些实现显得格外的简单与方便。把你引擎设计得更为可设置化一些,这将为调试与测试带来更大的方便;玩家与用户也能很方便地选择他(她)们喜欢的运行方式。
Console (控制台)
哈!我知道所有人都乐意去更风做一个象Quake那样的控制台(console)系统。但这的确是一个非常好的想法。通过命令行变量与函数,你就能够在运行时改变你的游戏或是引擎的设置,而不需要重启。开发期间输出调试信息它将显得非常的有效。很多时间你都需要测试一系列变量的值,将这些值输出到控制台上要比运行一个debugger速度显然要快得多。你的引擎在运行期间,一旦发现了一个错误,你不必立即退出程序;通过控制台,你可以做些非常轻便的控制,并将这个错误信息打印出来。假如你不希望你的最终用户看见或是使用该控制台,你可以非常方便地将其disable,我想没人能看得见它。
Support (支持)
支持系统(Support)在你引擎中任何地方都将被使用到。该系统包含了你引擎中所有的数学成分(点,面,矩阵等),(内)存储管理器,文件载入器,数据容器(假如你不愿自己写,也可以使用STL)。该模块任务显得非常基础与底层,或许你会将它复用到更多别的相关项目中去。
Renderer/Engine Core (渲染/引擎 内核)
哈~是呀,所有的人都热爱3D图象渲染!因为这边有着非常多的不同种类的3D世界渲染方式,可要为各类拥有不同工作方式的3D图形管道做出一个概要描述也是几乎不可能的。
不管你的渲染器如何工作,最重要的是将你的渲染器组件制作得基化(based)与干净(clean)。首先可以确定的是你将拥有不同的模块来完成不同的任务,我将渲染器拆分为以下几个部份:可见裁减(Visibility)、碰撞检测与反馈(Collision Detection and Response)、摄像器(Camera)、静态几何体(Static Geometry)、动态几何体(Dynamic Geometry)、粒子系统(Particle Systems)、布告板(Billboarding)、网格(Meshes)、天空体(Skybox)、光线(Lighting)、雾(Fogging)、节点阴影(Vertex Shading)和输出(Output)。
其中每一个部分都得需要一个接口来方便地实现改变设置(settings)、位置(position)、方向(orientation)、以及其他可能与系统相关的属性配置。
即将显露出来的一个主要缺陷便是“特性臃肿”,这将取决于设计期间你想实现什么样的特性。但如不把新特色置入引擎的话,你就会发觉一切都将变的很困难,解决问题的方式也显得特别逊色。
还有一件有意义的事便是让所有的三角形[s](或是面[faces])最终在渲染管道里经过同一点。(并非每次的每个三角形,这里讨论的是三角形列表[ lists]、扇形[fans]、带形[strips]、等) 多花一些工作让所有物体的格式都能经过相同的光线、雾、以及阴影代码,这样就能非常便利地仅通过切换材质与纹理id就使任何多边形具有不同的渲染效果。
这不会伤及到被大量被渲染绘出的点,但是一旦你不当心,它可能会导致大量的冗余代码。
你也许最终便能发现,实现所有这些你所需的极酷效果可能只占了所有的15%左右的代码量甚至更少。这是当然的,因为大多数游戏引擎并不只是图形表现。
Game Interface (游戏介质)
一个3D(游戏)引擎很重要的部分便是------它是一个游戏引擎。但这并不是一个游戏。一个真正的游戏所需的一些组件永远不要将它包含到游戏引擎里。引擎与游戏制作之间的控制介质能使代码设计变得更清晰,应用起来也会更舒服。这虽是一些额外的代码,但它能使游戏引擎具有非常好重用性,通过设计架够游戏逻辑(game logic)的脚本语言(scripting language)也能使开发变的更方便,也可以将游戏代码置入库中。如果你想在引擎本身中嵌入你的游戏逻辑系统设计的话,大量的问题与大量修改一定会让你打消复用这个引擎的念头。
因此,此时你很可能在思考这个问题:联系引擎与游戏的介质层到底提供了什么。答案就是控制(control)。几乎引擎的每一个部分都有动态的属性,而该引擎/游戏介质层(engine/game layer)提供了一个接口去修改这些动态属性。它们包括了摄像器(camera)、模型属性(model properties)、光线(lights)、粒子系统物理(particle system physics)、声效播放(playing sounds)、音乐播放(playing music)、输入操作(handling input)、切换等级(changing levels)、碰撞检测以及反馈(collision detection and response)、以及2D图形界面的顶端显示、标题画面等相关的东西。基本上来讲如果你想让你的游戏能优雅的实现这些元素,在引擎中置入这个介质层(interface)是必不可少的。
The Game (游戏)
在这里,我无法告诉你如何去写你的游戏。这该轮到你发挥啦。如果你已经为你那令人赞异的引擎设计出了一套出色的介质层的话,我想在设计撰写游戏过程中一定会轻松许多。
3D游戏引擎设计是一项巨大的软件工程。一个人独立完成设计并撰写也并非不可能,但这不只是熬一两个晚上便能搞定的,你很可能会出写出几兆的源代码量。如果你没有持久的信念与激情,你很可能无法完成它。
当然,别指望你的第一次尝试就能写出完整的引擎,挑一个比较小的项目所需的小规模引擎去实现。按你的方式去努力工作,你就能到达成功。
E文原稿
Introduction
So lets say your writing a 3D game engine that supports a pretty good set of features. Your list of desires includes curved surfaces, dynamic lighting, volumetric fog, mirrors and portals, skyboxes, vertex shaders, particle systems, static mesh models and animated mesh models. If you have a good idea of how all these things work, you can probably start putting these things together into an engine.
But wait! Before you start coding you need to think about what you're going to do with your engine. Most likely, you have aspirations of a game. If you just jump in and start coding an engine, you're gonna get burned and end up having to re-write huge portions of the engine multiple times to add effects and control later. A little forethought into engine design will save headaches and time. The thing you don't want to do is aspire to huge projects. You'll end up not finishing it, and moving onto something else. While this is a way to learn everything you need, you won't be completing things. It's good to actually finish projects. You'll feel better for it and you can show off!
Let's take a look at the basic components of a full-featured 3D game engine. First off, this is for those people are reasonably experienced with 3d but need a little direction. These are not hard and fast rules that you must use. It's just what has worked well for me and to give an overview of how much work and how many parts go into a game engine. I call these parts System, Console, Support, Renderer/Engine Core, Game Interface, Game, and Tools/Data.
Tools/Data
During development, you're going to need some data, and unfortunately it's not going to be as easy as writing some text files that define a cube. At the least you will need 3d model editors, level editors, and graphics programs. These things can be bought or you can find a free program online that does the same type of thing. Unfortunately you're probably going to need more tools than this and the software doesn't exist yet. You need to write it. You may end up writing your own level editor if you can't find one that does the things you want. You'll probably need to write some code to pack files into an archive since dealing with and distributing hundreds or thousands of files can be a pain. You'll need to write converters or plug-ins from your 3d model editor format into your own format. You'll need tools to process game data, such as visibility computations or lightmaps.
The basic line is that you'll probably have nearly as much or more code in tools than actual game code. You can find formats and tools that you can use, but sooner or later you'll realize you need some things that are very custom to your engine and you'll end up writing your own anyway.
Although there is a probably a big rush to get tools done just so there are usable, do take care in your coding. Someone may actually try to use, extend or modify your tools one day, especially if you make your engine to be opensource or modifiable.
You should probably be prepared to spend as much time graphics, levels, sound, music and models as you did writing the game, tools, and engine.
System
System is the part of the engine that communicates with the machine itself. If an engine is written very well the system is the only part that should need major modifications if porting to a different platform. Within the system are several sub-systems. Graphics, Input, Sound, Timer, Configuration. The System is responsible for initializing, updating, and shutting down all these sub-systems.
The Graphics Sub-System is pretty straightforward. If it deals with putting things on the screen, it goes here. One would most likely write this component using OpenGL, Direct3D, Glide, or software rendering. To get even fancier, you could implement multiple API interfaces and put a Graphics Layer on top of them to give users more choice and more chance for compatibility and performance. This is a bit difficult though, especially since not all API's will have the same feature sets.
The Input Sub-System should take all input (Keyboard, Mouse, Gamepad and Joystick) and unify it and allow for abstraction of control. For example, somewhere in the game logic, there will be a need to see if the user wants to move his or her position forward. Rather than doing multiple checks for each type of input, one would make one call to the input sub-system and it would transparently check all devices. This also allows for ease of configuration by the user, and easily having multiple inputs perform the same action.
The sound system is responsible for loading and playing sounds. Pretty straight forward, but most current games support 3D sound and this will make things a little more complex.
Pretty much everything in a 3d game engine (I'm assuming real-time games here...) is based on time. Hence you need some time management routines in the Timer sub-system. This is actually pretty simple, but since anything that moves will move with time, a decent set of routines will keep you from writing the same code over and over.
The Configuration unit actually sits above all the other sub-systems. It's responsible for reading configuration files, command line parameters, or whatever setup method is used. The other sub-systems query this system when they are initializing and running. This makes it easy to change resolution, color depth, key bindings, sound support options, or even the game that gets loaded. Making your engine very configurable just makes it easier to test and allows users to set things up the way they like.
Console
Ok, I know everyone likes to follow the crowd and have a console like Quake. But it's really a good idea. With console variables and functions you can change settings in your game and your engine without restarting it. It's wonderful for outputting debug information while developing. Often times you'll be needing to examine some variables and outputting the to the console is faster and sometimes better than running a debugger. Once your engine is running, if an error occurs, you don't have to quit the application; you can handle it gracefully and just print an error message. If you don't want your end user to see or use the console, its easy to disable it and no one is the wiser that it's still there.
Support
This is a system that is used by pretty much every other system in the engine. This includes all your mathematics routines, (vectors, planes, matrix, etc), memory managers, file loaders, containers (or use STL if you don't roll your own). This stuff is pretty basic and will probably be used in most any project you ever are involved in.
Renderer/Engine Core
Ah yes, everyone loves rendering 3D graphics. Because there are so many different methods of rendering 3D worlds, it's nearly impossible to give a description of a graphics pipeline that works in all situations.
Regardless of how you implement your renderer, the most important thing is to make your renderer component based and clean. Make sure you have distinct modules for different things. The sub-sections that I break the renderer into are sections like Visibility, Collision Detection and Response, Camera, Static Geometry, Dynamic Geometry, Particle Systems, Billboarding, Meshes, Skybox, Lighting, Fogging, Vertex Shading, and Output.
Each one of these sections needs to have an interface that easily allows for changes in settings, position, orientation, or any other setting that may be associated with the system.
One major pitfall to look out for is feature bloat. Decide what you are going to implement at the design stage. If you don't adding new features will start to get hard to do and the solutions will be kludgy.
One thing that is nice and convenient is to have all s (or faces) ultimately pass through the same point in the render pipeline. (Not a at a time, I'm talking about lists, fans, strips, etc.) It takes a bit more work to get everything into a format that can be passed through the same lighting, fogging, and shading code but in the end you'll be happy that any effect that you can do to one polygon can be done to any polygon in your game simply by changing its material/texture id.
It doesn't hurt to have multiple points from which things are drawn, but it can lead to redundant code if you're not careful.
You'll eventually find out that all those cool effects that you wanted to implement will be 15% or less of the total code you end up writing. That's right, most of a game engine is not graphics.
Game Interface
The most important part of a 3D game engine is that it is a game engine. It is not a game. The actual game should never have components that are put into the game engine. Having a thin layer between the engine and the game makes for cleaner code and ease of use. It's a little extra code, but it also makes a game engine very reusable and it becomes much easier to use a scripting language for game logic, or put the game code in a library. If you embed any of your game logic in the engine itself, don't plan on reusing it later without lots of problems and modifications.
So you're probably wondering what this layer between the engine and game provides. The answer is control. For each part of the engine that has any dynamic properties, the engine/game layer provides an interface to modify it. Some things in this category include the camera, model properties, lights, particle system physics, playing sounds, playing music, handling input, changing levels, collision detection and response, and placement of 2D graphics for a heads up display, title screen or whatever. Basically if you want your game to be able to do it, there must be an interface into the engine to do it.
The Game
Well, at this point, I can't tell you how to write your game. That's up to you. You've spent a while writing this amazing engine with a great interface that will make your game writing process less stressing.
3D game engines are huge software projects. A single person can write one, but it's not an overnight process. You're probably going to have more than a few megabytes of source code. If you're not motivated to finish it, you won't.
Also don't expect to write a full engine on your first try. Pick a small project that has small requirements in the engine. Work your way up. You'll get there.
转换Blog空间
转换了Blog的空间,地址如下:
Managed DirectX(4)
有了上述信息之后,我们就可以创建一个设备了。让我们回到我们的代码中,开始工作。首先,我们需要有一个为我们应用程序应用的设备对象。我们可以添加一个私有成员(private member)设备变量。在你的类中定义下面这样的变量:
private Device device = null;
再在我们的类中添加一个新的函数,名叫“InitializeGraphics”。在你的类中添加以下代码:
/// <summary>
/// 初始化图形设备
/// </summary>
public void InitializeGraphics()
{
// 设置图形参数
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
// 创建设备
device = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing, presentParams);
}
如你所看到的,它创建了一个图形参数子变量,把成员设置成Windowed和SwapEffect。然后创建了一个设备。我们用0来作为适配标识符,因为它是默认的适配器。我们创建了一个实际的硬件设备,反对使用引用光栅或者软件光栅。你会注意到我们用“this”关键字来作为绘制窗口,因为我们的应用程序,尤其是这个类是一个Windows窗体,仅此而已。我们也让CPU来进行顶点处理,就像我们当初提及的那样。
这已经相当了不起了,但是现在这个类还不能够被调用。所以让我们来改变类的主函数来实际调用一下这个方法。让我们像下面那样来改变这个静态的main函数:
static void
{
using (Form1 frm = new Form1())
{
// 显示窗体,然后初始化我们的图形引擎
frm.Show();
frm.InitializeGraphics();
Application.Run(frm);
}
}
我们对这个函数做了一下小小的改动。首先,我们用using子句添加在创建窗体的外围,这样可以确保我们的窗口在应用程序失去作用域的时候是良好的。其次,我们对窗体添加了Show命令。这样做是为了在我们创建设备之前窗口被实际加载和显示(如此,就创建了窗口句柄)。我们调用函数来绑定我们创建的设备,而且为我们的应用程序使用标准的运行方法。你现在可以编译并运行它了。祝贺你,你已经成功地创建了你的第一个D3D应用程序。
诚然,这个程序相当丑陋。它创建了一个设备,但是我们不能用它来做任何事情。它只是用来看一下的。你不会告诉我它跟我们原来建立的C#空项目没什么两样吧?让我们改变现况,给它加点东西。
Windows窗体类有一个内置的计算重绘的方法:用重载的OnPaint(你也可以用Paint事件),窗口每次被重绘的时候,这个事件都会被激活。这似乎是个写绘图代码的好地方。我们现在不想立刻就作出一些让人令人惊讶的,流行的东西。所以让我们把窗口清成单色(Solid Color),在你定义的类中添加以下代码:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
device.Clear(ClearFlags.Target, System.Drawing.Color.CornflowerBlue, 1.0f, 0);
device.Present();
}
我们在设备上用Clear方法来用单色填充窗口。为了以防万一,我们使用了预定义(predefined)颜色的一种?CornflowerBlue。Clear方法的第一个参数的意思是我们想要实际清除什么,在这个例子里,我们要清除目标窗口。在后面我们还要使用ClearFlags的其它枚举成员。第二个参数是我们将要把我们的目标窗口清除成什么颜色。后面几个参数就不是那么重要了。在设备被清除以后,我们想要更新物理显示器。Present方法就是做的。这个重载的方法有少许不同就是它显示了设备的全部区域。我们将在以后讨论其他的重载方法。
如果你现在运行这个程序,你会注意到当它运行的时候,应用程序的背景颜色是浅蓝色(cornflower blue)。你可以调整窗口的大小或者把它最大化。每一次你这么做的时候,窗体的全部表面都被填充满了浅蓝色。当我们继续看这个窗口的时候,它仍然相当丑陋。你可以在设计器中通过改变窗口的背景颜色来达到同样的效果。我们需要绘制一些令人印象深刻的其他的东西。
3维图形中最基本的绘制对象就是三角形()了。如果有足够多的三角形,你可以表现出任何东西,甚至是平滑的弯曲的表面。所以我们第一个绘图很自然就是一个单独的三角形了。为了绘制一个简单的三角形,我们将不去考虑一些东西,比如“自然空间(world space)”或者“变换式(transforms)”(这些是我们不久以后研究的内容)。取而代之的是我们将用屏幕坐标(screen coordinates)来绘制一个三角形。所以,为了绘制一个让人惊讶的三角形,我们需要两件东西。首先,我们需要一个能够保留我们三角形信息的数据结构(data construct)。其次,我们要告诉设备,我们要真正开始绘制了。
幸运的是,Managed DirectX运行时已经有保留我们三角形数据的结构了。在Direct3D命名空间内有一个CustomVertex类,它保存有很多公共的“顶点格式(vertex format)”结构。一个顶点格式结构保留有Direct3D理解和能应用的格式化数据。我们一会将讨论更多的这种结构,但是现在我们要在我们的三角形里用TransformedColored结构。这个结构告诉Direct3D运行时我们的三角形不需要被变换(transformed)(意思就是被循环(rotated)或移动(moved)),因为我们将指定它在屏幕中的坐标。当然也为在三角形中的每一个顶点指定颜色分量(color component)。返回那个重载的OnPaint,在Clear方法之后添加以下代码:
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].SetPosition(new Vector4(this.Width - (this.Width / 5.0f), this.Height ? (this.Height / 5.0f), 0.5f, 1.0f));verts[1].Color = System.Drawing.Color.Black.ToArgb();verts[2].SetPosition(new Vector4(this.Width / 5.0f, this.Height - (this.Height / 5.0f) , 0.5f, 1.0f));verts[2].Color = System.Drawing.Color.Purple.ToArgb();我们创建的每一个数组成员都表示三角形的一个顶点,所以,我们需要创建三个顶点。我们用重新创建的Vector4结构在数组的每一成员上调用SetPosition方法。一个变换的顶点位置在屏幕坐标上包括X,Y(相对于Windows原点坐标0,0),还包括Z和rhw成员(三维其次坐标W)。在我们现在的示例中先忽略后两个成员。Vector4结构是保存这些信息的好方法。然后我们设置每个顶点的颜色。注意,我们需要调用我们正在使用的标准颜色的ToArgb方法。Direct3D规定颜色必须是个32位的整数,这个方法可以把这些颜色转换成这种格式。
你应该也注意到了,我们用现有窗口的宽度和高度来计算三角形的坐标。我们这样做仅仅只是让三角形可以跟随窗口一块调整大小。
现在我们有了数据,需要告知Direct3D我们想要绘制和如何绘制三角形了。我们可以这么做,在重载的OnPaint方法中的Clear函数调用之后添加以下代码:
device.BeginScene();
device.VertexFormat = CustomVertex.TransformedColored.Format;
device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts);
device.EndScene();
这是什么意思呢?它相当简单。BeginScene方法可以让Direct3D知道我们将要开始绘制东西,而且已经准备好了。现在,我们已经告知Direct3D我们要绘制东西,还需要告知它我们要绘制什么。这就是VertexFormat属性的意思。它告知Direct3D运行时我们正在使用的固定功能管道格式(fixed function pipeline format)。以防万一,我们还使用了变换和有色顶点管道(transformed and colored vertices pipeline)。不必担心你不明白固定功能管道是什么,我们马上就接触它了。
DrawUserPrimitives的作用是实际在哪个地方绘图。这个函数后面参数的精确涵义是什么呢?第一个参数是我们打算绘制图元的基本类型。对我们来说有很多现有的不同类型,但是,现在我们只想绘制一组三角形,所以我们选择TriangleList基本类型(primitive type)。第二个参数是我们打算绘制三角形的数量。对于三角形来说,你的顶点数目应该总是能够被3整除。因为我们只绘制一个三角形,自然,我们这里用1。这个函数的最后一个参数是Direct3D将用来绘制三角形的数据。因为我们已经填充了我们的数据,所以已经准备就绪了。最后一个方法EndScene只是通知Direct3D运行时,我们不再绘图了。你必须在每次调用BeginScene方法后调用EndScene方法。
现在编译运行我们的新程序。你会注意到我们的背景中已经有了一个三角形。需要注意的重要一点是在我们代码中指定的颜色是三角形顶点的颜色,不是三角形内部的颜色。这个颜色从一个顶点到另一个逐步消失。Direct3D自动为你在三角形中内插(interpolates)这些颜色。修改我们选择的颜色来实际感受一下效果。
如果你正在运行这个程序,你或许会注意到一些事情。举个例子来说,如果你把窗口调的小一些,它没有更新它的内容,什么也没有做。原因就是Windows没有考虑到收缩一个窗口的事情,你需要重新绘制整个窗口。毕竟,你只需要简单删除一些要显示的数据,不用清除所有已存在的数据。幸运的是,还有一个简单的方式。我们只需要告诉Windows,我们始终需要窗口重绘。这只需要在重载的OnPaint方法的末尾添加以下代码就可以很容易实现使窗口作废。
this.Invalidate();
Oh,看起来我们似乎破坏了我们的程序。现在运行程序只显示了一个空白屏幕。而且我们的三角形在屏幕上有时一闪一闪的。这个效果比你调整窗口大小要明显的多。这并不好,但是为什么会这样呢?在使我们的窗口无效后要试着使Windows变得灵活和重绘我们目前的窗口(空白区)。有着色超出了重载的OnPaint范围。通过改变我们创建的窗口的“样式(style)”是很容易固定的。在你的窗体设计器中,用下面的代码来代替“TODO”块。
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
现在当你运行程序的时候,一切都跟预期的一样了。我们所做的全部就是告诉Windows所有的着色都发生在重载的OnPaint中(WmPaint来自于经典的Win32消息),而且我们的窗体不是透明的。这样就确保没有额外的着色从Windows发生,而且每件事情都将通过我们。或许你也注意到了,如果你调整窗口大小到不可见客户区,应用程序将抛出一个异常。如果这真的很烦扰你,你可以改变窗体的最小尺寸属性。
Managed DirectX(3)
Part I:Beginning Graphics Concepts
Chapter 1.Introducing Direct3D
多媒体应用程序很流行的一个方面就是它可以添加3D图形。今天随着现代的GPU(Graphics Processing Unit)强大的处理能力,逼真地场景可以实时显现了。你在玩最近的3D游戏时,有没有惊讶道:“它们是怎么做到的?”而没有想到它们很复杂呢?Managed Direct3D可以让开发人员利用一种简单快速的方式在屏幕上得到一个复杂的(或简单的)图形和动画。在这一章,我们将涉及以下内容:
? 创建一个默认的D3D设备
? 在自然空间和屏幕坐标中绘制一个三角形
? 在我们的三角形中添加一束光
Getting Started
在开始开发第一个Managed Direct3D应用程序前,我们需要把我们的运行环境准备好。按照下面的步骤来完成:
1. 我们要做的第一件事就是打开Visual Studio.NET,然后新建一个项目
2. 让我们选择Visual C#项目,创建一个新的Windows应用程序。我们需要一个地方来绘制图形,标准的Windows Form就很适合。
3. 给你的项目起一个你喜欢的名字,然后创建它。
在项目被创建之后,我们需要确保Managed DirectX引用已经添加到我们的项目中。我们可以通过添加组建来实现。在项目菜单中选择添加引用,添加Microsoft.DirectX和Microsoft.DirectX.Direct3D引用。目前,我们就需要这些了。
引用被添加到项目中后,我们就可以编写Managed DirectX代码了。但是在写代码之前,我们还需要添加两个using子句来使我们以后在用到这些的时候不必写完全限定名。你可以通过打开在你的应用程序得主要Windows Forms的代码窗口(一般默认是Form1.cs)的using子句的末尾来添加:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
这一步不是必要的,但是它可以节省你大量输入,因为你不必在每次使用它的时候写完全限定名。现在我们开始写我们的第一个Managed DirectX应用程序。
The Direct3D Device
所有的有关绘图的Direct3D类的根就是device类,你可以认为这个类就是你电脑中的真实图形设备,所有在你所绘制的场景中的的图形对象都是从这个类衍生出来的。
在这里,device有三个构造函数,现在,我们只准备使用其中的一个,在后面的章节中,我们将介绍另外的构造函数。
我们要使用的函数原型和参数说明如下:
public Device
(
System.Int32 adapter ,
Microsoft.DirectX.Direct3D.DeviceType deviceType ,
System.Windows.Forms.Control renderWindow ,
Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags, Microsoft.DirectX.Direct3D.PresentParameters presentationParameters
)
现在,这些参数的含义是什么呢,我们如何使用它?第一个参数“adapter”是我们想这个类表现的那个物理设备。在你的计算机中每一个设备都有唯一的适配标识符(通常是0当小于你所拥有的设备数)。Adapter 0总是默认的设备。
关于重载的Device构造函数 |
重载的Device与父基几乎是一样的,除了renderWindow这个参数以外,它为非托管(或者Non-Windows Forms)窗口句柄提供了一个变量IntPtr。这个单独的变量IntPtr为IDirect3DDevice9接口提供了一个非托管COM指针。它可以用在你的托管代码的非托管应用程序中。 |
第二个参数deviceType是告诉Direct3D你想创建什么类型的设备。一般使用DeviceType.Hardware,意思是你想创建一个硬件设备类型。还有一个选项是DeviceType.Reference,它允许你使用引用光栅,它可以执行Direct3D运行时的所有特效,但是运行起来相当慢。它主要被人们用来调试或者用来测试你的显卡不支持的应用程序的特性。
第三个参数renderWindow把一个窗口绑定到设备上。因为Windows窗体(Forms)容器类(Control Class)包含有窗口句柄(Window Handle),我们可以应用一个派生类(Derived Class)来绘制窗口。这个参数你可以用窗体(Form),面板(Panel)或者其他的派生容器类(Control-Derived Class)。现在,我们将用窗体。
使用软设备(Software Device) |
你应该注意到,引用光栅(reference rasterizer)参数只有在DirectX SDK中才有,所以只有DirectX运行时的将没有这个特性。renderWindow最后一个值是DeviceType.Software。它允许你自定义软件光栅(Software rasterizer)。除非你有一个自定义的软件光栅方案,否则忽略它。 |
第四个参数behaviorFlags被用来控制设备创建以后的状态。CreateFlags的大多数枚举成员都可以被马上组合成多个行为。有一些状态标识符是互斥的,我以后再说。现在我们只用到SoftwareVertexProcessing(软件顶点处理)这个标识符。该标识符表明所有的顶点处理都交给CPU。很明显,虽然这都交给GPU处理要慢,但是我们并不知道你的显卡是否支持这一特性,所以还是值得的。
最后一个参数presentationParameters控制如何把设备的这些数据呈现到屏幕上。设备图像参数的每一个方面都由这个类来控制。我们以后再进一步叙述这个结构。现在我们来讲述剩下的枚举成员:Windowed和SwapEffect。
Windowed是一个布尔值(Boolean),用来判断设备是全屏模式(false)还是窗口模式(true)。
SwapEffect用来控制交换缓冲状态的。如果你选择SwapEffect.Flip,运行时将创建一个临时后台缓冲区(extra back buffer),然后复制每一个变化到前台缓冲区去即时显示。SwapEffect.Copy跟Flip很相似,只是需要你把后台缓冲区的数量设置成1。我们现在将选择的选项是SwapEffect.Discard。它可以把你不准备呈现的缓冲区的内容简单删除。
Managed DirectX(2)
Managed DirectX API包含一组相关的命名空间集,每个命名空间都包含不同的函数。公共函数通常被包含在“Microsoft . DirectX”中。在Managed DirectX中完整的命名空间列表见表一:
表一. Managed DirectX Namespaces | |
Microsoft.DirectX | 父命名空间,包含了所有公共代码 |
Microsoft.DirectX.Direct3D | Direct3D图形API,D3DX帮助库 |
Microsoft.DirectX.DirectDraw | DirectDraw图形API. |
Microsoft.DirectX.DirectPlay | DirectPlay网络API. |
Microsoft.DirectX.DirectSound | DirectSound音频API. |
Microsoft.DirectX.DirectInput | DirectInput 用户输入API. |
Microsoft.DirectX.AudioVideoPlayback | 简单的音频和视频回放API. |
Microsoft.DirectX.Diagnostics | 简单的诊断API. |
Microsoft.DirectX.Security | DirectX代码访问安全的底层结构. |
Microsoft.DirectX.Security.Permissions | DirectX代码访问安全的权限类. |
就如你所看到的,上表包括了在DirectX中大多数函数。在本书中,我们主要学习Direct3D,但同时也会涉及其他部分的。
现在,我们已经知道了一些基础信息,我们有足够的信息可以开始编写一些Managed DirectX程序了。但是在开始编写之前,有些事情你应该知道。
首先,你需要一个代码编辑器和运行环境。我建议使用Visual Studio 2003,这也是微软公司所推荐的。不管你使用什么编辑器,你都需要.NET 1.1版运行环境,你可以在配套光盘中找到它的安装文件(如果你安装Visual Studio 2003,你就不用手动安装了,程序会自动安装到你的机器上)。
其次,你需要安装DirectX 9 SDK Update Developer Runtime。我建议你安装配套光盘中的DirectX 9 Software Development Kit Update,它不但包含DirectX 9 SDK Update Developer Runtime,还包括很多例子和Managed DirectX的文档。