本文档主要记录在visual studio 2010下配置DirectX SDK (June 2010)后,在MFC程序中如何搭建Direct3D程序框架并介绍基本绘图步骤。
Direct3D程序框架可以被分为Direct3D初始化和Direct3D线程:主程序界面(绘图)两部分。
1) 获取IDirect3D9接口:创建IDirect3D9对象并将IDirect3D9接口返回给此对象(或者说此对象实现了IDirect3D9接口)。
2) 获取设备信息,检测硬件顶点处理方式:用接口内中的方法检测显卡设备是否支持硬件顶点处理,若不支持则使用软件顶点处理。
3) 填充D3DPRESENT_PARAMETERS结构:设定将要创建的IDirect3DDevice对象的特性。
4) 创建IDirect3DDevice对象: IDirect3DDevice以创建IDirect3DDevice对象函数的参数返回,通过此对象(给予地址)可以进行绘图。一般是获取所创建对象的地址。
由Direct3D初始化步骤可知,最终是要创建一个IDirect3DDevice对象。由于Direct3D符合COM标准,一旦提到“对象”便可想到它是由一种类定义得来,其必包含“数据成员”和“函数成员”。由COM的实质(由C++类描述),被定义的“对象”将会用“数据成员”和“函数成员”表明其属性和操作[比如一个C++形状对象,其内必有标明形状的数据成员,必有实现其形状的函数成员]。如今,我们得到一个IDirect3DDevice对象,它可能具有画笔、颜色种类等数据成员,对应着画图的各种方法。这就是得到的一个对象的目的,只有这个对象才具备D3D某种绘图功能(数据 + 函数)。创建IDirect3DDevice对象之前的步骤是铺垫和准备,对参数的设置对应着D3D的某种特定模式。【这段是个人牢骚,也随便记录点年轻时候的感慨^-^(这个笑脸是别人教的^-^)】
Direct3D程序界面无论是窗体型还是全屏型都需要不断刷新屏幕来显示界面。如此可以设一个循环来不断来刷新屏幕,但对于MFC架构来说,由于封装性我们已经不能将需要不断循环的那部分加入到MFC的主程序循环中了。为此对于MFC Direct3D界面绘图部分,可以单一的开一道线程来实现不断刷新屏幕的需求。
此项目基于MFC对话框,为了简单在创建项目时可以不勾选About box项,建立好项目后。为项目增添新的头文件D3D9App.h和新源程序文件D3D9App.cpp。在对话框头文件中添加IDirect3DDevice指针变量,设为public型,用于指向创建的IDirect3DDevice对象。在D3D9App.h文件中申明D3D初始化程序函数和D3D主程序界面实现函数,在D3D9App.cpp文件中定义相应的函数。在对话框源程序文件的OnInitDialog函数中调用D3D初始化程序,此时将IDirect3DDevice指针变量作为参数传入D3D初始化函数中,伺机获取被创建的IDirect3DDevice对象的地址。将并为D3D主程序界面实现函数开启一道线程,不断刷新D3D界面。
编者可以将Direct3D初始化步骤任意的模块化,只要将需要的接口返回来即可。以下结构将初始化步骤编写在InitD3D9一个函数中。
LPDIRECT3D9 pD3D9 = NULL; if( !(pD3D9 = Direct3DCreate9(D3D_SDK_VERSION) ) ) { ::MessageBox(NULL, _T("IDirec3D9 object failed"), _T("HI"), MB_OK); return E_FAIL; }
创建IDirect3D9对象的函数为Direct3DCreate9,此函数参数唯一。若函数执行成功则将创建的IDirect3D9对象地址返回。
检测设备信息由IDirect3D9接口函数GetDeviceCaps实现,注意此函数的第三个参数不可设置为“D3DCAPS9 *”类型,否则将会编译出错,此参数将返回设备信息。如果检测到的顶点处理方式记录下来,备作为创建IDirect3DDevice对象时的参数使用。
作为一个完整的框架,接下来还可以设置D3D程序是为窗体模式还是为全屏模式,如果设置D3D程序为窗体模式则用GetClientRect函数获取D3D程序父窗体的尺寸,此函数的第一个参数需要窗体的句柄(获取一个窗体的句柄有两种方法:一是直接使用此窗体类型对象的数据成员m_hWnd,二是利用GetDlgItem、GetSafeHwnd获取),第二参数即可返回窗体客户区的大小。如果设置为全屏方式则需要用GetSystemMetrics函数获取系统屏幕信息(这里就需要注意句柄的获取了,否则D3D程序初始化有可能会失败)。不管是窗体模式还是全屏模式,目的都是为了获取到D3D窗体或全屏的大小及句柄,以为接下来要设置的D3DD3DPRESENT_PARAMETERS结构做好铺垫。
填充此结构时,有三个成员值需要手动获取。
成员 |
对应值 |
BackBufferWidth |
上一步提到的父窗口客户区宽度或全屏宽度 |
BackBufferHeight |
上一步提到的父窗口客户区高度或全屏高度 |
hDeviceWindow |
显示设备输出窗口的句柄(上一步提到的方式可获取),全屏时句柄尚未知如何获取 |
创建IDirect3DDevice对象使用CreateDevice函数,此函数是IDirect3D9对象接口函数,第3个参数是显示设备输出窗口的句柄,第3个参数是顶点处理方式,第5个参数是填充好的D3DPRESENT_PARAMETERS结构,第6个参数返回创建的IDirect3DDevice对象的地址,终于获取到IDirect3DDevice对象的地址了。由列举的这几个参数可知,前面几步也是必不可少的,万事具备之时,不仅有只欠东风之时,也有欠CreateDevice函数之点。
好了,如果不出意外,D3D初始化已经完毕了。您可以使用IDirect3DDevice对象来进行D3D图像绘制了。
由前面提到的D3D界面需要不断刷新之故,在对话框源程序文件中使用AfxBeginThread函数专为D3D绘制函数开启一道线程。然后在此线程中实现D3D界面不断刷新和绘制工作。
跟其它程序的主程序循环一样,此界面刷新实现依旧采用死循环实现。这似乎是程序图形界面不可缺少的法宝了。
while(1) { HRESULT hr; //D3D surface will disappear if has not this code if( pDevice ) { //Reports the current cooperative-level status of the Direct3D device //for a windowed or full-screen application hr = pDevice->TestCooperativeLevel(); if( FAILED( hr ) ) { if( hr == D3DERR_DEVICELOST ) { continue; } else if( hr == D3DERR_DEVICENOTRESET ) { //Reset } continue; } } if (SUCCEEDED(pDevice->BeginScene()) ) { //Draw something else pDevice->EndScene(); } pDevice->Present(NULL, NULL, NULL, NULL); }
根据D3D界面的“性格”,在刷新D3D数据之前需要用IDirect3DDevice对象接口函数TestCooperativeLevel来检测当前的设备是否可用,并返回系列错误,根据这些错误适当添加一些复位代码就可以将使设备恢复正常,只有等设别恢复正常之后,咱们才可以在IDirect3DDevice对象接口函数BeginScene和EndScene之间Draw something else,如绘制一个三角形,然后还用调用IDirect3DDevice对象接口函数Present来显示界面内容。
Direct3D初始化完成了,界面不断刷新实现了,那么展现的数据(图像)在哪里呢?做了那么多不就是为了显示D3D图像的么。画个图入个门吧,如果大家追求安稳性,那还不选三角形。
1) 准备绘图的数据,并依据准备好的绘图数据信息(大小等)创建顶点缓冲区(所谓缓冲区就是将这些数据存到某个地方吧,不知道的我就猜了再查了)。
2) 访问在缓冲区内存:将准备好的绘图数据存入创建的缓冲区内存中。
3) 设置渲染状态:觉得几何物体怎么样被渲染(?给画好的图涂颜色的方式,我想差不多)
4) 绘制准备:设置资源流(?反正是准备)、设置顶点格式、设置索引缓冲区(若有索引数据)
5) 绘制。
1)定义绘图数据并创建顶点缓冲区
还记得C语言中式怎么定义一个结构体的么,里面包含整形,浮点型,这个绘图数据就是这些。
typedef struct CUSTOMVERTEX { FLOAT x, y, z; FLOAT rhw; D3DCOLOR color; }_CUS_D3D9Vertex; _CUS_D3D9Vertex CUS_D3D9Vertex[] = { {150.0f,50.0f,0.5f,1.0f,D3DCOLOR_XRGB(255, 0, 0)}, {250.0f,250.0f,0.5f,1.0f,D3DCOLOR_XRGB(0, 255, 0)}, {50.0f,250.0f,0.5f,1.0f,D3DCOLOR_XRGB(0, 0, 255)} };
创建顶点缓冲区肯定是用IDirect3DDevice对象接口函数CreateVertexBuffer(第一个参数是绘图数据在内存中的大小,倒数第2个参数数返回所创缓冲区的地址,所以需要先定义绘图数据)来实现了,我就说这个对象肯定有什么作用。
2)访问缓冲区内存
数据缓冲区已经在上一步创立好了,所以需要访问。当然了是使用IDirect3DDevice对象接口函数Lock访问。然后将绘图数据拷贝到缓冲区中,缓冲区就有了绘图的数据了。
3)设置渲染状态
设置渲染状态使用IDirect3DDevice对象接口函数SetRenderState来实现,就是设置怎么涂图画,参数含义需要读懂手册。这个是不是该看英文功底和计算机素养了。
4)绘制准备
A 设置资源流
使用IDirect3DDevice对象接口函数SetStreamSource来设置,此函数的第2个参数是指向缓冲区的指针,第4个参数是缓冲区绘图数据的大小。
B 设置顶点格式
设置顶点格式是指根据顶点绘图数据的要求(若顶点绘图数据中包含了颜色,则顶点格式需要包含支持绘制颜色这一项)。参数手册中查看。
C设置索引缓冲区
无索引缓冲区,无需设置。
5)绘制
待所有的准备工作都准备好之时就可以调用IDirect3DDevice对象接口函数DrawPrimitive进行绘制了。此接口函数第1个参数表示绘制图形的形式,第2个参数表示绘制图形的起始顶点,第3个参数表示绘制个数。参数含义可自行领会,说得也不一定准确。
再次强调,绘制三角形的操作是在以下Draw something else 部分完成的。其中pDevice为创建的IDirect3DDevice对象的地址。
if (SUCCEEDED(pDevice->BeginScene()) ) { //Draw something else pDevice->EndScene(); } pDevice->Present(NULL, NULL, NULL, NULL);
为了体现以下渲染的概念,在这里显示两个运行结果。
在设置渲染状态这一步时使用如下语句(pDevice为创建的IDirect3DDevice对象的地址):
pDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
图1 线框模式渲染物体
同上,在相同的地方添置语句
pDevice->SetRenderState(D3DRS_LIGHTING,FALSE);
图 2 关闭光源渲染图画
Introduction.To.Direct3D9中文版(翁云兵版)^-^还有参考文献呢!
此次笔记记录完毕。