我也知道我的文章并不是多么的不可或缺,没了这个翻译大家照样过,目前为止基本都是些入门类的东西,有时候我会想到曾小贤的午夜节目,那个夜晚被胡一非痛骂才明白原来自己做的并不是没有意义,消沉的这段时间,看到陆续还有几个网友留言鼓励我,我真的非常感谢你们,我不是什么大神,也不是什么天才,我高数重修了两遍才过,非是不努力而是真的智力不足学起来比别人要多花几倍的时间,但我只是一直努力着,欠了38个学分的时候我没有选择提前留级(如果学年结束时欠了36分以上会退学),我苦行僧般地过了下半学年勉强保住不留级(24学分),刚来上海的时候我是C盲(之前一直搞WEB),没有数据结构的基础不懂STL但我还是撑过来了,四个月以后轻松地完成工作,八个月以后我觉得我们的代码到处是问题,不管遇到怎样的困境,我从来没有放弃过,就像我经常消沉但我总会走出来结束太监状态,希望在看这篇翻译的你,无论在技术还是生活上,永远不要轻言放弃,不管在怎样的困境,只要有1%的希望,就值得我们拼尽全力去争取,人之所以能战胜神,是因为我们相信着的奇迹。
课程概述
有一个简单的方式来学习的DirectX。杯具的是,简单的方法往往有很多功能上的限制,即使没有,在一个比较大型的游戏使用它仍然会很麻烦。当然,更简单的 方法是,努力地学习,本教程会尽量写得更易于编程,以便不使理论的部分太艰深晦涩。在本节中,您将学习如何在屏幕上画一个三角形。我们将通过创造一系列顶点来构建三角并通过Direct3D设备在屏幕上绘制之。
首先,我们将涵盖如何工作的,那么我们将在代码本身和生成程序的理论。
灵活顶点格式(Flexible Vertex Formats)
如果您完成了第3课中的学习,你会记得顶点的定义:一个确定在在三维空间中的点的精确位置和属性。位置只包含三个坐标数值以描述顶点在空间中的位置。顶点的属性也用数值定义。
Direct3D使用一种称为灵活顶点格式(Flexible Vertex Format简称FVF)的技术。该技术中一个顶点格式包含一个顶点的位置和多个属性数据。一个FVF顶点会是一个格式化的数据,您可以根据您的需要修改和设置。让我们来看看究竟丫是如何工作的。
顶点可以用一个结构体来描述,它包含所有与创建该3D图像有关的数据。为了展示这个图像,我们要把所有的信息复制到视频RAM,然后让Direct3D来复制数据到后台缓冲区。然而,如果我们不得不把一个顶点所有可能需要的数据发送过去会怎样呢?如下图所示。
当然,你可能不会马上看到这儿有什么问题,但让我们不得不说我们只需要这些信息中的两块。我们可以看到将其发送到视频RAM会快得多,就像这样:
这就是在我们使用FVF时发生的。我们选择我们要使用的信息,并发送之,这样就可以使我们在每帧之间发送更多的顶点。
FVF 代码
在Direct3D中,每个顶点都是预先设定好的顶点格式。正如标题声称,这种格式是灵活的,是使用的Direct3D提供的某些元素。这些元素通过使用特殊标识来设置,比如何时逻辑或在一起,创建一个顶点的定义,或一个告诉Direct3D顶点格式代码的。
让我们来看看它如何做到这一点。比方说,我们希望包括位置和我们的顶点漫反射颜色。我们将建立一个代码:
稍后,我们将简单地使用CUSTOMFVF,而不是每次都输出整个FVF代码。我们将在一分钟后来看这样的一个例子。
我们可以在这个表达式里添加各种标识。以下是一组贯穿本套教程始终的标识表,以及他们做什么的说明。
[Table 4.1 - FVF Code Flags]
标识 |
描述 |
参数类型 |
D3DFVF_XYZ |
表示该顶点格式包括X,Y和Z的一个未转换的顶点坐标。未转化意味着顶点尚未转化为屏幕坐标。 |
float, float, float |
D3DFVF_XYZRHW |
表示该顶点格式包括X,Y和Z坐标以及一个转换过的额外的RHW值。这意味着顶点已经在屏幕坐标系。Z和RHW会在构建软件引擎时使用,我们不会进入。 |
float, float, float, float |
D3DFVF_DIFFUSE |
表示顶点格式包含一个32位颜色代码,用于表示漫射光颜色。 |
DWORD |
D3DFVF_SPECULAR |
表示顶点格式包含一个32位颜色代码,用于镜面高亮使用的颜色。 |
DWORD |
D3DFVF_TEX0 |
表示顶点格式包含任何将被应用到模型纹理坐标。 |
float, float |
[Close Table] |
|
|
当然了,还有更多的标识没有被提及,它们已经是包含在DirectX文档中了。而我们只需要本教程中使用到的这些标识。
创建顶点
现在我们需要使用我们的新格式创建顶点。我们不使用任何新函数或诸如此类的东西,我们通过建立一个简单的结构体装入FVF代码中包含的变量。
例如,我们用在上面的例子用D3DFVF_XYZRHW和D3DFVF_DIFFUSE两个标识一起的话,我们应该建立如下结构:
{
FLOAT x, y, z, rhw; // from the D3DFVF_XYZRHW flag
DWORD color; // from the D3DFVF_DIFFUSE flag
}
如你所见,前四个浮点数是D3DFVF_XYZRHW标识的值,而DWORD是D3DFVF_DIFFUSE标识的值。如果您看看上表,你会发现这其中的变量类型与FVF标识。
现在,让我们使用我们的新CUSTOMVERTEX结构来构建一个实际的顶点。我们可以像下面这样做:
当然了,你也可以弄个顶点数组,就像这样:
{
{320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255),},
{520.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 255, 0),},
{120.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0),},
} ;
结果会是一个三角形,我们很快会在屏幕上看到。
这仅仅是一个灵活顶点格式的示例。我们稍后会继续就如何建立更复杂的顶点格式,恩,就从现在开始吧。
顶点缓存
现在,我们已经完成了两件事情。首先,我们建立了FVF代码。第二,我们已经创建了一个三角形。现在我们需要得到那个三角形以准备给Direct3D使用。要做到这一点,我们创建了所谓的顶点缓冲区。
一个顶点缓冲区是一个简单的接口,它存储在一段内存中(显存或内存),以持有你的游戏中顶点或模型的信息。我们通过函数CreateVertexBuffer()来创建这个接口。不过它的参数有点复杂。下面是函数原型:
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer,
HANDLE * pSharedHandle);
我们来逐个分析一下这些个参数
UINT Length,
这个参数包含将要创建的顶点缓存的大小。我们得到将存储在缓冲区中的顶点数目乘以一个顶点大小的结果。例如,一个三角形包含三个顶点,因此该三角形的缓冲区大小是:3* sizeof(CUSTOMVERTEX)。
DWORD Usage,
有时候,有些特殊的方法能改变DirectX处理的顶点集。在本教程我们不会深入讨论这些。这个参数可以包含标志表明这些特殊的方式。因为我们不会使用任何所以我们只要把它设置为0。
DWORD FVF,
这是FVF代码中我们接触得比较早的。我们只需填写CUSTOMFVF。如果我们要改变FVF代码,这部分也将改变(改变那个#define)。这是告诉DirectX顶点是什么格式了,这个参数很重要。
D3DPOOL Pool,
这个参数告诉Direct3D在哪创建顶点缓存以及如何创建之。以下是一个表,说明该参数的可能的条目。在本教程中,我们将使用标志D3DPOOL_MANAGED。
[Table 4.2 - D3DPOOL Values]
值 |
描述 |
D3DPOOL_DEFAULT |
此标志表示该缓存应该创建在最适当的内存以便设置和资源可用。然而,强加一些限制并不总是对游戏有好处。 |
D3DPOOL_MANAGED |
这表明,缓冲将在位于显存 |
D3DPOOL_SYSTEMMEM |
这表明,缓存将在系统内存中。顶点缓存设在这里通常不能由Direct3D设备访问,但可以通过其他更高级的手段访问。 |
D3DPOOL_SCRATCH |
这也表明了缓存将坐落在系统内存中,但是,又没有办法让显存访问它。这种类型一般用于存储图像信息,现在我们用不到(但稍后将使用),比如屏幕已经转到另一个地图但角色暂时还没有到达(不久后就到)。 |
[Close Table] |
|
LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer,
如果你理解它,那我告诉你它是我们正在创建的顶点缓存的接口。我们给这个参数一个空指针,函数会给他赋值。
HANDLE* pSharedHandle
文档里说过这个参数,我引述一下:“保留。设置这个参数为NULL。“我不能说肯定,但显然微软希望我们将其设置为NULL。就这样吧。我们将它设置为NULL。
看完这些参数,让我们来看看这个功能在我们的程序中如何写:
d3ddev -> CreateVertexBuffer( 3 * sizeof (CUSTOMVERTEX),
0 ,
CUSTOMFVF,
D3DPOOL_MANAGED,
& v_buffer,
NULL);
现在您已经创建了顶点缓存,下面你需要把顶点加载进去。你用一个简单的函数memcpy()就可以完成。但是,在您访问到顶点缓存之前,你需要将其锁定。
基于两个原因,你需要锁定这个缓存。首先,你需要告诉Direct3D你需要内存的完全控制权。换句话说,它不应该由任何可能的其他进程使用。第二,你需要告诉视频硬件别动它。否则可没法保证显存保持不动。锁定告诉显存与内存当你正在使用它时别干蠢事儿。
要锁定一个缓冲区,可以使用函数Lock(),它有四个参数,但都相当简单:
UINT SizeToLock,
VOID ** ppbData,
DWORD Flags);
让我们来看看这些参数:
UINT OffsetToLock, UINT SizeToLock,
如果我们只是想锁定我们的顶点缓冲的一部分,我们将表明在这两个参数。第一个指示自缓存的起始点到开始锁定位置的偏移量,以字节为单位。第二个表示多少字节被锁定。因为我们现在要锁定整个缓冲区,所以我们将设置这些都为0。
VOID** ppbData,
除非你是至少中等水平以上的C+ +,否则这个参数您老还是别动了。基本上这是一个void*指针并且没有指向特定类型的变量。例如,一个**double,而一个int*指向一个int。每个指针类型都有它自己的格式,因此它不能转换不会丢失数据为另一种类型。但是一个void *指针,却可以转换为任何类型。
在这里,我们有一个空指针*.这个指针被锁定的存数去起始位置的指针。顶点缓冲区接口会处理其细节,但我们在下一步会需要这个指针。所以函数lock()函数将填充恰当的地址给这个指针。有一个在下面的例子,看看我们如何填补这个参数。
DWORD Flags
这是一个很高级的参数,我们不会在本教程中深入。本上它提供特殊的方式来处锁定内存。如果你是真正感兴趣的,它们可以研究在DirectX文档。现在,我们只是将它设置为0。
让我们来补完这个函数,看看它的样子:
v_buffer -> Lock( 0 , 0 , ( void ** ) & pVoid, 0 ); // locks v_buffer, the buffer we made earlier
接下来,我们调用memcpy()复制顶点到顶点缓存。
最后,我们还有一个非常复杂的函数:Unlock()。这个函数没有参数。它所做的是告诉Direct3D,跟内存搞完了,解开束缚吧。写起来像这样:
由于我们刚刚得知命令的数量,我们要把我们自己创建的功能放进一个单独的函数里init_graphics()。
{
// create three vertices using the CUSTOMVERTEX struct built earlier
CUSTOMVERTEX vertices[] =
{
{ 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
{ 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
{ 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
};
// create the vertex and store the pointer into v_buffer, which is created globally
d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
0,
CUSTOMFVF,
D3DPOOL_MANAGED,
&v_buffer,
NULL);
VOID* pVoid; // the void pointer
v_buffer->Lock(0, 0, (void**)&pVoid, 0); // lock the vertex buffer
memcpy(pVoid, vertices, sizeof(vertices)); // copy the vertices to the locked buffer
v_buffer->Unlock(); // unlock the vertex buffer
}
在这里,我建议您多读几次以确保你彻底明白了这一切。这是3D编程中一个相当关键的的部分,我们将在教程的休息时间使用和修改它。
绘制图元
现在,我们实际上已经获得屏幕上的内容!但在此发生之前我们有三个非常简单的函数要认识一下。每一个被从Direct3D设备接口调用。让我们来看看。
SetFVF()
这些函数的第一个是SetFVF()。 SetFVF()是一种告诉Direct3D使用当前使用什么样的FVF代码的函数。我们当然可以有多个FVF代码并使用在两个不同的三维场景。在我们开始画之前,我们只需要告诉我们的Direct3D使用的是哪一个。这个函数写出来是这样的:
SetStreamSource()
下一步,我们的函数SetStreamSource(),它告诉Direct3D我们从哪个顶点缓冲取顶点。这其中有一对参数,让我们来看看函数原型:
LPDIRECT3DVERTEXBUFFER9 pStreamData,
UINT OffsetInBytes,
UINT Stride);
第一个参数是要用的顶点缓存的序号。我们稍后深入它的工作原理,但现在将它设置为0,因为我们只有一个顶点缓冲区。
第二个参数是指向我们先前创建的顶点缓存的指针。
第三个参数指应该从顶点缓冲的第几个字节开始。这通常是0。
最后一个参数是每个顶点的大小。我们填写如下: SizeOf(CUSTOMVERTEX)。
让我们来看看在函数怎样被调用:
DrawPrimitive()
现在我们已经告诉Direct3D我们正在使用哪种格式的顶点以及哪里得到它们,我们告诉它请绘制我们已经建立的顶点。这个函数绘制在选定的顶点缓存的图元到屏幕上。这里是函数原型:
UINT StartVertex,
UINT PrimitiveCount);
第一个参数是使用的图元类型。这些在第3课已经讲过了,但所用的代码在这里:
[Table 4.3 - D3DPRIMITIVETYPE Values]
Value |
Description |
D3DPT_POINTLIST |
|
D3DPT_LINELIST |
|
D3DPT_LINESTRIP |
|
D3DPT_TRIANGLELIST |
|
D3DPT_TRIANGLESTRIP |
|
D3DPT_TRIANGLEFAN |
|
[Close Table] |
|
第二个参数是我们要绘制在屏幕上的第一个顶点的序号。如果我们愿意,我们可以在顶点缓冲区的中间开始。但是,现在我们希望绘制整个缓冲区,因此我们将在这里把0。
第三个也是最后一个参数是我们要绘制图元的数量。如果我们要画一个三角形,我们设1(只有一个三角形)。如果我们画点,我们会设为3,有三点。画线条也是设为3。
现在让我们来看看整个render_frame()函数,现在我们已经修改了它。
void render_frame( void )
{
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->BeginScene();
// select which vertex format we are using
d3ddev->SetFVF(CUSTOMFVF);
// select the vertex buffer to display
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
// copy the vertex buffer to the back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
}
在看整个程序之前,先来看看最后一个步骤是很有必要的。
释放顶点缓存
就像Direct3D设备和Direct3D一样,顶点缓冲区也必须在程序关闭之前被释放。
void cleanD3D( void )
{
v_buffer->Release(); // close and release the vertex buffer
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
}
现在让我们来看看整个程序,看看我们都干了什么。
最终代码
好吧,让我们来看看这个三角长啥样。如果你从来没有见过三角形,这将是一个教育的经验。否则你可以看到一个三角形是made in Direct3D。
无论如何,让我们来看看最后的DirectX代码。在本课程中所涵盖的新的部分像往常是加粗的。
2 #include < windows.h >
3 #include < windowsx.h >
4 #include < d3d9.h >
5
6 // define the screen resolution
7 #define SCREEN_WIDTH 800
8 #define SCREEN_HEIGHT 600
9
10 // include the Direct3D Library file
11 #pragma comment (lib, " d3d9.lib " )
12
13 // global declarations
14 LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
15 LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
16 LPDIRECT3DVERTEXBUFFER9 v_buffer = NULL; // the pointer to the vertex buffer
17
18 // function prototypes
19 void initD3D(HWND hWnd); // sets up and initializes Direct3D
20 void render_frame( void ); // renders a single frame
21 void cleanD3D( void ); // closes Direct3D and releases memory
22void init_graphics(void); // 3D declarations
23
24struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;} ;
25#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
26
27 // the WindowProc function prototype
28 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
29
30
31 // the entry point for any Windows program
32 int WINAPI WinMain(HINSTANCE hInstance,
33 HINSTANCE hPrevInstance,
34 LPSTR lpCmdLine,
35 int nCmdShow)
36 {
37 HWND hWnd;
38 WNDCLASSEX wc;
39
40 ZeroMemory(&wc, sizeof(WNDCLASSEX));
41
42 wc.cbSize = sizeof(WNDCLASSEX);
43 wc.style = CS_HREDRAW | CS_VREDRAW;
44 wc.lpfnWndProc = WindowProc;
45 wc.hInstance = hInstance;
46 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
47 wc.lpszClassName = L"WindowClass";
48
49 RegisterClassEx(&wc);
50
51 hWnd = CreateWindowEx(NULL,
52 L"WindowClass",
53 L"Our Direct3D Program",
54 WS_OVERLAPPEDWINDOW,
55 0, 0,
56 SCREEN_WIDTH, SCREEN_HEIGHT,
57 NULL,
58 NULL,
59 hInstance,
60 NULL);
61
62 ShowWindow(hWnd, nCmdShow);
63
64 // set up and initialize Direct3D
65 initD3D(hWnd);
66
67 // enter the main loop:
68
69 MSG msg;
70
71 while(TRUE)
72 {
73 while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
74 {
75 TranslateMessage(&msg);
76 DispatchMessage(&msg);
77 }
78
79 if(msg.message == WM_QUIT)
80 break;
81
82 render_frame();
83 }
84
85 // clean up DirectX and COM
86 cleanD3D();
87
88 return msg.wParam;
89}
90
91
92 // this is the main message handler for the program
93 LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
94 {
95 switch(message)
96 {
97 case WM_DESTROY:
98 {
99 PostQuitMessage(0);
100 return 0;
101 } break;
102 }
103
104 return DefWindowProc (hWnd, message, wParam, lParam);
105}
106
107
108 // this function initializes and prepares Direct3D for use
109 void initD3D(HWND hWnd)
110 {
111 d3d = Direct3DCreate9(D3D_SDK_VERSION);
112
113 D3DPRESENT_PARAMETERS d3dpp;
114
115 ZeroMemory(&d3dpp, sizeof(d3dpp));
116 d3dpp.Windowed = TRUE;
117 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
118 d3dpp.hDeviceWindow = hWnd;
119 d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
120 d3dpp.BackBufferWidth = SCREEN_WIDTH;
121 d3dpp.BackBufferHeight = SCREEN_HEIGHT;
122
123 // create a device class using this information and the info from the d3dpp stuct
124 d3d->CreateDevice(D3DADAPTER_DEFAULT,
125 D3DDEVTYPE_HAL,
126 hWnd,
127 D3DCREATE_SOFTWARE_VERTEXPROCESSING,
128 &d3dpp,
129 &d3ddev);
130
131 init_graphics(); // call the function to initialize the triangle
132}
133
134
135 // this is the function used to render a single frame
136 void render_frame( void )
137 {
138 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
139
140 d3ddev->BeginScene();
141
142 // select which vertex format we are using
143 d3ddev->SetFVF(CUSTOMFVF);
144
145 // select the vertex buffer to display
146 d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX));
147
148 // copy the vertex buffer to the back buffer
149 d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
150
151 d3ddev->EndScene();
152
153 d3ddev->Present(NULL, NULL, NULL, NULL);
154}
155
156
157 // this is the function that cleans up Direct3D and COM
158 void cleanD3D( void )
159 {
160 v_buffer->Release(); // close and release the vertex buffer
161 d3ddev->Release(); // close and release the 3D device
162 d3d->Release(); // close and release Direct3D
163}
164
165
166// this is the function that puts the 3D models into video RAM
167void init_graphics(void )
168 {
169 // create the vertices using the CUSTOMVERTEX struct
170 CUSTOMVERTEX vertices[] =
171 {
172 { 400.0f, 62.5f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
173 { 650.0f, 500.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
174 { 150.0f, 500.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
175 };
176
177 // create a vertex buffer interface called v_buffer
178 d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
179 0,
180 CUSTOMFVF,
181 D3DPOOL_MANAGED,
182 &v_buffer,
183 NULL);
184
185 VOID* pVoid; // a void pointer
186
187 // lock v_buffer and load the vertices into it
188 v_buffer->Lock(0, 0, (void**)&pVoid, 0);
189 memcpy(pVoid, vertices, sizeof(vertices));
190 v_buffer->Unlock();
191}
192
来吧,更新您的程序,让我们看看我们得到的。如果你运行这个,你应该在屏幕上看到以下内容:
如果你看到这样的三角,那么恭喜你!你成功了。当然,后面还有更多,在开始下一课之前我建议你试着做做下面的小练习,更可让你更熟悉程序。
1。改变三角形的颜色。
2。在程序运行时改变三角的形状。
3。将一个点的颜色褪色成另一个点的颜色。
当然,你可能会失望地发现,这一个三角形算哪门子三维啊?让我们继续下一课的学习,并使其通过旋转,调整大小和移动让你感觉到它确确实实在三维空间中。
下一课:转变顶点
英文原文:http://www.directxtutorial.com/tutorial9/b-direct3dbasics/dx9B4.aspx
Translate By 王大宝(OneDouble.net)