第11章 Direct3D编程基础
2D游戏是贴图的艺术,3D游戏是渲染的艺术。这句话在我学过了之前的GDI编程之后,前一句算是有所体会,现在是来理解后一句的时候了。
安装DirectX SDK配置啥的就不说了,直接进入正题,先来个典型的Direct3D程序框架图:
主要分为5个部分:
- 创建一个Windows窗口
- Direct3D的初始化
- 消息循环
- 渲染图形
- 结束应用程序,清除在初始化阶段锁创建的COM对象,退出程序
至于COM (Component Object Model, 组件对象模型) 接口,是一项能够使DirectX独立于编程语言并具备向下兼容的技术。COM接口对象通过控制对某对象的引用计数个数来决定其生命周期,由其自行经营。
一个DirectX程序通用框架:
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//------------------------------------------------------------------------------------------------
#include <d3d9.h> //-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Direct3D程序的核心框架" //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //定义一个安全释放宏,便于后面COM接口指针的释放 //-----------------------------------【全局函数声明部分】-------------------------------------
// 描述:全局函数声明,防止“未声明的标识”系列错误
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
HRESULT Direct3D_Init(HWND hwnd); //在这个函数中进行Direct3D的初始化
HRESULT Objects_Init(HWND hwnd); //在这个函数中进行要绘制的物体的资源初始化
VOID Direct3D_Render(HWND hwnd); //在这个函数中进行Direct3D渲染代码的书写
VOID Direct3D_CleanUp( ); //在这个函数中清理COM资源以及其他资源 //-----------------------------------【WinMain( )函数】---------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //Direct3D资源的初始化
Direct3D_Init (hwnd); //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //【5】消息循环过程
MSG msg = { }; //初始化msg
while( msg.message != WM_QUIT ) //使用while循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。
}
else
{
Direct3D_Render(hwnd); //进行渲染
}
}
//【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message ) //switch语句开始
{
case WM_PAINT: // 若是客户区重绘消息
Direct3D_Render(hwnd); //调用Direct3D渲染函数
ValidateRect(hwnd, NULL); // 更新客户区的显示
break; //跳出该switch语句 case WM_KEYDOWN: // 若是键盘按下消息
if (wParam == VK_ESCAPE) // 如果被按下的键是ESC
DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息
break; //跳出该switch语句 case WM_DESTROY: //若是窗口销毁消息
Direct3D_CleanUp(); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Direct3D_Init( )函数】--------------------------------------
// 描述:Direct3D初始化函数,进行Direct3D的初始化
//------------------------------------------------------------------------------------------------
HRESULT Direct3D_Init(HWND hwnd)
{
if(!(S_OK==Objects_Init(hwnd))) return E_FAIL; //调用一次Objects_Init,进行渲染资源的初始化
return S_OK;
} //-----------------------------------【Object_Init( )函数】--------------------------------------
// 描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init(HWND hwnd)
{
return S_OK;
} //-----------------------------------【Direct3D_Render( )函数】--------------------------------------
// 描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
void Direct3D_Render(HWND hwnd)
{
//暂时为空
} //-----------------------------------【Direct3D_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
void Direct3D_CleanUp()
{
//暂时为空
}
关于上面的程序有两点要提到的是:
1. #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } 这里的宏定义是定义了一个带参数的宏,我以前以为C++中宏定义只能定义一个全局变量之类的,看来还是理解得不够深入。C++宏定义详解 。
2. 那个S_OK是WinError.h中预定好的宏,#define S_OK ((HRESULT)0L),其实就是0。HRESULT在msdn中的定义如下:
接下来写的Direct3D程序,简单点说,只需做如下这四步就可以了:
- 在Direct3D_Init()函数里完成Direct3D的相关初始化
- 在Objects_Init()函数里完成将要绘制的绘制资源的初始化
- 在Direct3D_Render()函数里完成渲染
- 在Direct3D_CleanUp()函数里完成资源的清理
1. Direct3D初始化
下面来介绍下Direct3D初始化4步曲:
- 创建Direct3D接口对象
- 获取设备硬件信息
- 填充 D3DPRESENT_PARAMETERS 结构体
- 创建Direct3D设备接口
第一步是创建Direct3D接口对象,注意Direct3D接口对象和Direct3D设备接口不是一个概念。
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建
上面定义了一个LPDIRECT3D9类型的指针,不过我看到这里的时候觉得好奇怪,定义指针不是应该有个 * 号的么?为什么这里不用加呢? 用编译器查找定义功能,终于在 d3d9.h 中找到了答案:
typedef struct IDirect3D9 *LPDIRECT3D9, *PDIRECT3D9;
现在终于明白为什么不需要加 * 号了,因为已经在头文件中定义过了,这里也涉及到了结构体指针的用法,需要自己体会总结一番的。那么下面的问题是IDirect3D9是什么玩意,查了mdsn文档,发现它是一个接口:
Applications use the methods of the IDirect3D9 interface to create Microsoft Direct3D objects and set up the environment. This interface includes methods for enumerating and retrieving capabilities of the device.
然后要将该对象初始化,用 Direct3DCreate9 函数:
Create an IDirect3D9 object and return an interface to it.
IDirect3D9* Direct3DCreate9(
UINT SDKVersion
);
这个函数返回指向IDirect3D9接口的指针,并进行DirectX的版本协商。所以初始化的第一步可以这样写:
// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
//--------------------------------------------------------------------------------------
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建
if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
return E_FAIL;
第二步是获取设备信息,使用的是GetDeviceCaps方法,获取指定设备的性能参数,会把所获取的硬件设备的信息保存到一个叫 D3DCAPS9 的预设好的结构体中。
IDirect3D9::GetDeviceCaps method:
HRESULT GetDeviceCaps(
[in] UINT Adapter,
[in] D3DDEVTYPE DeviceType,
[out] D3DCAPS9 *pCaps
);
第一个参数是显卡的序号,通常使用默认值D3DADAPTER_DEFAULT,表示使用当前显卡。第二个参数是D3DDEVTYPE类型的,表示设备类型,取值为D3DDEVTYPE结构体的某一成员:
typedef enum D3DDEVTYPE {
D3DDEVTYPE_HAL = ,
D3DDEVTYPE_NULLREF = ,
D3DDEVTYPE_REF = ,
D3DDEVTYPE_SW = ,
D3DDEVTYPE_FORCE_DWORD = 0xffffffff
} D3DDEVTYPE, *LPD3DDEVTYPE;
第3个参数是 用于接受设备信息的D3DCAPS9结构体指针,查了下mdsn文档,里面的成员太多了,就不贴出来了。
顶点是3D图形学中的基本元素(说道这里,其实我并没有学过有关计算机图形学的内容,直接来学DirectX是不是有点太突兀了),而Direct3D中有硬件顶点运算(显卡支持,速度快)和软件顶点运算这两种方式。所以我们应该优先考虑硬件顶点运算方式。但是老的显卡也许不支持硬件顶点运算,所以我们需要在Direct3D初始化过程中,通过GetDeviceCaps方法检查显卡支持顶点运算模式。
Direct3D初始化的第二步可以这样写:
D3DCAPS9 caps; int vp = ;
if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
{
return E_FAIL;
}
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算
注:
上面的 caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT 这里做了个按位与运算,我原来以为是逻辑与。至于这里为什么作个逻辑与运算便可以判断是否支持硬件顶点运算,我也不是很明白。
下面是一些字段在MSDN中的解释:.
D3DDEVTYPE_HAL:Hardware rasterization(光栅化). Shading is done with software, hardware, or mixed transform and lighting.
D3DCAPS9里的成员DevCaps: Flags identifying the capabilities of the device
D3DDEVCAPS_HWTRANSFORMANDLIGHT:#define D3DDEVCAPS_HWTRANSFORMANDLIGHT 0x00010000L /* Device can support transformation and lighting in hardware and DRAWPRIMITIVES2EX must be also */
#define FAILED(hr) (((HRESULT)(hr)) < 0)
第三步是填充 D3DPRESENT_PARAMETERS 结构体,这个结构体很重要
typedef struct _D3DPRESENT_PARAMETERS_
{
UINT BackBufferWidth; //后台缓冲区宽度
UINT BackBufferHeight; //后台缓冲区高度
D3DFORMAT BackBufferFormat; //后台缓冲区的保存像素格式
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType; //多重采样类型
DWORD MultiSampleQuality; //多重采样的格式
D3DSWAPEFFECT SwapEffect; //指定Direct3D如何将后台缓冲区的内容复制到前台缓存中
HWND hDeviceWindow; //窗口句柄,指定在哪个窗口上进行绘制
BOOL Windowed; //窗体的显示模式,TRUE表示窗口模式,FALSE表示全屏模式
BOOL EnableAutoDepthStencil; //是否为应用程序自动管理深度缓存
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags; //附加属性,通常为0
/* FullScreen_RefreshRateInHz must be zero for Windowed mode */
UINT FullScreen_RefreshRateInHz; //全屏幕时指定屏幕的刷新率
UINT PresentationInterval; //指定后台缓冲区与前台缓冲区的最大交换频率
} D3DPRESENT_PARAMETERS;
有好多专业名词,看得也不是很明白。Direct3D初始化的第三步代码:
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = WINDOW_WIDTH;
d3dpp.BackBufferHeight = WINDOW_HEIGHT;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = ;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = ;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = ;
d3dpp.FullScreen_RefreshRateInHz = ;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
注:ZeroMemory函数:Fills a block of memory with zeros.用0来填充一块内存区域
void ZeroMemory(
[in] PVOID Destination, //A pointer to the starting address of the block of memory to fill with zeros.
[in] SIZE_T Length //The size of the block of memory to fill with zeros, in bytes.
);
Direct3D初始化第四步是正式创建Direct3D设备接口,其实就是利用第一步创建的Direct3D接口对象,调用其IDirect3D9::CreateDevice方法:
HRESULT CreateDevice(
[in] UINT Adapter, //显卡序号
[in] D3DDEVTYPE DeviceType, //设备类型
[in] HWND hFocusWindow, //窗口句柄,指定当Direct3D程序从前台变换到后台时的提示窗口
[in] DWORD BehaviorFlags, //设备行为标识
[in, out] D3DPRESENT_PARAMETERS *pPresentationParameters, //第三步创建的结构体
[out, retval] IDirect3DDevice9 **ppReturnedDeviceInterface //指定我们创建的Direct3D设备接口的指针,使用这个指针,已完成之后的绘制过程
);
Direct3D初始化第四步代码:
if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, &g_pd3dDevice)))
return E_FAIL;
Direct3D初始化的完整代码如下:
#include <d3d9.h> //-----------------------------------【库文件包含部分】---------------------------------------
#pragma comment(lib,"winmm.lib") //调用PlaySound函数所需库文件
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib") //-----------------------------------【宏定义部分】--------------------------------------------
#define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Direct3D初始化四步曲 示例程序" //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //定义一个安全释放宏,便于后面COM接口指针的释放 //-----------------------------------【全局变量声明部分】-------------------------------------
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象 //-----------------------------------【全局函数声明部分】-------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
HRESULT Direct3D_Init(HWND hwnd); //在这个函数中进行Direct3D的初始化
HRESULT Objects_Init(HWND hwnd); //在这个函数中进行要绘制的物体的资源初始化
VOID Direct3D_Render(HWND hwnd); //在这个函数中进行Direct3D渲染代码的书写
VOID Direct3D_CleanUp( ); //在这个函数中清理COM资源以及其他资源 //-----------------------------------【WinMain( )函数】--------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //Direct3D资源的初始化,成功或者失败都用messagebox予以显示
if (S_OK==Direct3D_Init (hwnd))
{
MessageBox(hwnd, L"Direct3D初始化完成~!", L"浅墨的消息窗口", ); //使用MessageBox函数,创建一个消息窗口
}
else
{
MessageBox(hwnd, L"Direct3D初始化失败~!", L"浅墨的消息窗口", ); //使用MessageBox函数,创建一个消息窗口
} //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 PlaySound(L"NightElf3.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循环播放背景音乐 //【5】消息循环过程
MSG msg = { }; //初始化msg
while( msg.message != WM_QUIT ) //使用while循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。
}
else
{
Direct3D_Render(hwnd); //进行渲染
}
}
//【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message ) //switch语句开始
{
case WM_PAINT: // 若是客户区重绘消息
Direct3D_Render(hwnd); //调用Direct3D渲染函数
ValidateRect(hwnd, NULL); // 更新客户区的显示
break; //跳出该switch语句 case WM_KEYDOWN: // 若是键盘按下消息
if (wParam == VK_ESCAPE) // 如果被按下的键是ESC
DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息
break; //跳出该switch语句 case WM_DESTROY: //若是窗口销毁消息
Direct3D_CleanUp(); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Direct3D_Init( )函数】--------------------------------------
// 描述:Direct3D初始化函数,进行Direct3D的初始化
//------------------------------------------------------------------------------------------------
HRESULT Direct3D_Init(HWND hwnd)
{
//--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
//--------------------------------------------------------------------------------------
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建
if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
return E_FAIL; //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
//--------------------------------------------------------------------------------------
D3DCAPS9 caps; int vp = ;
if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
{
return E_FAIL;
}
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
//--------------------------------------------------------------------------------------
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = WINDOW_WIDTH;
d3dpp.BackBufferHeight = WINDOW_HEIGHT;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = ;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = ;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = ;
d3dpp.FullScreen_RefreshRateInHz = ;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
//--------------------------------------------------------------------------------------
if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, &g_pd3dDevice)))
return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 if(!(S_OK==Objects_Init(hwnd))) return E_FAIL; //调用一次Objects_Init,进行渲染资源的初始化
return S_OK;
} //-----------------------------------【Object_Init( )函数】--------------------------------------
// 描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init(HWND hwnd)
{
return S_OK;
} //-----------------------------------【Direct3D_Render( )函数】--------------------------------------
// 描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
void Direct3D_Render(HWND hwnd)
{
//暂时为空
} //-----------------------------------【Direct3D_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
void Direct3D_CleanUp()
{
//暂时为空
}
2. Direct3D动画显示技术——交换链
件重点部分来说:交换链由两个或者两个以上的表面组成,每个表面都是存储着2D图形的一个线性数组,其中每个元素都表示这屏幕上的一个像素。对于三维物体,Direct3D使用深度缓冲区为最终绘制的图像的每个像素都存储一个深度信息。
前台缓冲区和后台缓冲是位于系统内存或显存里的内存块,对应于将要显示的二维显示区域。前台缓冲区是显示在屏幕上的,后台缓冲区则主要用于图形绘制的准备工作。图像在经过后台缓冲区中的处理后,就可以和前台缓冲区进行一个交换操作,即交换链页面翻转。通过前台缓冲区和后台缓冲区的配合,运用交换链技术,就可以流畅而高效地绘制出漂亮的动画图像来。
第二个要注意的是页面的翻转是经过交换指向表面内存的指针来实现的
3. 粗略理解固定功能渲染流水线体系
空间中的物体需要使用三维坐标来描述,但是显示器是二维的,所以在屏幕上渲染一个三维场景时,首先需要将描述物体的三维坐标变换为二维坐标,这在Direct3D中成为顶点坐标变换。
4. Direct3D设备接口
简而言之就是在初始化Direct3D的第三步填充 D3DPRESENT_PARAMETERS 时,第八个参数指定了在哪个窗口进行绘制,在第四步中:
(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,hwnd, vp, &d3dpp, &g_pd3dDevice)))
倒数第二个参数指定的是D3DPRESENT_PARAMETERS的实例d3dpp,这样就间接地把hwnd和Direct3D设备联系起来了,之后便可以通过 g_pd3dDevice 指针进行各种绘制操作了。
5. Direct3D中绘制二维文本
ID3DXFont接口负责Direct3D应用程序中创建字体以及实现二维文本的绘制,该接口封装了Windows字体和Direct3D设备指针。
D3DXCreateFont函数,用于创建字体:
HRESULT D3DXCreateFont(
_In_ LPDIRECT3DDEVICE9 pDevice, //Direct3D设备指针
_In_ INT Height, //字体高度
_In_ UINT Width, //字体宽度
_In_ UINT Weight, //字体的权重值
_In_ UINT MipLevels, //字体的过滤属性
_In_ BOOL Italic, //是否是斜体
_In_ DWORD CharSet, //使用的字符集
_In_ DWORD OutputPrecision, //输出文本精度
_In_ DWORD Quality, //输出质量
_In_ DWORD PitchAndFamily, //字体的索引号,通常是奢设为0
_In_ LPCTSTR pFacename, //字体名称,如“微软雅黑”
_Out_ LPD3DXFONT *ppFont //存储我们新创建的字体指针,我们要靠这个它来进行字体绘制相关操作
);
DrawText函数,用于绘制文本:
INT DrawText(
[in] LPD3DX10SPRITE pSprite, //指定字符串所属的ID3DXSprite对象接口,设为0表示在当前窗口绘制字符串
[in] LPCTSTR pString, //指定我们将要绘制的字符串内容
[in] INT Count, //指定绘制字符个数,-1表示自动绘制到字符串结束
[in] LPRECT pRect, //绘制字符串的矩形区域位置
[in] UINT Format, //指定在上面矩形区域的摆放属性,比如水平居中什么的
[in] D3DXCOLOR Color //指定字符串颜色
);
6. Direct3D渲染五部曲
五个步骤:
- 清屏操作
- 开始场景
- 正式绘制
- 结束场景
- 翻转显示
第一步:清屏操作
绘制画面之前需要调用IDirect3DDevice9接口的Clear方法将后台缓冲区中的内容清空,并设置表面填充颜色等。
HRESULT Clear(
[in] DWORD Count, //指定接下来的一个参数pRect指向的矩形数组中矩形的数量
[in] const D3DRECT *pRects, //要清空的目标矩形区域
[in] DWORD Flags, //指定需要清空的缓冲区
[in] D3DCOLOR Color, //指定在清空颜色缓冲区后每个像素对应的颜色值
[in] float Z, //指定清空深度缓冲区后每个像素对应的深度值
[in] DWORD Stencil //指定清空缓冲区之后模版缓冲区中每个像素对应的模版值
);
调用实例:
g_pd3dDevice->Clear(, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(, , ), 1.0f, );
第二步:开始绘制
g_pd3dDevice->BeginScene();
注意的是,IDirect3DDevice9::BeginScene() 和 IDirect3DDevice9::EndScene() 是成对出现的。
第三步:正式绘制
在BeginScene()方法和EndScene()方法之间,通常很长,这一步的代码取决于我们想要绘制什么样的内容。
第四步:结束绘制
调用EndScene()方法:
g_pd3dDevice->EndScene();
第五步:翻转显示
调用Present()函数,一个很重要的函数:
HRESULT Present(
[in] const RECT *pSourceRect, //指向复制源矩形区域指针。一般设为NULL
[in] const RECT *pDestRect, //指向复制目标矩形区域指针。一般也设为NULL
[in] HWND hDestWindowOverride, //指向当前绘制的窗口句柄,设为0或NULL表示取之前初始化第三步填充D3DPRESENT_PARAMETERS结构题中的 hDeviceWindows值,显然一般设为NULL
[in] const RGNDATA *pDirtyRegion //指向最小更新区域的指针,一般设为NULL
);
四个设为NULL的参数。。。。。额。。。还真好记。
g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示
来个示例程序,功能是获取每秒帧数函数:
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h> //-----------------------------------【库文件包含部分】---------------------------------------
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib") //-----------------------------------【宏定义部分】--------------------------------------------
#define WINDOW_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE L"【致我们永不熄灭的游戏开发梦想】Direct3D渲染五步曲 示例程序" //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //定义一个安全释放宏,便于后面COM接口指针的释放 //-----------------------------------【全局变量声明部分】-------------------------------------
// 描述:全局变量的声明
//------------------------------------------------------------------------------------------------
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象
ID3DXFont* g_pFont=NULL; //字体COM接口
float g_FPS = 0.0f; //一个浮点型的变量,代表帧速率
wchar_t g_strFPS[]; //包含帧速率的字符数组 //-----------------------------------【全局函数声明部分】-------------------------------------
// 描述:全局函数声明,防止“未声明的标识”系列错误
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );//窗口过程函数
HRESULT Direct3D_Init(HWND hwnd); //在这个函数中进行Direct3D的初始化
HRESULT Objects_Init(HWND hwnd); //在这个函数中进行要绘制的物体的资源初始化
VOID Direct3D_Render(HWND hwnd); //在这个函数中进行Direct3D渲染代码的书写
VOID Direct3D_CleanUp( ); //在这个函数中清理COM资源以及其他资源
float Get_FPS(); //-----------------------------------【WinMain( )函数】--------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{
//【1】窗口创建四步曲之一:开始设计一个完整的窗口类
WNDCLASSEX wndClass = { }; //用WINDCLASSEX定义了一个窗口类
wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小
wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式
wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针
wndClass.cbClsExtra = ; //窗口类的附加内存,取0就可以了
wndClass.cbWndExtra = ; //窗口的附加内存,依然取0就行了
wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。
wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,,,LR_DEFAULTSIZE|LR_LOADFROMFILE); //本地加载自定义ico图标
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。
wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个白色画刷句柄
wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。
wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 //【2】窗口创建四步曲之二:注册窗口类
if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
return -; //【3】窗口创建四步曲之三:正式创建窗口
HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //Direct3D资源的初始化,调用失败用messagebox予以显示
if (!(S_OK==Direct3D_Init (hwnd)))
{
MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), ); //使用MessageBox函数,创建一个消息窗口
} //【4】窗口创建四步曲之四:窗口的移动、显示与更新
MoveWindow(hwnd,,,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,使窗口左上角位于(250,80)处
ShowWindow( hwnd, nShowCmd ); //调用ShowWindow函数来显示窗口
UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //【5】消息循环过程
MSG msg = { }; //初始化msg
while( msg.message != WM_QUIT ) //使用while循环
{
if( PeekMessage( &msg, , , , PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage( &msg ); //将虚拟键消息转换为字符消息
DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。
}
else
{
Direct3D_Render(hwnd); //进行渲染
}
}
//【6】窗口类的注销
UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); //程序准备结束,注销窗口类
return ;
} //-----------------------------------【WndProc( )函数】--------------------------------------
// 描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message ) //switch语句开始
{
case WM_PAINT: // 若是客户区重绘消息
Direct3D_Render(hwnd); //调用Direct3D渲染函数
ValidateRect(hwnd, NULL); // 更新客户区的显示
break; //跳出该switch语句 case WM_KEYDOWN: // 若是键盘按下消息
if (wParam == VK_ESCAPE) // 如果被按下的键是ESC
DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息
break; //跳出该switch语句 case WM_DESTROY: //若是窗口销毁消息
Direct3D_CleanUp(); //调用自定义的资源清理函数Game_CleanUp()进行退出前的资源清理
PostQuitMessage( ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句
return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程
} return ; //正常退出
} //-----------------------------------【Direct3D_Init( )函数】--------------------------------------
// 描述:Direct3D初始化函数,进行Direct3D的初始化
//------------------------------------------------------------------------------------------------
HRESULT Direct3D_Init(HWND hwnd)
{
//--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
//--------------------------------------------------------------------------------------
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建
if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
return E_FAIL; //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
//--------------------------------------------------------------------------------------
D3DCAPS9 caps; int vp = ;
if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
{
return E_FAIL;
}
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
else
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
//--------------------------------------------------------------------------------------
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = WINDOW_WIDTH;
d3dpp.BackBufferHeight = WINDOW_HEIGHT;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = ;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = ;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = ;
d3dpp.FullScreen_RefreshRateInHz = ;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //--------------------------------------------------------------------------------------
// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
//--------------------------------------------------------------------------------------
if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, &g_pd3dDevice)))
return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 if(!(S_OK==Objects_Init(hwnd))) return E_FAIL; //调用一次Objects_Init,进行渲染资源的初始化
return S_OK;
} //-----------------------------------【Object_Init( )函数】--------------------------------------
// 描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init(HWND hwnd)
{
//创建字体
if(FAILED(D3DXCreateFont(g_pd3dDevice, , , , , false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, , _T("微软雅黑"), &g_pFont)))
return E_FAIL;
srand(timeGetTime()); //用系统时间初始化随机种子
return S_OK;
} //-----------------------------------【Direct3D_Render( )函数】-------------------------------
// 描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
void Direct3D_Render(HWND hwnd)
{
//--------------------------------------------------------------------------------------
// 【Direct3D渲染五步曲之一】:清屏操作
//--------------------------------------------------------------------------------------
g_pd3dDevice->Clear(, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(, , ), 1.0f, ); //定义一个矩形,用于获取主窗口矩形
RECT formatRect;
GetClientRect(hwnd, &formatRect); //--------------------------------------------------------------------------------------
// 【Direct3D渲染五步曲之二】:开始绘制
//--------------------------------------------------------------------------------------
g_pd3dDevice->BeginScene(); // 开始绘制
//--------------------------------------------------------------------------------------
// 【Direct3D渲染五步曲之三】:正式绘制,在这里我们写了四段文字
//--------------------------------------------------------------------------------------
//在窗口右上角处,显示每秒帧数
int charCount = swprintf_s(g_strFPS, , _T("FPS:%0.3f"), Get_FPS() );
g_pFont->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_XRGB(,,)); //在纵坐标100处,写第一段文字
formatRect.top = ;//指定文字的纵坐标
g_pFont->DrawText(, _T("【致我们永不熄灭的游戏开发梦想】"), -, &formatRect, DT_CENTER,
D3DCOLOR_XRGB(,,)); //在纵坐标250处,写第二段文字
formatRect.top = ;
g_pFont->DrawText(, _T("游戏开发的世界,我们来降服你了~!"), -, &formatRect,
DT_CENTER, D3DCOLOR_XRGB(,,)); //在纵坐标400处,写第三段文字
formatRect.top = ;
g_pFont->DrawText(, _T("闪闪惹人爱"), -, &formatRect, DT_CENTER,
D3DCOLOR_XRGB(rand() % , rand() % , rand() % ));//采用随机RGB值,做出“闪闪惹人爱”的特效 //--------------------------------------------------------------------------------------
// 【Direct3D渲染五步曲之四】:结束绘制
//--------------------------------------------------------------------------------------
g_pd3dDevice->EndScene(); // 结束绘制
//--------------------------------------------------------------------------------------
// 【Direct3D渲染五步曲之五】:显示翻转
//--------------------------------------------------------------------------------------
g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示
} //-----------------------------------【Get_FPS( )函数】------------------------------------------
// 描述:用于计算每秒帧速率的一个函数
//--------------------------------------------------------------------------------------------------
float Get_FPS()
{
//定义四个静态变量
static float fps = ; //我们需要计算的FPS值
static int frameCount = ;//帧数
static float currentTime =0.0f;//当前时间
static float lastTime = 0.0f;//持续时间 frameCount++;//每调用一次Get_FPS()函数,帧数自增1
currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间 //如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零
if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟
{
fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值
lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间
frameCount = ;//将本次帧数frameCount值清零
} return fps;
} //-----------------------------------【Direct3D_CleanUp( )函数】--------------------------------
// 描述:资源清理函数,在此函数中进行程序退出前资源的清理工作
//---------------------------------------------------------------------------------------------------
void Direct3D_CleanUp()
{
//释放COM接口对象
SAFE_RELEASE(g_pFont)
SAFE_RELEASE(g_pd3dDevice)
}
注:
参考博客:
【Visual C++】游戏开发笔记三十二 浅墨DirectX提高班之一 DirectX大局观认知篇