发信人: laical(阿凡) 
整理人: wenbobo(2002-07-10 16:43:27), 站内信件
 | 
 
 
MFC架构下的DirectX8
 第一章 MFC框架
 (DX8MFC)
 这里的MFC框架指的是一个符合游戏开发应用的框架,当然你也可以写一个符合你要求的MFC框架。如果你对MFC比较熟悉的话可以直接从第二章开始阅读。本框架是以后几个例子的基础,如果你对MFC不是很了解的话,就要认真阅读本章,以求对这个MFC框架有一个深入的了解。
 框架中包括两个类:
 CDX8MFCApp类和CFrameWin类,CDX8MFCApp类是应用程序类,CFrameWin类是框架的主类,以后我们的大部分代码都是从这里扩展的。首先来看一看CDX8MFCApp类,它包括CDX8MFCApp()、ExitInstance()、InitInstance()、OnIdle(LONG lCount)等成员函数和一个Game对象。
 InitInstance()成员函数在程序初始化时就被调用,在这里我建立了一个窗口:
 BOOL CDX8MFCApp::InitInstance()
 {
 	// The one and only window has been initialized, so show and update it.
 	m_pMainWnd = new CFrameWin();
     m_pMainWnd->ShowWindow(m_nCmdShow);
     m_pMainWnd->UpdateWindow();
 	Game = (CFrameWin*) m_pMainWnd;
 	Game->Init();
 
 	return TRUE;
 }
 
 ExitInstance()成员函数在程序终止时被调用,在这里我们释放一些对象和指针:
 int CDX8MFCApp::ExitInstance() 
 {
 	// TODO: Add your specialized code here and/or call the base class
 	Game->End();
 	delete Game;
 
 	return CWinApp::ExitInstance();
 }
 
 OnIdle(LONG lCount)成员函数会在没有Windows消息要处理的时候被调用,也就是说OnIdle()成员函数会不断的被调用,这正好被我们用作游戏循环。
 BOOL CDX8MFCApp::OnIdle(LONG lCount) 
 {
 	// TODO: Add your specialized code here and/or call the base class
 	if(Game->window_active==TRUE)
 	{
 		Game->Active();
 		Game->window_active=FALSE;
 	}
 
 	Game->Go();
 
 	return TRUE;
 }
 
 Game对象是一个CFrameWin类指针,我们在InitInstance()成员函数中创建了一个CFrameWin对象并把CFrameWin对象的指针值赋给Game。
 下面我们来看一看CFrameWin类,它包括Active()、End()、Go()、Init()、Update()等成员函数。
 Init()成员函数,你可以在这里做一些自己的初始化。回顾CDX8MFCApp类的InitInstance()成员函数可知,在完成窗口初始化后InitInstance()成员函数里就调用了Game->Init(),也就是说Init()在窗口初始化后被调用。
 void CFrameWin::Init()
 {
 	AfxMessageBox("Init");
 }
 
 Go()成员函数会不断的被循环调用,它又调用了Update()和DestroyWindow()。Update()用于更新窗口,调用DestroyWindow()则会结束应用程序。如果你把DestroyWindow()语句删除掉,程序会不断的循环。
 void CFrameWin::Go()	//Game循环
 {
 	AfxMessageBox("Go");
 	Update();
 	DestroyWindow();
 }
 
 Active()成员函数会在应用程序被击活的时候被调用。
 void CFrameWin::Active()	//窗口被激活
 {
 	TRACE("Active\n");
 	AfxMessageBox("Active");
 }
 
 这个程序并不做任何事,只是一个MFC框架。你可以从http://gamedev.363.net 下载例子的源程序,或通过E-mail: [email protected] 向本文作者索取。
 
 第二章 初始化DirectX8
 (DX8MFC1)
 本例将以第一章的MFC框架为基础对CFrameWin类进行扩展。主要加入了DrawScene()、InitDirect3D(HWND hwnd)和ShutdownDirect3D()三个函数。
 InitDirect3D(HWND hwnd)函数对Direct3D进行初始化:
 HRESULT CFrameWin::InitDirect3D(HWND hwnd)
 {
     pID3D = Direct3DCreate8(D3D_SDK_VERSION);
 
     HRESULT hr;
     do
     {
         // we need the display mode so we can get
         // the properties of our back buffer
         D3DDISPLAYMODE d3ddm;
         hr = pID3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
         if(FAILED(hr))
             break;
 
         D3DPRESENT_PARAMETERS present;
         ZeroMemory(&present, sizeof(present));
         present.SwapEffect       = D3DSWAPEFFECT_COPY;
         present.Windowed         = TRUE;
         present.BackBufferFormat = d3ddm.Format;
 
         hr = pID3D->CreateDevice(D3DADAPTER_DEFAULT,
                                  D3DDEVTYPE_HAL,
                                  hwnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &present,
                                  &pID3DDevice);
 
         if(FAILED(hr))
             break;
 
         // we do our own coloring, so disable lighting
         hr = pID3DDevice->SetRenderState(D3DRS_LIGHTING,
                                          FALSE);
 
     } while(0);
 
     return hr;
 }
 
 IDirect3D是我们首先要用到的接口,你可以这样写:
 IDirect3D8 * pID3D = Direct3Dcreate8(D3D_SDK_VERSION);
 在你使用pID3D以前,请检查pID3D是否为非空。
 你下一步通常是创建D3D设备,但在创建D3D设备之前你要调用GetAdapterDisplayMode方法取得必须的信息:
 D3DDISPLAYMODE d3ddm;
 pID3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);
 接下来是取得当前显示模式参数。下面的参数是Surface格式。你可以用这些参数来创建一个D3DPRESENT_PARAMETERS结构:
 D3DPRESENT_PARAMETERS present;
 ZeroMemory(&present, sizeof(present));
 present.SwapEffect              = D3DSWAPEFFECT_COPY;
 present.Windowed                = TRUE;
 present.BackBufferFormat        = d3ddm.Format;
 D3DPRESENT_PARAMETERS描述了显示器Surface的信息,交换机制的类型,应用程序是窗口的还是全屏模式等信息。
 在本例中,Surface是以拷贝方法代替页面翻转的,因为它是一个窗口模式的应用程序。把后台表面设置成与当前显示模式相匹配的格式,一个准备显示的Surface可以Draw在后台表面上。
 
 现在你可以创建一个IDirect3DDevice8接口了:
 pID3D->CreateDevice(D3DADAPTER_DEFAULT,
                     D3DDEVTYPE_HAL,
                     hwnd,
                     D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                     &present,
                     &pID3DDevice);
 这个函数有六个参数,幸运的是没有一个是很复杂的。D3DADAPTER_DEFAULT告诉Direct3D使用主显示器,只有当你使用多显示器时才是必须的。你可以使用一个数值来指定另外一个显示器。调用IDirect3D的GetAdapterCount将返回系统的适配器数目。
 第二个参数,D3DDEVTYPE_HAL,告诉Direct3D使用硬件加速。其它选项包括D3DDEVTYPE_REF 和 D3DDEVTYPE_SW,通常你都会希望使用硬件加速的,但有时侯你可能会使用软件加速进行测试。
 指定窗口取得焦点。如果是全屏应用程序,你需要一个最顶层窗口。
 D3DCREATE_SOFTWARE_VERTEXPROCESSING指定顶点处理类型。你也可以使用硬件加速或是联合类型,我不使用硬件加速为的是广泛的兼容性。如果你想支持T&L,则你必须使用硬件加速。
 最后两个参数很简单,一个是你以前建立的,而pID3Ddevice是你现在要创建的IDirect3DDevice8接口。如果方法返回D3DERR_NOTAVAILABLE,则你写的参数是正确的,但你的设备不支持你指定的参数。
 最完美的是这个方法自动为你创建后台缓冲(back buffers)和深度缓冲(depth buffers)。剪裁(Clipping)作为后台表面(backface culling)被自动激活。灯光也被自动激活了,直到你定义顶点颜色之前,你可以禁止使用灯光:
 pID3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
 
 DrawScene()函数:
 HRESULT CFrameWin::DrawScene()
 {
     HRESULT hr;
     do
     {
         // clear back buffer
         hr = pID3DDevice->Clear(0,
                                 NULL,
                                 D3DCLEAR_TARGET,
                                 D3DCOLOR_RGBA(0,63,0,0),
                                 0,
                                 0);
         if(FAILED(hr))
             break;
 
         // start drawing
         hr = pID3DDevice->BeginScene();
         if(FAILED(hr))
             break;
 
         // Put all drawing code here
 
         hr = pID3DDevice->EndScene();
         if(FAILED(hr))
             break;
 
         // flip back buffer to front
         hr = pID3DDevice->Present(NULL, NULL, NULL, NULL);
     } while(0);
 
     return hr;
 }
 
 Clear会填充你指定的缓冲区。你可以填充Z缓冲区、后台缓冲区或摸板缓冲区(stencil buffer)。在这个例子中你将用绿色填充后台缓冲区。所以,我们设定D3DCLEAR_TARGET标志和绿色。
 在本例中BeginScene和EndScene并没有做什么,但在以后的例子中我们会用到它的。这两个函数是画图元时的例行公事代码,
 这个函数不断的翻转后台表面。我们可以不断的在后台表面画一些东西,然后把后台表面翻转到前台表面。
 
 ShutdownDirect3D()函数
 void CFrameWin::ShutdownDirect3D()
 {
     HELPER_RELEASE(pTexture);
     HELPER_RELEASE(pIndexBuffer);
     HELPER_RELEASE(pStreamData);
     HELPER_RELEASE(pID3DDevice);
     HELPER_RELEASE(pID3D);
 }
 ShutdownDirect3D释放所有的接口。将来你可能要加入额外的代码来关闭Direct3D接口,但现在已经够了。如果你运行程序,你将得到一个绿色背景的窗口。你可以按“ESC”键来退出应用程序。
 
 第三章 画三角形
 (DX8MFC2)
 定义你的顶点格式,Direct3D引入了一种可变形顶点格式(flexible vertex format)(FVF)的概念。在FVF中,你定义一个结构其中包括所需要的顶点组成部分。这个结构会随着你的程序而改变,但在这里你将初步把它定义成这个样子:
 struct MYVERTEX
 {
   FLOAT x, y, z; // The transformed position
   FLOAT rhw;     // 1.0 (reciprocal of homogeneous w)
   DWORD color;   // The vertex color
 };
 示例的开始定义了一个顶点结构,顶点的名称,和每一个三角形的顶点。在你的InitDirect3D函数中,你必须创建一个顶点缓冲区:
 int num_elems = sizeof(vertices) / sizeof(vertices[0]);
 pID3DDevice->CreateVertexBuffer(sizeof(MYVERTEX) *
                                 num_elems,
                                 D3DUSAGE_WRITEONLY,
                                 D3DFVF_XYZRHW|D3DFVF_DIFFUSE,
                                 D3DPOOL_DEFAULT,
                                 &pStreamData);
 函数的第一个参数是顶点结构的字节大小。在应用程序还不能读取顶点之前,传一个D3DUSAGE_WRITEONLY标记给它。这里可以有不同的标记来指定如何处理你的顶点,但现在你可以确信Direct3D已经能正确的工作了。
 下一步,指定我们用的是什么FVF格式。当你还没有使用坐标系预转换之前,指定为D3DFVF_XYZRHW标记。以后你使用自己的矩阵坐标系转换时,把它改成D3DFVF_XYZ。D3DFVF_DIFFUSE告诉Direct3D,我们将为每一个顶点指定颜色。D3DPOOL_DEFAULT指定内存的管理模式。
 最后一个参数是顶点缓冲区的指针,在例子1中你已经定义了它,但并没有用上。
 如果你不向顶点缓冲区填入有用数据的话,顶点缓冲区是没有用的:
 MYVERTEX *v;
 pStreamData->Lock(0, 0, (BYTE**)&v, 0);
 for(int ii = 0; ii < num_elems; ii++)
 {
   v[ii].x     = vertices[ii].x;
   v[ii].y     = vertices[ii].y;
   v[ii].z     = vertices[ii].z;
   v[ii].rhw   = vertices[ii].rhw;
   v[ii].color = vertices[ii].color;
 }
 pStreamData->Unlock();
 这是不难理解的,Lock返回一个你想写入顶点数据的指针。下一步你从你的顶点阵列中拷贝数据。然后,反还这个指针。
 这一对的调用可以告诉Direct3D你的FVF格式,并设定顶点阵列为当前的活动顶点阵列。(你可以有多个顶点阵列)。
 pID3DDevice->SetVertexShader(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
 
 pID3DDevice->SetStreamSource(0, pStreamData, sizeof(MYVERTEX));
 SetVertexShader告诉Direct3D使用与CreateVertexBuffer同样的格式。
 SetStreamSource告诉Direct3D使用pStreamData作为当前顶点阵列,并取得所有元素的大小。
 你现在可以加入画三角形的代码了。在DrawScene()的BeginScene和EndScene之间加入如下代码:
 int num_elems = sizeof(vertices) / sizeof(vertices[0]);
 pID3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,
                            0,
                            num_elems / 3);
 D3DPT_TRIANGLELIST标记将命令Direct3D画不连续的三角形。你指定从索引的第0个顶点开始,指定所要画的三角形数目。
 如果正确的话,你会看到一个三角形画在先前的绿色背景窗口上。
 
 第四章 画索引三角形
 (DX8MFC3)
 上一章的画三角形方式运行效率是较低的,而实际上我们都会使用DrawIndexedPrimitive()而不是DrawPrimitive()。想一想,如果要画两个相连的三角形,共有四个顶点。用DrawIndexedPrimitive()画要画四个顶点,而用DrawPrimitive()画则要画六个顶点。
 如果你可以顶点建立索引,你就可以用DrawIndexedPrimitive()画三角形了。我们可以为一个三角形建立这样的索引:
 WORD indices[] = { 0, 1, 2 };
 它表示三角形中,第一个顶点对应于顶点阵列的第0个顶点;三角形中,第二个顶点对应于顶点阵列的第1个顶点;三角形中,第三个顶点对应于顶点阵列的第2个顶点;
 要画索引三角形,首先要建立索引缓冲:
 num_elems = sizeof(indices) / sizeof(indices[0]);
 pID3DDevice->CreateIndexBuffer(sizeof(WORD) * num_elems,
                                D3DUSAGE_WRITEONLY,
                                D3DFMT_INDEX16,
                                D3DPOOL_DEFAULT,
                                &pIndexBuffer);
 第二步是用顶点填充这个索引缓冲:
 WORD *pIndex;
 pIndexBuffer->Lock(0, 0, (BYTE **)&pIndex, 0);
 for(ii = 0; ii < num_elems; ii++)
 {
   pIndex[ii] = indices[ii];
 }
 pIndexBuffer->Unlock();
 设定索引缓冲:
 pID3DDevice->SetIndices(pIndexBuffer, 0);
 把DrawScene()的相应的pID3DDevice->DrawPrimitive(...)换成:
 pID3DDevice->DrawIndexedPrimitive(
                  D3DPT_TRIANGLELIST,
                  0,
                  sizeof(indices) / sizeof(indices[0]),
                  0,
                  sizeof(indices) / sizeof(indices[0]) / 3);
 运行程序的到的还是一个三角形。
 
 第五章 加入帖图
 (DX8MFC4)
 首先,在MYVERTEX结构中加入帖图坐标系tu和tv,并给顶点阵列赋以适当的值。
 下一步,设置你的帖图:
 D3DXCreateTextureFromFile(pID3DDevice,
                           "dx5_logo.bmp",
                           &pTexture);
 pID3DDevice->SetTexture(0, pTexture);
 其中的"dx5_logo.bmp"指的是帖图文件,你可以用其他的文件代替它,运行程序你会看到一个带帖图的三角形。
 
 第六章 帖图立方体
 (DX8MFC5)
 现在样我们来进入三维的世界吧!这里你要启用Z缓冲(z-buffer),设置立方体的材质,世界坐标系和投影坐标系。
 启用Z缓冲(z-buffer),你要在D3DPRESENT_PARAMETERS结构中加入:
 present.EnableAutoDepthStencil  = TRUE;
 present.AutoDepthStencilFormat  = D3DFMT_D16;
 这里告诉DirectX8使用16位的Z缓冲,下一步:
 pID3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
 到这里Z缓冲已经设置完成。最后你还要在DrawScene()中调用清除Z缓冲内容的代码:
 pID3DDevice->Clear(0,
                    NULL,
                    D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                    D3DCOLOR_RGBA(0,63,0,0),
                    1.0,
                    0);
 MYVERTEX结构改成:
 struct MYVERTEX
 {
     FLOAT x, y, z; // The transformed position
     DWORD color;   // The vertex color
     FLOAT tu, tv;  // Texture coordinates
 };
 在InitDirect3D()也作了相应改动,具体可见源代码。
 Direct3D中有多种矩阵,在这里只使用其中的三个:世界、视图和投影矩阵。世界矩阵变换会把正方体放在世界坐标系中,视图矩阵变换把正方体放在可视空间内,投影矩阵使正方体看起来有深度感。
 BuildMatrices()函数将建立这三个矩阵:
 void CFrameWin::BuildMatrices()
 {
     D3DXMATRIX matrix;
     D3DXMatrixRotationY(&matrix, timeGetTime() / 1000.0f);
     pID3DDevice->SetTransform(D3DTS_WORLD, &matrix);
 
     D3DXMatrixLookAtLH(&matrix, &D3DXVECTOR3(0.0f, 3.0f, -5.0f), // 摄像机的空间位置
                                 &D3DXVECTOR3(0.0f, 0.0f, 0.0f),  // 摄象机观察点
                                 &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); // 摄象机向上方向矢量
     pID3DDevice->SetTransform(D3DTS_VIEW, &matrix);
 
     // 设置我们的平截面为45度角
     D3DXMatrixPerspectiveFovLH(&matrix, D3DX_PI / 4, 4.0f / 3.0f, 1.0f, 100.0f);
     pID3DDevice->SetTransform(D3DTS_PROJECTION, &matrix);
 }
 运行本章的例子你将看到一个旋转的正方体。
 
 声明:
 本文的SourceCode可以从http://gamedev.363.net得到
 欢迎您光临我的主页:http://gamedev.363.net
 作者:陈伟凡
 E-mail: [email protected]
 2001/1/12
 
  | 
 
 
 |