一.前言
學習DirectX的初衷是為了做游戲,為了開發游戲引擎。我在之前其實學習過一段時間的DirectX,但是由於后來一些其他原因將DirectX的學習擱置到了一邊。現在有了比較充裕的時間,想把DirectX的相關知識撿起來,復習以前學習過的知識,順帶學習新的知識。
二.windows相關
首先,其實我對windows編程了解也不是很多。大一的時候看過一段時間的windows程序設計這本書,但是好像看天書一樣,學了沒有多久就轉去學Qt去了。后來回來學習DirectX的時候,發現需要windows編程的基礎,當時還比較后悔沒有好好看windows程序設計這本書。還在學習DirectX對windows編程能力的要求不是很高,當然你對windows編程越了解越好啦。不了解windows編程也沒有關系,花一點點時間去了解一下windows窗口的建立和消息循環相關的知識就可以開始DirectX的學習了。當然以后要是有時間還是多學習下windows編程相關知識比較好。
因為我不想花費太多的時間在windows編程上面,所以說在這個地方我也不會做太多的介紹。而且為了不每次在寫一個又一個的demo的時候,都要重復創建一個windows窗口的過程,所以我在這里做了一個小小的封裝。我將這個封裝后的類稱為Engine_Application,它具體的定義如下:
1 /*======================================================== 2 * 應用程序類,用於創建窗口 3 *=======================================================*/ 4 5 6 #ifndef _APPLICATION_H_ 7 #define _APPLICATION_H_ 8 9 #include <windows.h> 10 #include <functional> 11 #include <fstream> 12 13 #pragma comment(lib, "winmm.lib") 14 15 typedef LRESULT (CALLBACK* MsgProc)(HWND, UINT msg, WPARAM, LPARAM); 16 17 class Engine_Application 18 { 19 20 public: 21 22 Engine_Application(MsgProc msg); 23 24 virtual ~Engine_Application(); 25 26 public: 27 28 bool InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, int x = 0, 29 int y = 0, int width = 800, int height = 600); 30 31 void Show(int nCmdShow); 32 33 int Run(); 34 35 void SetRenderFunc(std::function<void (float dt)> pFunc); 36 37 public: 38 39 int GetWinWidth()const; 40 41 int GetWinHeight()const; 42 43 HWND GetWinHwnd()const; 44 45 protected: 46 47 HWND m_hWnd; 48 HINSTANCE m_hInstance; 49 50 MsgProc m_pMsg; 51 52 wchar_t *m_pClassName; 53 54 int m_nWidth; 55 int m_nHeight; 56 57 std::function<void (float dt)> m_pRenderFunc; 58 }; 59 60 61 62 #endif
現在讓我們來具體看一下這個類里面的內容:
a.消息處理函數
1 typedef LRESULT (CALLBACK* MsgProc)(HWND, UINT msg, WPARAM, LPARAM);
首先我定義了一個函數指針,注意看一下就會發現這個函數指針的原型和一個窗口的消息函數是一模樣的。是的,我這里是定義了Application類中的消息函數,可以在后面的成員變量中發現一個MsgProc的變量,那個變量就是用於保存Application類的消息函數指針,作用是用於Application的消息處理。而且我們發現Application類的構造函數也必須要求傳遞一個MsgProc類型的變量,因為一個application需要有一個自己的消息函數。
b.窗口初始化函數
bool InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, int x = 0, int y = 0, int width = 800, int height = 600);
參數十分簡單,都是一些初始化一個windows窗口的一些基本東西,從參數名稱也可以看出他們的作用。
hInstance:應用的實例句柄
className:類名
style:窗口的風格
x,y:窗口左上角的坐標
width,height:窗口的寬度和高度
c.消息循環和渲染處理函數
int Run();
d.設置渲染函數回調
void SetRenderFunc(std::function<void (float dt)> pFunc);
這里用了一個C++11中的新特性std::function,沒有接觸過的朋友可以先去百度一下。說簡單點,這就是一個和函數指針類似的東西。<>中的內容表明了,它是指向一個返回值為void,參數為float類型的函數。這里的float參數,傳遞的是兩幀之間的時間間隔。
e.具體實現
介紹了大半天的類的定義,現在我把它的具體實現放在下面,實現很簡單,相信有一點windows基礎的都能看得很明白。因為我不想在windows下花太多的時間,具體代碼如下:
1 #include "Engine_Application.h" 2 3 Engine_Application::Engine_Application(MsgProc msg) 4 { 5 m_pMsg = msg; 6 } 7 8 Engine_Application::~Engine_Application() 9 { 10 UnregisterClass(m_pClassName, m_hInstance); 11 } 12 13 14 bool Engine_Application::InitWindow(HINSTANCE hInstance, wchar_t *className, wchar_t *winName, DWORD style, 15 int x /* = 0 */, int y /* = 0 */, 16 int width /* = 800 */, int height /* = 600 */) 17 { 18 WNDCLASS wnd; 19 20 wnd.cbClsExtra = 0; 21 wnd.cbWndExtra = 0; 22 wnd.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); 23 wnd.hCursor = LoadCursor(NULL, IDC_ARROW); 24 wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); 25 wnd.hInstance = hInstance; 26 wnd.lpfnWndProc = m_pMsg; 27 wnd.lpszClassName = className; 28 wnd.lpszMenuName = NULL; 29 wnd.style = CS_VREDRAW | CS_HREDRAW; 30 31 if (!RegisterClass(&wnd)) 32 { 33 return false; 34 } 35 36 HWND hwnd = CreateWindow(className, winName, style, x, y, width, height, NULL, NULL, hInstance, NULL); 37 38 if (!hwnd) 39 { 40 return false; 41 } 42 43 m_hWnd = hwnd; 44 m_hInstance; 45 46 m_nWidth = width; 47 m_nHeight = height; 48 49 m_pClassName = className; 50 51 return true; 52 } 53 54 55 void Engine_Application::Show(int nCmdShow) 56 { 57 ShowWindow(m_hWnd, nCmdShow); 58 UpdateWindow(m_hWnd); 59 } 60 61 void Engine_Application::SetRenderFunc(std::function<void (float dt)> pFunc) 62 { 63 m_pRenderFunc = pFunc; 64 } 65 66 67 int Engine_Application::Run() 68 { 69 MSG msg; 70 ZeroMemory(&msg, sizeof(MSG)); 71 72 while (msg.message != WM_QUIT) 73 { 74 if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 75 { 76 TranslateMessage(&msg); 77 DispatchMessage(&msg); 78 79 } else 80 { 81 if (m_pRenderFunc) 82 { 83 static float lastTime = 0.0f; 84 static float currentTime = 0.0f; 85 86 currentTime = static_cast<float>(timeGetTime()); 87 float delta = (currentTime - lastTime) * 0.001f; 88 89 m_pRenderFunc(delta); 90 91 lastTime = currentTime; 92 } 93 } 94 } 95 96 97 return msg.wParam; 98 } 99 100 int Engine_Application::GetWinHeight()const 101 { 102 return m_nHeight; 103 } 104 105 int Engine_Application::GetWinWidth()const 106 { 107 return m_nWidth; 108 } 109 110 HWND Engine_Application::GetWinHwnd()const 111 { 112 return m_hWnd; 113 }
f.DirectD程序流程
這里不是說windows相關嗎?為什么會說上Direct3D呢?因為上面的Application類我是為了寫Direct3D封裝的,在消息循環的地方做了下處理,所以先說下Direct3D程序流程。先看下面的圖:
從上圖來看windows窗口我們已經創建好了,至於DirectX的初始化,馬上就會講到。所以說我們現在關心的是消息循環的部分。可以看到進入消息循環之后做了兩個事情,一個是消息處理,一個是渲染處理。消息循環的部分,我放在了Run()這個函數中。我們截取出一段代碼來看。
while (msg.message != WM_QUIT) { if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { if (m_pRenderFunc) { static float lastTime = 0.0f; static float currentTime = 0.0f; currentTime = static_cast<float>(timeGetTime()); float delta = (currentTime - lastTime) * 0.001f; m_pRenderFunc(delta); lastTime = currentTime; } } }
在上面的代碼中我們首先檢測當前的消息是否為WM_QUIT,如果是則退出消息循環,程序結束。否則從消息隊列從去取出消息,如果取出一個消息則將起發送到消息處理函數進行處理。否則則調用m_pRenderFunc進行渲染操作。在if語句中還計算了當前兩次渲染之間的時間間隔。
三.DirectX基礎
說了這么久終於要開始進入正題了,關於一些DirectX的發展史什么的就不細說了,相信大家也不喜歡聽,我們直接開始吧。
1.開發環境的搭建
這個開發環境的搭建,網上的教程很多,而且我也比較懶,就直接貼一個鏈接吧。傳送門:http://www.cnblogs.com/graphics/archive/2009/11/04/1595727.html
2.DirectX結構
a.HAL(硬件抽象層)
大多數基於Direcr3D API設計開發的三維圖形程序都運行於硬件抽象層(Hardware Abstraction Layer),也就是HAL之上。使用HAL的好處,是既能充分利用系統硬件的加速功能,又隱藏了硬件相關的設備特性。這就是說,Direct3D利用HAL實現了設備無關的特性,通過Direct3D可以編寫出與設備無關的高效代碼。硬件抽象層是由硬件制造商提供的特定於硬件的接口,Direct3D利用該接口實現了對顯示硬件的直接訪問。所以,應用程序永遠不需要訪問HAL,直接與Direct3D API打交道就可以了。HAL可以是顯卡的一部分,也可以是和顯卡驅動程序相互關聯的單獨動態鏈接庫(DLL)。
HAL僅僅是與設備相關的代碼,對於硬件不支持的功能,它並不提供軟件模擬。在DirectX 9.0中,針對同一種顯示設備,HAL提供3種頂點處理模式:軟件頂點處理、硬件頂點處理和混合頂點處理。還有,純硬件設備是HAL的變體,純硬件設備類型僅支持硬件頂點處理。
b.REF(參考光柵設備)
有時候,Direct3D提供的某些功能不被我們的顯卡支持,但是我們偏偏想去使用這些功能的話,就可以使用一下Direct3D的輔助設備,也就是參考光柵設備(Reference Rasterizer Device,REF)。這種REF設備可以以軟件運算的方式完全支持Direct3D API。借助REF設備,我們可以在代碼中使用那些不為當前硬件所支持的特性,並對這些特征進行測試。
3.初始化DirectX
DirectX的初始化可以分解為一下幾個步驟:
a.獲取接口IDirect3D9指針。
這個接口主要用於獲取硬件設備信息,並且創建接口IDirect3DDevice9。獲取IDirect3D9的代碼十分簡單,如下所示:
IDirect3D9 *pD3D = nullptr;
pD3D = Direct3DCreate9(D3D_SDK_VERSION);
Direct3DCreate9函數返回一個IDirect3D9d的指針,如果創建失敗會返回NULL,它的參數必須為D3D_SDK_VERSION,這樣才能保證會引用正確的頭文件。
b.檢測設備性能
檢測設備性能(D3DCAPS9),判斷顯卡是否支持硬件頂點運算(當然還可以檢測硬件對其他性能的支持情況)。為什么要進行檢查,因為在后面初始化IDirect3DDevice9的時候,我們需要根據主顯卡的一些性能來進行初始化。要檢查設備性能,首先我們要獲取一個D3DCAPS9對象,可以通過如下代碼獲取:
D3DCAPS9 caps;
pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
GetDeviceCaps的原型如下:
HRESULT GetDeviceCaps( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [out] D3DCAPS9 *pCaps );
Adater:用於指定物理顯卡的序號, D3DADAPTER_DEFAULT表示為當前正在使用的顯卡。
DeviceType:指定設備的類型。 例如:D3DDEVTYPE_HAL(硬件設備),D3DDEVTYPE_REF(軟件設備)
pCaps:返回已經被填充的D3DCAPS9結構
一旦獲取了D3DCAPS9結構對象,就可以通過&運算檢測當前的硬件是否支持某些功能。例如檢測是否支持硬件頂點運算:
DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; }
通過將DevCaps與D3DDEVCAPS_HWRASTERIZATION做&運算,如果為true則表示支持硬件頂點運算,將vp設為D3DCREATE_HARDWARE_VERTEXPROCESSING。否則支持設為D3DCREATE_SOFTWARE_VERTEXPROCESSING,表示采用軟件頂點運算。這里將這個常量值保存在vp這個變量中,因為在后面創建IDirect3DDevice9對象的時候會用到這個變量。
c.填充D3DPRESENT_PARAMETER結構
IDirect3DDevice9的創建過程中,會要求一個D3DPRESENT_PARAMETER類型的參數。所以說,這一步,我們需要填充D3DPRESENT_PARAMETER結構中的內容,就好像我們在你創建一個windows窗口的時候需要填充WNDCLASS結構一樣。該結構的定義如下:
typedef struct D3DPRESENT_PARAMETERS { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS, *LPD3DPRESENT_PARAMETERS;
BackBufferWidth:指定后台緩沖區的寬度
BackBufferHeigh:指定后台緩沖區的高度
BackBufferFormat:指定后台緩沖區的保存像素格式,可以用D3DFORMAT枚舉定義。可以用GetDisplayMode獲取當前像素格式。
BackBufferCount:指定后台緩沖區的數量。
MultiSampleType:表示多重采樣的類型。通常我們將MultiSampleType設為D3DMULTISAMPLE_NONE。
MultiSampleQuality:表示多重采樣的格式。通常我們將其設為0。
SwapEffect:用於指定Direct3D如何將后台緩沖區的內容復制到前台的緩存中,由於D3DSWAPEFFECT_DISCARD效率最高,通常我們都將其設為D3DSWAPEFFECT_DISCARD。
hDeviceWindow:窗口句柄,這里指定我們需要在哪個窗口上進行繪制。這個參數也可以設為NULL,這時就表示對當前被激活的窗口進行繪制。
Windowed:表示繪制窗體的顯示模式,為TRUE使表示使用窗口模式,為FALSE則表示使用全屏模式。
EnableAutoDepthStencil:表示Direct3D是否為應用程序自動管理深度緩存,這個成員為TRUE的話,表示需要自動管理深度緩存,這時候就需要對下一個成員AutoDepthStencilFormat進行相關像素格式的設置。
AutoDepthStencilFormat:如果我們把EnableAutoDepthStencil成員設為TRUE的話,在這里就需要指定AutoDepthStencilFormat的深度緩沖的像素格式。具體格式可以在結構體D3DFORMAT中進行選取。
Flags:表示附加屬性,通常都設為0.
FullScreen_RefreshRateInHz:表示在全屏模式時指定的屏幕的刷新率,,在全屏模式時在EnumAdapterModes枚舉類型中進行取值,我們在全屏模式時將其設為默認值D3DPRESENT_RATE_DEFAULT,窗口模式時這個成員沒有意義,我們把它就設為0了。
PresentationInterval:用於指定指定后台緩沖區與前台緩沖區的最大交換頻率,可在D3DPRESENT中進行取值。
下面看一個填充的實例:
D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = 600; d3dpp.BackBufferWidth = 800; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = TRUE;
上面的代碼中我們將后台緩存的大小設置為800*600,並且是窗口模式。
d.創建IDirect3DDevice9對象
有上面三步的准備工作,現在我們可以創建IDirect3DDevice9對象了。該對象代表了我們用來顯示3D圖形的物理硬件設備,后面涉及到的一些圖像的繪制就全靠它了,它的重要性就不言而喻了吧。IDirect3DDevice9對象的創建需要借助CreateDevice這個函數,它的原型如下:
HRESULT CreateDevice( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [in] HWND hFocusWindow, [in] DWORD BehaviorFlags, [in, out] D3DPRESENT_PARAMETERS *pPresentationParameters, [out, retval] IDirect3DDevice9 **ppReturnedDeviceInterface );
這個函數的幾個參數都十分簡單,在前面我們基本上都已經介紹過了。前兩個參數和GetDeviceCaps的前兩個參數一模一樣,至於第三個參數就是我們剛剛保存好的vp這個變量了,還記得吧。第四個參數就是我們剛剛填充好的D3DPRESENT_PARAMETER的一個指針,最后一個參數就是我們需要的IDirect3DDevice9了。下面看一個例子:
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, vp, &d3dpp, &pDevice);
怎么樣,設備的創建十分簡單吧。
下面再貼上一段DirectX初始化的完整代碼:
m_pD3D = Direct3DCreate9(D3D_SDK_VERSION); FALSE_RETURN(m_pD3D); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = m_nHeight; d3dpp.BackBufferWidth = m_nWidth; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = bWindowed; m_bWindowed = bWindowed; D3DCAPS9 caps; m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; } m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dpp, &m_pDevice);
4.DirectX渲染的基本步驟
說了一大篇,終於說到渲染了。廢話不多說,開始說渲染。
a.清屏操作
每當繪制畫面之前,我們都需要通過IDirect3DDevice9接口的Clear方法將后台緩沖區中的內容進行清空,並設置上清屏之后的顏色。其中Clear函數的原型聲明如下:
HRESULT Clear( [in] DWORD Count, [in] const D3DRECT *pRects, [in] DWORD Flags, [in] D3DCOLOR Color, [in] float Z, [in] DWORD Stencil );
Count, pRects:我將第一個和第二個參數放到一起來說。首先是pRects,是一個指向D3DRECT的指針。Clear允許進行部分清除,但是你要提供清除區域的矩形位置。pRects參數就是用於保存需要清除的矩形區域,Count參數則保存了矩形區域的數量。這有點類似於我們寫程序的時候傳遞一個數組,一個參數傳遞首地址而另一個參數傳遞數組的長度,是一個道理。如果pRects設置為NULL,則Count必須設置為0,這個時候表示對整個視口進行清除。
Flags:這個參數用於指定那些緩存需要被清除,可以的取值有D3DCLEAR_TARGET(后台緩存),D3DCLEAR_ZBUFFER(深度緩存),D3DCLEAR_STENCIL(模板緩存)。可以用|運算符來進行多個緩存清除的操作。
Color:指定清除后的顏色。
Z:用於指定清空深度緩沖區后每個像素對應的深度值。
Stencil:用於指定清空模板緩沖區之后模板緩沖區中每個像素對應的模板值。
例如,下面的代碼表示清除整個視口的后台緩存,深度緩存,模板緩存。將清除后深度緩存的深度值設為1.0,模板緩存的模板值設為0,視口顏色設為紅色。
pDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, D3DCOLOR_XRGB(255, 0, 0), 1.0, 0);
b.開始渲染
在使用DirectX進行繪制之前要首先調用IDirect3DDevice9::BeginScene函數,這個函數和IDirect3DDevice9::EndScene總是成對出現。IDirect3DDevice9::BeginScene()函數調用很簡單,沒有參數,返回值為HRESULT。
pDevice->BeginScene();
c.添加渲染內容
調用IDirect3DDevice9::BeginScene()之后,就可以在后面加上我們需要渲染的內容了。這里的內容就太廣泛了,你可以在這里繪制一個簡單的三角形,也可以再這里繪制一些復雜的3d場景,這個就看具體需求了。
d.完成繪制,翻轉顯示
渲染部分的代碼添加完成之后,就需要調用IDirect3DDevice9::EndScene()來完成渲染了,前面就已經說過IDirect3DDevice9::EndScene()和IDirect3DDevice9::BeginScene()是成對出現的。光光完成渲染還不夠,我們的渲染工作都是在后台緩存完成的,必須將它進行一個翻轉,切換過來才能正常的顯示。這個時候我們就需要調用Present來完成翻轉工作。該函數的原型聲明如下:
HRESULT Present( [in] const RECT *pSourceRect, [in] const RECT *pDestRect, [in] HWND hDestWindowOverride, [in] const RGNDATA *pDirtyRegion );
pSourceRect:表示指向復制源矩形區域的指針,一般我們都將其設為NULL。
pDestRect:表示指向復制目標矩形區域的指針,一般我們將其設為NULL。
hDestWindowOverride:表示指向當前繪制的窗口句柄。如果我們設為0或NULL就表示取我們之前初始化DirectX填充的D3DPRESENT_PARAMETERS結構體中的hDeviceWindows的值,一般我們將其設為NULL。
pDirtyRegion:表示指向最小更新區域的指針,一般我們將其設為NULL。
調用也很簡單,如下:
pDevice->Present(NULL, NULL, NULL, NULL);
e.交換鏈
通過上面的內容,已經了解了使用DirectX進行渲染的一個基本流程。但是有沒有考慮到這樣一個問題,如果我們在渲染部分所做的一些操作很復雜,也許下一幀開始渲染的時候,當前幀的內容還沒有渲染完成。而且一些不斷重復的進行一些清屏操作,可能會造成閃屏的現象。為了解決這個問題,Direct3D采用了一個叫交換鏈的東西(Swap Chain)。用接口IDirect3DSwapChain9來表示。快速交換鏈一般維護着兩個到三個的表面,工作方式有點像放電影。可以分為前台緩沖區和后台緩沖區兩個部分,前台緩沖區和后台緩沖區是存在系統內存或者顯存里的一個內存塊。前台緩沖區中的內容是顯示器顯示的內容,我們可以看見的內容。而后台緩沖區則主要用於下一幀或者幾幀的繪制,當當前的內容顯示完成之后,則直接將后台緩存區的內容復制到前台緩沖區進行顯示,而后台緩沖區則進行后面內容的繪制。這樣就可以讓渲染的內容顯得更加的平滑。如下圖所示:
四.Engine_Render
為了簡化后面的一些工作,我打算對Direct3D也進行一個簡單的封裝,目前也才剛剛開始。涉及到的例如渲染狀態設置,頂點,矩陣變換的內容都還未進行封裝。目前僅僅是將初始化內容進行了一下封裝,方便測試一些demo。我將這個類叫做Render,它的定義如下:
/*================================================= * 渲染設備類 *===============================================*/ #ifndef _RENDERDEVICE_H_ #define _RENDERDEVICE_H_ #include <windows.h> #include <d3d9.h> #include <d3dx9.h> #include <fstream> #include "Engine_Comment.h" #pragma comment(lib, "d3d9.lib") #pragma comment(lib, "d3dx9.lib") #define DEBUG class Engine_RenderDevice { public: Engine_RenderDevice(HWND hwnd, int width, int height); ~Engine_RenderDevice(); public: bool InitDevice(bool bWindowed = true); bool InitFontFPS(); bool IsRendering()const; bool BeginRendering(bool bTarget, bool bDepth, bool bStencil); void Clear(bool bTarget, bool bDepth, bool bStencil); void EndRendering(); void SetClearColor(unsigned r, unsigned g, unsigned b); D3DCOLOR GetClearColor()const; void ShowFPS(float dt); #ifdef DEBUG IDirect3DDevice9 *GetDevice()const; #endif // DEBUG protected: IDirect3D9 *m_pD3D; IDirect3DDevice9 *m_pDevice; ID3DXFont *m_pFontFPS; D3DCOLOR m_dwClearColor; HWND m_hWnd; int m_nWidth; int m_nHeight; bool m_bWindowed; bool m_bIsRendering; float m_fFPS; }; #endif // !_RENDERDEVICE_H_
再附上實現部分的源碼:
#include <cstdio> #include <iostream> #include "Engine_RenderDevice.h" Engine_RenderDevice::Engine_RenderDevice(HWND hwnd, int width, int height) { m_pD3D = nullptr; m_pDevice = nullptr; m_pFontFPS = nullptr; m_hWnd = hwnd; m_nWidth = width; m_nHeight = height; m_dwClearColor = D3DCOLOR_XRGB(0, 0, 0); m_bIsRendering = false; m_fFPS = 0.0f; } Engine_RenderDevice::~Engine_RenderDevice() { Release<ID3DXFont *>(m_pFontFPS); Release<IDirect3DDevice9 *>(m_pDevice); Release<IDirect3D9 *>(m_pD3D); } bool Engine_RenderDevice::InitDevice(bool bWindowed /* = true */) { m_pD3D = Direct3DCreate9(D3D_SDK_VERSION); FALSE_RETURN(m_pD3D); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.BackBufferCount = 1; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferHeight = m_nHeight; d3dpp.BackBufferWidth = m_nWidth; d3dpp.EnableAutoDepthStencil = TRUE; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.hDeviceWindow = m_hWnd; d3dpp.MultiSampleQuality = 0; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.Windowed = bWindowed; m_bWindowed = bWindowed; D3DCAPS9 caps; m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps); DWORD vp = 0; if (caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) { vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; } else { vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; } m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dpp, &m_pDevice); FALSE_RETURN(m_pDevice); InitFontFPS(); return true; } bool Engine_RenderDevice::InitFontFPS() { D3DXCreateFont(m_pDevice, 40, 0, 1000, D3DX_DEFAULT, FALSE, DEFAULT_CHARSET, 0, 0, 0, L"Times New Roman", &m_pFontFPS); FALSE_RETURN(m_pFontFPS); return true; } void Engine_RenderDevice::SetClearColor(unsigned r, unsigned g, unsigned b) { m_dwClearColor = D3DCOLOR_XRGB(r, g, b); } D3DCOLOR Engine_RenderDevice::GetClearColor()const { return m_dwClearColor; } bool Engine_RenderDevice::BeginRendering(bool bTarget, bool bDepth, bool bStencil) { DWORD flag = 0; if (bTarget) { flag |= D3DCLEAR_TARGET; } if (bDepth) { flag |= D3DCLEAR_ZBUFFER; } if (bStencil) { flag |= D3DCLEAR_STENCIL; } m_pDevice->Clear(0, NULL, flag, m_dwClearColor, 1.0f, 0); if (m_pDevice->BeginScene()) { m_bIsRendering = true; return true; } return false; } void Engine_RenderDevice::Clear(bool bTarget, bool bDepth, bool bStencil) { DWORD flag = 0; if (bTarget) { flag |= D3DCLEAR_TARGET; } if (bDepth) { flag |= D3DCLEAR_ZBUFFER; } if (bStencil) { flag |= D3DCLEAR_STENCIL; } if (m_bIsRendering) { m_pDevice->EndScene(); m_pDevice->Clear(0, NULL, flag, m_dwClearColor, 1.0f, 0); m_pDevice->BeginScene(); } } void Engine_RenderDevice::EndRendering() { if (m_bIsRendering) { m_pDevice->EndScene(); m_pDevice->Present(NULL, NULL, NULL, NULL); m_bIsRendering = false; } } void Engine_RenderDevice::ShowFPS(float dt) { static float delta = 0.0f; static int count = 0; delta += dt; ++count; if (delta >= 1.0f) { m_fFPS = count / delta; delta = 0.0f; count = 0; } /* 繪制FPS */ RECT rec; GetClientRect(m_hWnd, &rec); char fps[100]; sprintf(fps, "fps:%0.2f", m_fFPS); m_pFontFPS->DrawTextA(NULL, fps, -1, &rec, DT_LEFT, D3DCOLOR_XRGB(255, 0, 0)); } #ifdef DEBUG IDirect3DDevice9 *Engine_RenderDevice::GetDevice()const { return m_pDevice; } #endif // DEBUG
由於這個類目前的內容還十分簡單,有了前面的介紹,看懂的話肯定沒有什么問題,這里就不做說明了。
五.實例
前面說了這么長的篇幅,沒有什么例子來支撐,怎么能行。畢竟實踐是檢驗真理的唯一標准。本來我最開始的想法是直接做一個清屏色為紅色的窗口的,但是覺得這樣的效果給人太過枯燥,最后就還繪制了一個三角形。關於渲染部分的內容,可以不用抬去糾結。而且由於我還沒有封裝頂點,狀態設置等相關的內容,所以繪制的時候又把IDirect3DDevice9從對象里面拿出來了。只是暫時這樣做,以后會將這部分做好的。源碼如下:
#include "Engine_Application.h" #include "Engine_RenderDevice.h" struct Vertex { float x, y, z, rhw; D3DCOLOR color; const static DWORD FVF; }; const DWORD Vertex::FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE; Engine_RenderDevice *g_pDevice = nullptr; IDirect3DVertexBuffer9 *g_pVertexBuffer = nullptr; LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM w, LPARAM l) { switch (message) { case WM_KEYUP: if (w == VK_ESCAPE) { PostQuitMessage(0); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, message, w, l); } } void InitObjects() { Vertex triangle[] = { {400, 100, 0, 1.0, D3DCOLOR_XRGB(255, 0, 0)}, {700, 500, 0, 1.0, D3DCOLOR_XRGB(0, 255, 0)}, {100, 500, 0, 1.0, D3DCOLOR_XRGB(0, 0, 255)} }; void *temp; IDirect3DDevice9 *pDevice = g_pDevice->GetDevice(); pDevice->CreateVertexBuffer(sizeof(triangle), 0, Vertex::FVF, D3DPOOL_MANAGED, &g_pVertexBuffer, NULL); g_pVertexBuffer->Lock(0, sizeof(triangle), (void **)&temp, 0); memcpy(temp, triangle, sizeof(triangle)); g_pVertexBuffer->Unlock(); } void Update(float dt) { IDirect3DDevice9 *pDevice = g_pDevice->GetDevice(); pDevice->SetRenderState(D3DRS_LIGHTING, false); g_pDevice->BeginRendering(true, true, false); pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(Vertex)); pDevice->SetFVF(Vertex::FVF); pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); g_pDevice->ShowFPS(dt); g_pDevice->EndRendering(); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { Engine_Application *app = new Engine_Application(WndProc); app->InitWindow(hInstance, L"Engine", L"Engine_Application", WS_OVERLAPPEDWINDOW, 100, 100); app->SetRenderFunc(Update); app->Show(nShowCmd); g_pDevice = new Engine_RenderDevice(app->GetWinHwnd(), app->GetWinWidth(), app->GetWinHeight()); g_pDevice->InitDevice(); InitObjects(); return app->Run(); }
看了上面的代碼有沒有覺得,窗口的建立和Direct3D的初始化是多么簡潔。最后附上一張運行截圖:
PS:第一次寫這么長的博客,那些地方寫得不好的多多包涵。