从普通的VC编程到DX SDK9, 以前的技术成了现在的工具. 接触图形学和DX有一个月的时间了, 真正开始理论与实践相结合, 是从转到金容俊的<3D游戏编程>开始的.
开发工具: Visual Studio 2005 (VC 8.0) + DirectX SDK Oct. 2006. (注意: 需要添加DirectX SDK的include和lib到解决方案中).
第一步: Win32 SDK程序框架
对于Windows的SDK, 我们脑中都有一个很清晰的框架. 这是借之进行DX编程的第一步:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int );
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
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_WIN32SDK, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
...{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32SDK));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
...{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
...{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
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;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
... {
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
...{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
...{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
对这一框架我就不多说.
第二步: 创建D3D设备
我安装的是DirectX SDK Oct. 2006, 在Visual Studio 2005下使用VC的SDK编程.
对于D3D, 因和硬件相关, 需要对其进行初始化. 我将初始化工作放在InitInstance中. 在程序退出之前, 又需将其从存储区中释放:
#pragma comment (lib, "d3d9.lib")
LPDIRECT3D9 g_pD3D = NULL; // 创建D3D设备的D3D对象参数
LPDIRECT3DDEVICE9 g_pD3DDevice = NULL; // 渲染中使用的D3D设备
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
... {
......
// if (!hWnd)
if (SUCCEEDED(InitD3D(hWnd))) // 初始化D3D
...{
......
}
......
}
BOOL InitD3D(HWND hWnd)
... {
if(NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
...{
return E_FAIL;
}
D3DPRESENT_PARAMETERS D3Dpp; // 创建设备的结构体
ZeroMemory(&D3Dpp, sizeof(D3Dpp)); // 将结构体清零
D3Dpp.Windowed = TRUE; // 创建窗口模式
D3Dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // 最有效的Swap效果
D3Dpp.BackBufferFormat = D3DFMT_UNKNOWN; // 建立与当前显示模式匹配的后置缓冲
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&D3Dpp, &g_pD3DDevice)))
...{
return E_FAIL;
}
return S_OK;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
... {
switch (message)
...{
......
case WM_PAINT:
Render(); // 绘图函数
ValidateRect(hWnd, NULL);
break;
case WM_DESTROY:
ClearUp(); // 按栈序释放所有对象
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void Render()
... {
if(NULL == g_pD3DDevice)
return;
// 清除后置缓冲区, 同时设置为蓝色 (0, 0, 255)
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
// 开始渲染
if(SUCCEEDED(g_pD3DDevice->BeginScene()))
...{
......
// 结束渲染
g_pD3DDevice->EndScene();
}
// 显示后置缓冲的画面
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
1) 包含头文件<d3d9.h>和链接库文件"d3d9.lib";
2) 初始化D3D对象, 并创建D3D设备对象;
3) 设备后置缓冲及渲染操作;
4) 释放DIRECT3D9和DIRECT3DDEVICE9对象以及窗口类.
第三步: 绘制基本二维图形
三角形是构成其他几何图形 (二维或三维) 的基本元素. 在绘制三角形的过程中, 最主要的是对顶点 (Vertex) 进行操作. 在第二步中我将绘图函数Render() 置于WM_PAINT消息中处理, 在游戏编程中由于用户对画面的FPS (每秒钟帧数) 要求较高. 相比之下, CPU对消息的处理速度是比较快的, 因此利用消息循环的闲置时间进行绘图是可行的:
LPDIRECT3DDEVICE9 g_pD3DDevice = NULL; // 渲染中使用的D3D设备
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // 存储顶点的顶点缓冲
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
... {
......
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
...{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TETRIS2007));
ZeroMemory(&msg, sizeof(msg)); // 必须将消息清零
// 主消息循环:
while (WM_QUIT != msg.message) // 不再用传统的GetMessage来判断
...{
if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
...{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
...{
Render(); // 没有消息处理则绘图
}
}
return (int) msg.wParam;
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
... {
......
if (SUCCEEDED(InitD3D(hWnd)))
...{
if(SUCCEEDED(InitVB())) // 创建顶点缓冲并写入
...{
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}
}
return TRUE;
}
HRESULT InitVB()
... {
// 渲染三角形顶点
CUSTOMVERTEX vertices[] =
...{
...{150.0f, 50.0f, 0.5f, 1.0f, 0xffff0000}, // x, y, z, rhw, color
...{250.0f, 250.0f, 0.5f, `.0f, 0xff00ff00},
...{50.0f, 250.0f, 0.5, 1.0f, 0xff0000ff}
};
// 创建顶点缓冲
if(FAILED(g_pD3DDevice->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL)))
...{
return E_FAIL;
}
// 写入顶点缓冲
VOID *pVertices;
if(FAILED(g_pVB->Lock(0, sizeof(vertices), (void **)&pVertices, 0)))
...{
return E_FAIL;
}
memcpy(pVertices, vertices, sizeof(vertices));
g_pVB->Unlock();
return S_OK;
}
void Render()
... {
......
if(SUCCEEDED(g_pD3DDevice->BeginScene()))
...{
// 创建矩阵
SetupMatrices();
// 绘制顶点缓冲三角形
// 绑定设备数据流
g_pD3DDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
// 指定顶点着色信息(FVF)
g_pD3DDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// 输出
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// 结束渲染
g_pD3DDevice->EndScene();
}
// 显示后置缓冲的画面
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
1) 在WinMain中, 在消息循环之前需将msg所在存储区清零;
2) 在无消息处理时进行绘图;
3) 掌握Free Vertex Format并进行Vertex Buffer初始化;
4) 在写顶点缓冲时, 需对VB进行加锁Lock(), 写完释放Unlock();
5) 在绘图时, 绑定设备数据流SetStreamSource(), 指定顶点格式SetFVF(), 设定输出几何信息DrawPrimitive().