Direct3D 初涉: Direct3D 框架的搭建

DirectX 9.0 3D 游戏开发编程基础》 一书中在第 II 部分 第一章 初始化 Direct3D 中提供了一个DirectX 框架(所谓框架,只是一个支撑,在这个支撑下,可以添加代码实现相应的操作),但是整体感觉比较乱(不知道您是否有这种感觉?)。我将Direct3D 初始化、资源设置与清除、显示等操作全部封装到一个类中,希望可以让代码不再杂乱,便于理解。

 

Direct3D 的初始化过程可分解为如下步骤:

(1) 获取接口 IDirect3D9 的指针。该接口用于获取系统中物理硬件设备的信息并创建接口 IDirect3DDevce9 ,该接口是一个 C++ 对象,代表了我们用来显示 3D 图形的物理硬件设备。

(2) 检查设备性能(D3DCAPS9),判断主显卡(primary display adapter 或者 primary graphics card )是否支持硬件顶点运算。为了创建接口 IDirect3DDevice9,我们必须明确显卡是否支持该功能。

(3) 初始化 D3DPRESENT_PARAMETERS 结构的一个实例。该结构由许多数据成员组成,我们可以通过这些变量来指定即将创建接口 IDirect3DDevice9 的特性。

(4) 利用已初始化的 D3DPRESENT_PARAMETER 结构创建 IDirect3DDevice9 对象(一个 C++ 对象代表了我们用来显示 3D 图形的物理硬件设备)。


源代码分为三个文件:Direct3DFrame 类的头文件(Direct3DFrame.h)与源文件(Direct3DFrame.cpp),WinMain所在源文件(WinMain.cpp)。以下是代码:

// file.Direct3DFrame.h

#pragma once
#include <d3d9.h>
#include <d3dx9.h>

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

// Direct3D 框架类,完成 Direct3D 的初始化等操作
class Direct3DFrame
{
public:
Direct3DFrame(
LPCWSTR wndName,
HINSTANCE hInstance,
int width, int height,
bool windowed,
D3DDEVTYPE deviceType);

~Direct3DFrame(void);

public:
bool InitD3D();
// Setup() 设置资源,Cleanup() 清除资源
bool Setup();
void Cleanup();
bool Display(float timeDelta);

public:
static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam);
int EnterMsgLoop();

private:
bool CreateWin32Window(HWND& hWnd);

private:
LPCWSTR m_wndName; // 窗口名称
HINSTANCE m_hInstance; // 应用程序实例句柄
int m_width; // 后台缓冲大小
int m_height;
bool m_windowed; // 窗口模式 (true) 或者全屏模式 (false)
D3DDEVTYPE m_deviceType; // HAL or REF
IDirect3DDevice9* m_device;
};

 

// file. Direct3DFrame.cpp

#include "Direct3DFrame.h"

Direct3DFrame::Direct3DFrame(
LPCWSTR wndName,
HINSTANCE hInstance,
int width, int height,
bool windowed,
D3DDEVTYPE deviceType)
{
m_wndName = wndName;
m_hInstance = hInstance;
m_width = width;
m_height = height;
m_windowed = windowed;
m_deviceType = deviceType;
this->m_device = NULL;
}

Direct3DFrame::~Direct3DFrame(void)
{
}


bool Direct3DFrame::InitD3D()
{
HWND hWnd;
if (!CreateWin32Window(hWnd)) {
return false;
}

// ---- Init D3D:

HRESULT hr = 0;

// Step 1: Create the IDirect3D9 object.
/* IDirect3D9 用途:
* (1) 设置枚举 ( device enumeration )
* ->指获取系统中可用的每块图形卡的性能、显示模式(display mode)、格式及其他信息
* (2) 创建 IDirect3DDevice9 类型
*/
IDirect3D9* d3d9 = NULL;
d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

if (!d3d9) {
::MessageBox(0, TEXT("Direct3DCreate9() - FAILED"), 0, 0);
return false;
}

// Step 2: Check for hardare vp.
D3DCAPS9 caps;
d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, m_deviceType, &caps);

int vp = 0;
if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) {
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else {
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}

// Step3 : Fill out the D3DPRESENT_PARAMETERS structure.
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = m_width;
d3dpp.BackBufferHeight = m_height;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.Windowed = m_windowed;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

// Step 4: Create the device.
hr = d3d9->CreateDevice(
D3DADAPTER_DEFAULT, // primary adapter
m_deviceType, // device type
hWnd, // window assocated with device
vp, // vertex processing
&d3dpp, // present parameters
&m_device); // return created device

if (FAILED(hr)) {
// try again using a 16-bit depth buffer
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
hr = d3d9->CreateDevice(
D3DADAPTER_DEFAULT,
m_deviceType,
hWnd,
vp,
&d3dpp,
&m_device);

if (FAILED(hr)) {
d3d9->Release();
::MessageBox(0, TEXT("CreateDevice() - FAILED"), 0, 0);
return false;
}
}

d3d9->Release(); // done with d3d9 object

return true;
}

LRESULT CALLBACK Direct3DFrame::WndProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;

case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
::DestroyWindow(hWnd);
break;
}

return ::DefWindowProc(hWnd, msg, wParam, lParam);
}

bool Direct3DFrame::CreateWin32Window(HWND& hWnd)
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_wndName;

if (!RegisterClass(&wc)) {
::MessageBox(0, TEXT("RegisterClass() - FAILED"), 0, 0);
return false;
}

hWnd = ::CreateWindow(m_wndName, m_wndName,
WS_OVERLAPPEDWINDOW^WS_MAXIMIZEBOX , 0, 0, m_width, m_height,
0, 0, m_hInstance, 0);

if (!hWnd) {
::MessageBox(0, TEXT("CreateWindow() - FAILED"), 0, 0);
return false;
}

::ShowWindow(hWnd, SW_SHOW);
::UpdateWindow(hWnd);

return true;
}

bool Direct3DFrame::Display(float timeDelta)
{
if (m_device) {
// Instruct the device to set each pixel on the back buffer black -
// D3DCLEAR_TARGET: 0x00000000 (black) - and to set each pixel on
// the depth buffer to a value of 1.0 - D3DCLEAR_ZBUFFER: 1.0f
m_device->Clear(
0, // pRect 数组中矩形数目
0, // 所要执行清除操作的屏幕矩形数组,选择性清除区域
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, // 指定要清除的表面
0x00000000, // 指定将绘制目标设置为何种颜色
1.0f, // 深度缓存所要设置的值
0); // 模板缓存所要设定的值

// 提交后台缓存
m_device->Present(0, 0, 0, 0);
}

return true;
}

int Direct3DFrame::EnterMsgLoop()
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));

static float lastTime = (float)timeGetTime();

while (msg.message != WM_QUIT) {
if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else {
float currTime = (float)timeGetTime();
float timeDelta = (currTime - lastTime)*0.001f;
this->Display(timeDelta);
lastTime = currTime;
}
}

return msg.wParam;
}

bool Direct3DFrame::Setup()
{
return true;
}

void Direct3DFrame::Cleanup()
{
m_device->Release();
}

 

// file.WinMain.cpp

#include "Direct3DFrame.h"

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE prevInstance,
PSTR cmdLine,
int showCmd)
{
Direct3DFrame d3dFrame(TEXT("Direct3DFrame"), hInstance,
640, 480, true, D3DDEVTYPE_HAL);

if (!d3dFrame.InitD3D()) {
::MessageBox(0, TEXT("InitD3D() - FAILED"), 0, 0);
return 0;
}

if (!d3dFrame.Setup()) {
::MessageBox(0, TEXT("Setup() - FAILED"), 0, 0);
return 0;
}

d3dFrame.EnterMsgLoop();
d3dFrame.Cleanup();

return 0;
}

 

运行后截图:

 

知识点:

 

1. HAL 与 REF Direct3D 和图形设备之间的中间环节) 

HAL(Hardware Abstraction Layer,硬件抽象层),指示设备完成某些操作的设备相关的代码集,按照这种方式,Direct3D 就可不必了解设备的具体细节,其规范(specification)的制定便可独立于具体的硬件。

REF(Reference Rasterizer Deivce),以软件运算的方式完全支持 Direct3D API。借助 REF 设备,可在代码中使用那些不为当前硬件所支持的特性,并对这些特性进行测试。REF 速度十分缓慢,在测试以外的其他场合都很不实用。

在程序代码中,使用 D3DDEVTYPE 枚举类型来标识 HAL 设备与 REF 设备。HAL 设备使用 D3DDEVTYPE_HAL REF 设备用 D3DDEVTYPE_REF 来指定。

 

2. 顶点运算、检验硬件顶点运算

在 Direct3D 中,可用两种不同的方式进行顶点运算(Vertex Processing), 即软件顶点运算 (software vertex process) 和硬件顶点运算 (hardare vertex processing)。软件顶点运算总是会被支持的,所以总是可以使用的。而硬件顶点运算只有得到图形卡的支持方可使用。应该优先使用硬件顶点运算,因为如果使用硬件专有的加速功能,程序的执行速度将会比软件运算方式快得多。而且,在硬件中进行顶点运算,可以不占用资源,也就意味着 CPU 可被解放出来进行其他运算。

创建一个代表主显卡的 IDirect3DDevice9 类型对象时,必须指定使用该对象进行顶点运算的类型。如果可以,我们希望使用硬件顶点运算,但是由于并非所有的显卡都支持硬件顶点运算,我们必须首先检查图形卡是否支持该类型的运算。

使用 GetDeviceCaps 方法来检查设备性能。

 

3. D3DPRESENT_PARAMETERS 结构

 

4. IDirect3DDevice9::Clear 方法

 

/*******************************************************/ 

** 本文由 独酌逸醉 原创,转载请注明博客链接,谢谢!

** 小弟初学 Direct3D,文章中如果存在错误,烦请指证,不胜感激!

** 参考书籍:《DirectX 9.0 3D 游戏开发编程基础》 

** 时间:2011.10.28

/*******************************************************/

posted @ 2011-10-28 00:22 独酌逸醉 阅读(...) 评论(...) 编辑 收藏