在很多 DX11 的學(xué)習(xí)資料中掂恕,都告訴你狞悲,需要安裝 DirectX SDK。但微軟在發(fā)布 Windows 8 操作系統(tǒng)之后棋电,就把 DirectX SDK 集成進(jìn)了 Windows SDK,所以用 Visual Studio 2017 的小伙伴就不再需要安裝任何額外的安裝包了苇侵,只要在安裝 VS 的時(shí)候裝了 Visual C++ 和 Windows SDK赶盔,就可以直接愉快的開始 DX 的學(xué)習(xí)之旅了。
雖說不用安裝榆浓,不代表在第一次學(xué)習(xí) DX 的時(shí)候不會(huì)掉進(jìn)坑里于未,所以這里手把手的從安裝 VS2017 開始,到成功運(yùn)行第一個(gè) DirectX 程序陡鹃。
1. 安裝 Visual Studio 2017
下載 Visual Studio 2017 社區(qū)版烘浦,到本地雙擊運(yùn)行,等它安裝完成后看到這個(gè)界面:
勾選之后萍鲸,點(diǎn)擊右下角安裝按鈕闷叉,等待一段時(shí)間,安裝 Visual Studio猿推。建議安裝完成后不要?jiǎng)h除這個(gè)安裝管理器片习,以后會(huì)有用的。
注:因?yàn)槲业碾娔X上已經(jīng)安裝了 Visual Studio 蹬叭,所以右下角顯示的修改按鈕藕咏。
2. 運(yùn)行 Visual Studio 2017
安裝完成后,第一次運(yùn)行 VS秽五,可以看到這個(gè)界面孽查。
點(diǎn)擊左上角 文件-> 新建 -> 項(xiàng)目,可以看到如下的項(xiàng)目導(dǎo)航界面:
在這里坦喘,你只要正確安裝了 Visual Studio 2017 盲再,就可以在右邊選擇你想創(chuàng)建的項(xiàng)目類型,而我們的 DirectX 項(xiàng)目在這里創(chuàng)建瓣铣。
但今天我不算這樣創(chuàng)建答朋,我們從一個(gè)空項(xiàng)目開始,一步一步構(gòu)建一個(gè) DirectX 項(xiàng)目棠笑,這樣也方便我們了解 DX 代碼的結(jié)構(gòu)梦碗。
3. 創(chuàng)建空項(xiàng)目
初學(xué) C++ 的時(shí)候,大家肯定都寫過控制臺(tái)程序。而開發(fā) DX 洪规,我們就不能再用控制臺(tái)程序了印屁,因?yàn)楹诤鹾醯目刂婆_(tái)是顯示不出豐富多彩的圖形的。所以這里要?jiǎng)?chuàng)建桌面引用程序斩例。
這時(shí)雄人,可能有的以前用過 VS2015 或者 VS2012 的朋友第一用 VS2017 的時(shí)候會(huì)有點(diǎn)犯糊涂,因?yàn)橐郧白罱?jīng)典的Win32 項(xiàng)目創(chuàng)建選項(xiàng)沒有了念赶,我第一次用也蒙了很久础钠。原來再升級(jí)到 VS2017 時(shí),原來的 Win32 桌面應(yīng)用的選項(xiàng)被改成了 Windows 桌面向?qū)Ь恰.?dāng)你用這個(gè)開始創(chuàng)建項(xiàng)目的時(shí)候珍坊,你會(huì)發(fā)現(xiàn)它還是原來的味道。
注:如果用 VS2015 的朋友正罢,這里就選擇創(chuàng)建 Win32 桌面程序,項(xiàng)目的設(shè)置和這里用 VS2017 創(chuàng)建的設(shè)置一模一樣驻民。
創(chuàng)建完成發(fā)現(xiàn)什么代碼都沒有翻具,這很完美,干干凈凈的回还。下面就開始寫代碼
4. DirectX 的代碼
因?yàn)槭?Windows 應(yīng)用程序裆泳,自然繞不開 Win32 這道坎。這里我選擇寫的代碼是 DirectX11 的柠硕,至于 DirectX 12工禾,還沒開這個(gè)坑。
4.1 創(chuàng)建 d3dUtility.h 頭文件
將下面代碼復(fù)制進(jìn)去:
#pragma once
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")
#ifndef __d3dUtilityH__
#define __d3dUtilityH__
#include <Windows.h>
// 數(shù)學(xué)庫(kù)
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>
// DirectX11 相關(guān)庫(kù)
#include <d3d11.h>
#include <d3dcompiler.h>
namespace d3d {
// 初始化D3D
bool InitD3D(HINSTANCE hInstance, int width, int height,
ID3D11RenderTargetView** renderTargetView,
ID3D11DeviceContext** immediateContext,
IDXGISwapChain** swapChain,
ID3D11Device** device,
ID3D11Texture2D** depthStencilBuffer,
ID3D11DepthStencilView** depthStencilView);
// 消息循環(huán)
int EnterMsgLoop(bool(*ptr_display)(float timeDelta));
// 回調(diào)函數(shù)
LRESULT CALLBACK WndProc(
HWND,
UINT msg,
WPARAM,
LPARAM lParam
);
}
#endif // !__d3dUtilityH__
4.2 創(chuàng)建 d3dUtility.cpp 文件
將下面代碼復(fù)制進(jìn)去
//這是我們自己創(chuàng)建的"d3dUtility.h"頭文件
#include "d3dUtility.h"
//D3D初始化
//這個(gè)函數(shù)中包括兩個(gè)部分:第一部分:創(chuàng)建一個(gè)窗口蝗柔;第二部分:初始化D3D
//函數(shù)參數(shù)包括:
//1. HINSTANCE hInstance 當(dāng)前應(yīng)用程序?qū)嵗木浔?//2. int width 窗口寬
//3. int height 窗口高
//4. ID3D11RenderTargetView** renderTargetView 目標(biāo)渲染視圖指針
//5. ID3D11DeviceContext** immediateContext 設(shè)備上下文指針闻葵,設(shè)備上下文包含設(shè)備的使用環(huán)境和設(shè)置
//6. IDXGISwapChain** swapChain 交換鏈指針,用于描述交換鏈的特性
//7. ID3D11Device** device 設(shè)備用指針癣丧,每個(gè)D3D程序至少有一個(gè)設(shè)備
//8. ID3D11Texture2D** depthStencilBuffer; //深度/模板緩沖區(qū)
//9. ID3D11DepthStencilView** depthStencilView; //深度/模板視圖
bool d3d::InitD3D(
HINSTANCE hInstance,
int width,
int height,
ID3D11RenderTargetView** renderTargetView,
ID3D11DeviceContext** immediateContext,
IDXGISwapChain** swapChain,
ID3D11Device** device,
ID3D11Texture2D** depthStencilBuffer,
ID3D11DepthStencilView** depthStencilView)
{
//***********第一部分:創(chuàng)建一個(gè)窗口開始***************
//這部分的代碼和實(shí)驗(yàn)一中的創(chuàng)建窗口代碼基本一致槽畔,具體參數(shù)的注釋可以參考實(shí)驗(yàn)一
//創(chuàng)建窗口的4個(gè)步驟:1 設(shè)計(jì)一個(gè)窗口類;2 注冊(cè)窗口類胁编;3 創(chuàng)建窗口厢钧;4 窗口顯示和更新
//1 設(shè)計(jì)一個(gè)窗口類
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"Direct3D11App";
//2 注冊(cè)窗口類
if (!RegisterClass(&wc))
{
::MessageBox(0, L"RegisterClass() - FAILED", 0, 0);
return false;
}
//3 創(chuàng)建窗口
HWND hwnd = 0;
hwnd = ::CreateWindow(L"Direct3D11App",
L"D3D11",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
0,
0,
hInstance,
0);
if (!hwnd)
{
::MessageBox(0, L"CreateWindow() - FAILED", 0, 0);
return false;
}
//4 窗口顯示和更新
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
//***********第一部分:創(chuàng)建一個(gè)窗口結(jié)束***************
//***********第二部分:初始化D3D開始***************
//初始化D3D設(shè)備主要為以下步驟
//1. 描述交換鏈,即填充DXGI_SWAP_CHAIN_DESC結(jié)構(gòu)
//2. 使用D3D11CreateDeviceAndSwapChain創(chuàng)建D3D設(shè)備(ID3D11Device)
// 設(shè)備上下文接口(ID3D11DeviceContext)嬉橙,交換鏈接口(IDXGISwapChain)
//3. 創(chuàng)建目標(biāo)渲染視圖(ID3D11RenderTargetView)
//4. 設(shè)置視口(View Port)
//第一步早直,描述交換鏈,即填充DXGI_SWAP_CHAIN_DESC結(jié)構(gòu)
DXGI_SWAP_CHAIN_DESC sd; //首先聲明一個(gè)DXGI_SWAP_CHAIN_DESC的對(duì)象sd
ZeroMemory(&sd, sizeof(sd)); //用ZeroMemory對(duì)sd進(jìn)行初始化市框,ZeroMemory的用法見實(shí)驗(yàn)一的補(bǔ)充知識(shí)
sd.BufferCount = 1; //交換鏈中后臺(tái)緩存數(shù)量霞扬,通常為1
sd.BufferDesc.Width = width; //緩存區(qū)中的窗口寬
sd.BufferDesc.Height = height; //緩存區(qū)中的窗口高
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //指定32位像素格式,表示紅綠藍(lán)Alpha各8位,其他格式見書P50
sd.BufferDesc.RefreshRate.Numerator = 60; //刷新頻率的分子為60
sd.BufferDesc.RefreshRate.Denominator = 1; //刷新頻率的分母為1祥得,即刷新頻率為每秒6次
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //用來描述后臺(tái)緩存的用法控制CPU對(duì)后臺(tái)緩存的訪問
sd.OutputWindow = hwnd; //指向渲染目標(biāo)窗口的句柄
sd.SampleDesc.Count = 1; //多重采樣的屬性臀蛛,本例中不采用多重采樣即,
sd.SampleDesc.Quality = 0; //所以Count=1宁舰,Quality=0
sd.Windowed = TRUE; //TRUE為窗口模式法牲,F(xiàn)ALSE為全屏模式
//sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
//sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
//第二步,創(chuàng)建設(shè)備饮焦,交換鏈以及立即執(zhí)行上下文
//創(chuàng)建一個(gè)數(shù)組確定嘗試創(chuàng)建Featurelevel的順序
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0, //D3D11 所支持的特征怕吴,包括shader model 5
D3D_FEATURE_LEVEL_10_1, //D3D10 所支持的特征,包括shader model 4.
D3D_FEATURE_LEVEL_10_0,
};
//獲取D3D_FEATURE_LEVEL數(shù)組的元素個(gè)數(shù)
UINT numFeatureLevels = ARRAYSIZE(featureLevels);
//調(diào)用D3D11CreateDeviceAndSwapChain創(chuàng)建交換鏈县踢,設(shè)備转绷,和設(shè)備上下文
//分別存入swapChain,device硼啤,immediateContext
if (FAILED(D3D11CreateDeviceAndSwapChain(
NULL, //確定顯示適配器议经,NULL表示默認(rèn)顯示適配器
D3D_DRIVER_TYPE_HARDWARE, //選擇驅(qū)動(dòng)類型,這里表示使用三維硬件加速
NULL, //只有上一個(gè)參數(shù)設(shè)置D3D_DRIVER_TYPE_SOFTWARE時(shí)谴返,才使用這個(gè)參數(shù)
0, //也可以設(shè)置為D3D11_CREATE_DEVICE_DEBUG開啟調(diào)試模式
featureLevels, //前面定義的D3D_FEATURE_LEVEL數(shù)組
numFeatureLevels, //D3D_FEATURE_LEVEL的元素個(gè)數(shù)
D3D11_SDK_VERSION, //SDK的版本煞肾,這里為D3D11
&sd, //前面定義的DXGI_SWAP_CHAIN_DESC對(duì)象
swapChain, //返回創(chuàng)建好的交換鏈指針,InitD3D函數(shù)傳遞的實(shí)參
device, //返回創(chuàng)建好的設(shè)備用指針嗓袱,InitD3D函數(shù)傳遞的實(shí)參
NULL, //返回當(dāng)前設(shè)備支持的featureLevels數(shù)組中的第一個(gè)對(duì)象籍救,一般設(shè)置為NULL
immediateContext))) //返回創(chuàng)建好的設(shè)備上下文指針,InitD3D函數(shù)傳遞的實(shí)參
{
::MessageBox(0, L"CreateDevice - FAILED", 0, 0); //如果創(chuàng)建失敗渠抹,彈出消息框
return false;
}
//第三步蝙昙,創(chuàng)建并設(shè)置渲染目標(biāo)視圖
HRESULT hr = 0; //COM要求所有的方法都會(huì)返回一個(gè)HRESULT類型的錯(cuò)誤號(hào)
ID3D11Texture2D* pBackBuffer = NULL; //ID3D11Texture2D類型的,后臺(tái)緩存指針
//調(diào)用GetBuffer()函數(shù)得到后臺(tái)緩存對(duì)象梧却,并存入&pBackBuffer中
hr = (*swapChain)->GetBuffer(0, //緩存索引奇颠,一般設(shè)置為0
__uuidof(ID3D11Texture2D), //緩存類型
(LPVOID*)&pBackBuffer); //緩存指針
//判斷GetBuffer是否調(diào)用成功
if (FAILED(hr))
{
::MessageBox(0, L"GetBuffer - FAILED", 0, 0); //如果調(diào)用失敗,彈出消息框
return false;
}
//調(diào)用CreateRenderTargetView創(chuàng)建好渲染目標(biāo)視圖篮幢,創(chuàng)建后存入renderTargetView中
hr = (*device)->CreateRenderTargetView(pBackBuffer, //上面創(chuàng)建好的后臺(tái)緩存
NULL, //設(shè)置為NULL得到默認(rèn)的渲染目標(biāo)視圖
renderTargetView); //返回創(chuàng)建好的渲染目標(biāo)視圖大刊,InitD3D函數(shù)傳遞的實(shí)參
pBackBuffer->Release(); //釋放后臺(tái)緩存
//判斷CreateRenderTargetView是否調(diào)用成功
if (FAILED(hr))
{
::MessageBox(0, L"CreateRender - FAILED", 0, 0); //如果調(diào)用失敗,彈出消息框
return false;
}
//************************增加的步驟************************
D3D11_TEXTURE2D_DESC dsDesc;
dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; //這里表示24位用于深度緩存三椿,8位用于模板緩存
dsDesc.Width = 800; //深度模板緩存的寬度
dsDesc.Height = 600; //深度模板緩存的高度
dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; //綁定標(biāo)識(shí)符
dsDesc.MipLevels = 1;
dsDesc.ArraySize = 1;
dsDesc.CPUAccessFlags = 0; //CPU訪問標(biāo)識(shí)符缺菌,0為默認(rèn)值
dsDesc.SampleDesc.Count = 1; //多重采樣的屬性,本例中不采用多重采樣即搜锰,
dsDesc.SampleDesc.Quality = 0; //所以Count=1伴郁,Quality=0
dsDesc.MiscFlags = 0;
dsDesc.Usage = D3D11_USAGE_DEFAULT;
//創(chuàng)建深度模板緩存
hr = (*device)->CreateTexture2D(&dsDesc, 0, depthStencilBuffer);
if (FAILED(hr))
{
MessageBox(NULL, L"Create depth stencil buffer failed!", L"ERROR", MB_OK);
return false;
}
//創(chuàng)建深度模板緩存視圖
hr = (*device)->CreateDepthStencilView(*depthStencilBuffer, 0, depthStencilView);
if (FAILED(hr))
{
MessageBox(NULL, L"Create depth stencil view failed!", L"ERROR", MB_OK);
return false;
}
//將渲染目標(biāo)視圖和深度模板緩存視圖綁定到渲染管線
(*immediateContext)->OMSetRenderTargets(1, //綁定的目標(biāo)視圖的個(gè)數(shù)
renderTargetView, //渲染目標(biāo)視圖,InitD3D函數(shù)傳遞的實(shí)參
*depthStencilView); //綁定模板
//************************增加的步驟************************
/*
//將渲染目標(biāo)視圖綁定到渲染管線
(*immediateContext)->OMSetRenderTargets(1, //綁定的目標(biāo)視圖的個(gè)數(shù)
renderTargetView, //渲染目標(biāo)視圖蛋叼,InitD3D函數(shù)傳遞的實(shí)參
NULL ); //設(shè)置為NULL表示不綁定深度模板
*/
//第四步焊傅,設(shè)置視口大小剂陡,D3D11默認(rèn)不會(huì)設(shè)置視口,此步驟必須手動(dòng)設(shè)置
D3D11_VIEWPORT vp; //創(chuàng)建一個(gè)視口的對(duì)象
vp.Width = width; //視口的寬
vp.Height = height; //視口的高
vp.MinDepth = 0.0f; //深度值的下限狐胎,**由于深度值是[0, 1]所以下限值是0
vp.MaxDepth = 1.0f; //深度值的上限鸭栖,上限值是1
vp.TopLeftX = 0; //視口左上角的橫坐標(biāo)
vp.TopLeftY = 0; //視口左上角的總坐標(biāo)
//設(shè)置視口
(*immediateContext)->RSSetViewports(1, //視口的個(gè)數(shù)
&vp); //上面創(chuàng)建的視口對(duì)象
return true;
//***********第二部分:初始化D3D結(jié)束***************
}
//消息循環(huán),和之前"Hello World"程序中Run()起到同樣的功能
//bool (*ptr_display)(float timeDelta)表示傳遞一個(gè)函數(shù)指針作為參數(shù)
//這個(gè)函數(shù)有一個(gè)float類型的參數(shù)握巢,有一個(gè)bool類型的返回
int d3d::EnterMsgLoop(bool(*ptr_display)(float timeDelta))
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG)); //初始化內(nèi)存
static float lastTime = (float)timeGetTime(); //第一次獲取當(dāng)前時(shí)間
while (msg.message != WM_QUIT)
{
if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = (float)timeGetTime(); //第二次獲取當(dāng)前時(shí)間
float timeDelta = (currTime - lastTime)*0.001f; //獲取兩次時(shí)間之間的時(shí)間差
ptr_display(timeDelta); //調(diào)用顯示函數(shù)晕鹊,這在后面實(shí)現(xiàn)圖形的變化(如旋轉(zhuǎn))時(shí)會(huì)用到
lastTime = currTime;
}
}
return msg.wParam;
}
4.3 創(chuàng)建一個(gè) Main.cpp 文件
將下面代碼復(fù)制進(jìn)去:
#include "d3dUtility.h"
ID3D11Device* device = NULL; // D3D11設(shè)備指針
IDXGISwapChain* swapChain = NULL; // 交換鏈指針
ID3D11DeviceContext* immediateContext = NULL; // 執(zhí)行上下文
ID3D11RenderTargetView* renderTargetView = NULL;// 渲染目標(biāo)視圖指針
ID3D11DepthStencilView* depthStencilView = NULL;//深度模板視圖
ID3D11Texture2D* depthStencilBuffer = NULL; //深度緩存
bool Setup() {
return true;
}
void Cleanup() {
if (device) device->Release();
if (immediateContext) immediateContext->Release();
if (swapChain) swapChain->Release();
if (renderTargetView) renderTargetView->Release();
if (depthStencilView) depthStencilView->Release();
if (depthStencilBuffer) depthStencilBuffer->Release();
}
bool Display(float timeDelta) {
if (device) {
float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
immediateContext->ClearRenderTargetView(renderTargetView, ClearColor);
swapChain->Present(0, 0);
}
return true;
}
/*回調(diào)函數(shù)*/
LRESULT CALLBACK d3d::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);
}
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE prevInstance,
PSTR cmdLine,
int showCmd)
{
//初始化
//**注意**:最上面聲明的IDirect3DDevice9指針,在這里作為參數(shù)傳給InitD3D函數(shù)
if (!d3d::InitD3D(hinstance,
800,
600,
&renderTargetView,
&immediateContext,
&swapChain,
&device,
&depthStencilBuffer,
&depthStencilView))// [out]The created device.
{
::MessageBox(0, L"InitD3D() - FAILED", 0, 0);
return 0;
}
if (!Setup())
{
::MessageBox(0, L"Setup() - FAILED", 0, 0);
return 0;
}
d3d::EnterMsgLoop(Display);
Cleanup();
return 0;
}
5. 編譯運(yùn)行
編譯運(yùn)行的結(jié)果就是這樣的:
有人會(huì)說:“我靠這不還是黑框框嗎暴浦?這么多代碼溅话,說好的圖形呢?”但實(shí)時(shí)結(jié)果就是如此歌焦,幾百行代碼下來飞几,只是個(gè)黑框。萬(wàn)事開頭難独撇,這就是第一個(gè) DirectX 項(xiàng)目屑墨,代碼里包含了 DX 的基本結(jié)構(gòu)。而這些基本結(jié)構(gòu)券勺,就留到下一篇博客吧绪钥。