代码改变世界

Direct3D轮回:快速构建基于win32工程的Direct3D游戏框架

2011-06-21 22:11  独孤残云  阅读(3075)  评论(15编辑  收藏  举报

前段时间一直混迹于C++与C#语言,徘徊于DirectX与Xna之间,一直没什么大的收获。

重新拾回C++&DirectX,有一种返璞归真的感慨~ 多少有一些心得,简单总结一点~ 请园子的前辈们多多指教,多多拍砖^ ^

 

用过Xna的朋友都知道,客户在Xna中从来不用自己去实现3D设备的初始化,游戏的主循环,甚至是退出时的设备资源释放等等相关事宜。

不过很不幸,DirectX没有赐予大家这种特权,相关工作我们需要自己实现:

1.新建一个Win32工程,并在此基础上新增CD3DInit类(这里其实没有必要真正形成类,个人感觉全局函数用起来反倒方便);

/*----------------------------------
代码清单:D3DInit.h
来自:
http://kenkao.cnblogs.com/
----------------------------------
*/

#pragma once

#include 
<windows.h>
#include 
"d3d9.h"
#include 
"d3dx9.h"

#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

#define ReleaseCOM(x){if(x!=NULL) x->Release();x=NULL;}

HRESULT InitD3D(IDirect3D9 
**ppD3D, 
                IDirect3DDevice9 
**ppD3DDevice,
                HWND hWnd, BOOL ForceWindowed 
= FALSE,
                BOOL MultiThreaded 
= FALSE);
D3DInit.cpp
/*----------------------------------
代码清单:D3DInit.cpp
来自:
http://kenkao.cnblogs.com/
----------------------------------
*/

#include 
"D3DInit.h"

/*---------------------------------------------------
参考了《Advanced Animation with DirectX》书中的实现方法

参数一:IDirect3D9指针引用(指针的指针)
参数二:IDirect3DDevice9设备指针引用
参数三:有效的窗口句柄
参数四:是否全屏
参数五:是否多线程
----------------------------------------------------
*/
HRESULT InitD3D(IDirect3D9 
**ppD3D, 
                IDirect3DDevice9 
**ppD3DDevice,
                HWND hWnd, 
                BOOL ForceWindowed,
                BOOL MultiThreaded)
{
    IDirect3D9       
*pD3D       = NULL;
    IDirect3DDevice9 
*pD3DDevice = NULL;
    HRESULT           hr;

    
// 错误检查
    if(!ppD3D || !ppD3DDevice || !hWnd)
        
return E_FAIL;

    
// 初始化 Direct3D 
    if((pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
        
return E_FAIL;
    
*ppD3D = pD3D;

    
// 是否使用全屏状态
    int Mode;
    
if(ForceWindowed == TRUE)
        Mode 
= IDNO;
    
else
        Mode 
= MessageBox(hWnd, "Use fullscreen mode? (640x480x16)""Initialize D3D", MB_YESNO | MB_ICONQUESTION);

    
// 全屏与非全屏状态下的参数设置
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(
&d3dpp, sizeof(d3dpp));

    
if(Mode == IDYES) {

        DWORD     Width  
= 640;
        DWORD     Height 
= 480;
        D3DFORMAT Format 
= D3DFMT_R5G6B5;

        d3dpp.BackBufferWidth  
= Width;
        d3dpp.BackBufferHeight 
= Height;
        d3dpp.BackBufferFormat 
= Format;
        d3dpp.SwapEffect       
= D3DSWAPEFFECT_FLIP;
        d3dpp.Windowed         
= FALSE;
        d3dpp.EnableAutoDepthStencil 
= TRUE;
        d3dpp.AutoDepthStencilFormat 
= D3DFMT_D16;
        d3dpp.FullScreen_RefreshRateInHz 
= D3DPRESENT_RATE_DEFAULT;
        d3dpp.PresentationInterval       
= D3DPRESENT_INTERVAL_DEFAULT;
    } 
else {

        RECT ClientRect, WndRect;
        GetClientRect(hWnd, 
&ClientRect);
        GetWindowRect(hWnd, 
&WndRect);

        DWORD DesiredWidth  
= 640;
        DWORD DesiredHeight 
= 480;
        DWORD Width  
= (WndRect.right - WndRect.left) + (DesiredWidth  - ClientRect.right);
        DWORD Height 
= (WndRect.bottom - WndRect.top) + (DesiredHeight - ClientRect.bottom);

        MoveWindow(hWnd, WndRect.left, WndRect.top, Width, Height, TRUE);

        D3DDISPLAYMODE d3ddm;
        pD3D
->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

        d3dpp.BackBufferWidth  
= DesiredWidth;
        d3dpp.BackBufferHeight 
= DesiredHeight;
        d3dpp.BackBufferFormat 
= d3ddm.Format;
        d3dpp.SwapEffect       
= D3DSWAPEFFECT_DISCARD;
        d3dpp.Windowed         
= TRUE;
        d3dpp.EnableAutoDepthStencil 
= TRUE;
        d3dpp.AutoDepthStencilFormat 
= D3DFMT_D16;
        d3dpp.FullScreen_RefreshRateInHz 
= D3DPRESENT_RATE_DEFAULT;
        d3dpp.PresentationInterval       
= D3DPRESENT_INTERVAL_DEFAULT;
    }

    
// 创建3D设备
    DWORD Flags= D3DCREATE_MIXED_VERTEXPROCESSING;
    
if(MultiThreaded == TRUE)
        Flags 
|= D3DCREATE_MULTITHREADED;
    
if(FAILED(hr = pD3D->CreateDevice(
        D3DADAPTER_DEFAULT, 
        D3DDEVTYPE_HAL, hWnd, Flags, 
        
&d3dpp, &pD3DDevice)))
        
return hr;

    
*ppD3DDevice = pD3DDevice;

    
// 设置投影矩阵
    float Aspect = (float)d3dpp.BackBufferWidth / (float)d3dpp.BackBufferHeight;
    D3DXMATRIX matProjection;
    D3DXMatrixPerspectiveFovLH(
&matProjection, D3DX_PI/4.0f, Aspect, 1.0f10000.0f);
    pD3DDevice
->SetTransform(D3DTS_PROJECTION, &matProjection);

    
// 设置默认渲染状态
    pD3DDevice->SetRenderState(D3DRS_LIGHTING,         FALSE);
    pD3DDevice
->SetRenderState(D3DRS_ZENABLE,          D3DZB_TRUE);
    pD3DDevice
->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
    pD3DDevice
->SetRenderState(D3DRS_ALPHATESTENABLE,  FALSE);

    
// 设置默认纹理融合态
    pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    pD3DDevice
->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
    pD3DDevice
->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);

    
// 设置默认纹理采样器
    pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    pD3DDevice
->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

    
return S_OK;
}

D3DInit.h相当于一个总的头文件,我们可以把用于初始化D3D所需要引入的头文件、库文件统统放到这个下面,供其他文件引用。

D3DInit.cpp是其实现部分,目前只有一个用于快速初始化D3D设备的InitD3D函数,代码中给出了参考书名,不得不佩服与作者的匠心独运。函数内部给出了中文注释,其奥妙留待读者自己体会~

至于ReleaseCOM宏,可以作为通用的COM安全释放函数,熟练运用D3D的朋友大都会留出这样一个宏,建议大家养成这样一个习惯~

2.新增CD3DGame类

/*----------------------------------
代码清单:D3DGame.h
来自:
http://kenkao.cnblogs.com/
----------------------------------
*/

#pragma once

#include 
"D3DInit.h"

// 初始化
void Initialize(HWND hWnd);

// 内容加载
void LoadContent();

// 逻辑更新
void Update();

// 绘图更新
void Draw();

// 卸载内容
void UnloadContent();

// 释放并退出
void Dispose();

/*---------------------------------------------------------

1.用如下这一段替代_tWinMain消息循环的那一段

LoadContent();
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT) {
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Update();
Draw();
}
UnloadContent();
Dispose();

2.InitInstance的ShowWindow(hWnd, nCmdShow)之前加入

Initialize(hWnd);

----------------------------------------------------------
*/

 

/*----------------------------------
代码清单:D3DGame.cpp
来自:
http://kenkao.cnblogs.com/
----------------------------------
*/

#include 
"StdAfx.h"
#include 
"D3DGame.h"

IDirect3D9       
*g_pD3D       = NULL;
IDirect3DDevice9 
*g_pD3DDevice = NULL;

void Initialize(HWND hWnd)
{
    InitD3D(
&g_pD3D, &g_pD3DDevice, hWnd);
}

void LoadContent()
{
    
}

void Update()
{
    
}

void Draw()
{
    g_pD3DDevice
->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f0);
    
if(SUCCEEDED(g_pD3DDevice->BeginScene())) 
    {
        g_pD3DDevice
->EndScene();
    }
    g_pD3DDevice
->Present(NULL, NULL, NULL, NULL);
}

void UnloadContent()
{

}

void Dispose()
{
    ReleaseCOM(g_pD3DDevice);
    ReleaseCOM(g_pD3D);
}

安插这两个文件的目的,主要是为了构建我们自己的D3D游戏框架,各个函数的用途均已说明,完全仿Xna结构而来~(用习惯了^ ^)

我们可以按照D3DGame.h最下方的注释,将这个框架跟Win32默认代码框架关联起来。

以后所有的编码工作均在我们自己的这部分代码中实现,基本不需要再关心令人眼花缭乱的Win32工程代码。

3.改动后的Win32代码为:

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
 UNREFERENCED_PARAMETER(hPrevInstance);
 UNREFERENCED_PARAMETER(lpCmdLine);

  // TODO: 在此放置代码。
 MSG msg;
 HACCEL hAccelTable;

 // 初始化全局字符串
 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
 LoadString(hInstance, IDC_KEN3DGAME, szWindowClass, MAX_LOADSTRING);
 MyRegisterClass(hInstance);

 // 执行应用程序初始化:
 if (!InitInstance (hInstance, nCmdShow))
 {
  return FALSE;
 }

 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_KEN3DGAME));


 LoadContent();
 ZeroMemory(&msg, sizeof(MSG));
 while(msg.message != WM_QUIT) {
  if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
  Update();
  Draw();
 }
 UnloadContent();
 Dispose();

 return (int) msg.wParam;
}

 

 

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // 将实例句柄存储在全局变量中

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   Initialize(hWnd);

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

这样,一个可以无限复用的D3D基本框架就完成了~

如下执行效果图: