小白學習DirectX11:第一個demo

源代碼下載

 

簡單說說

由於工作需要處理視頻流,對處理速度要求比較高。已經完成的圖像處理的代碼(有空補上博客)處理每幀(3*1920*1080)串行需要160ms左右,不能滿足要求,所以在別人推薦下開始研究並行處理計算---DirectX。大致想法是想利用裏面的渲染和着色器等功能實現圖像處理的算法,由於是並行計算,所以速度肯定要快很多。

昨天開始大致看了很多博客文章還有幾本書,現在這兒推薦兩個博客

毛星雲的DirectX遊戲開發

X_Jun很詳細的教程

大牛的博客只是瀏覽了一下,因爲自己不太需要那麼多功能。但是覺得還是模模糊糊,沒有寫過代碼始終理解不上去。於是今天開始跟着一本書《Beginning DirectX11 Game Programming》的第二章,整合裏面的例子,加上一些自己的註釋。水平不高,註釋一個學習筆記吧。以前做完事情都沒有立即寫,今天做完決心今天就寫完這篇博客

 

DirectX配置

DX的配置問題比較簡單,有過常見庫配置經驗的都能夠很簡單完成。看了很多的文章,似乎較新版本的VS和系統已經集成了DX庫在Windows Kit目錄中,也不用另外安裝。此處不再贅述,有需要的可以再查一查。

 

主程序

#include<Windows.h>
#include<memory>
#include<dx11demo.h>

//系統消息處理函數
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT paintStruct;
	HDC hDC;

	switch (message)
	{
	case WM_PAINT:
		hDC = BeginPaint(hwnd, &paintStruct);
		EndPaint(hwnd, &paintStruct);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;

}


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
	LPWSTR cmdLine, int cmdShow)
{
	UNREFERENCED_PARAMETER(prevInstance);       //避免對於未使用變量的系統警告
	UNREFERENCED_PARAMETER(cmdLine);

	WNDCLASSEX wndClass = { 0 };               //窗口的類WNDCLASSEX,並初始化
	wndClass.cbSize = sizeof(WNDCLASSEX);      //窗口大小
	wndClass.style = CS_HREDRAW | CS_VREDRAW;   //窗口風格
	wndClass.lpfnWndProc = WndProc;           //回調函數,處理系統發來的時間消息
	wndClass.hInstance = hInstance;             
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = "DX11BookWindowClass";      //窗口的名字

	if (!RegisterClassEx(&wndClass))				//註冊窗口,創造窗口前必須註冊
		return -1;

	RECT rc = { 0,0,640,480 };
	AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);

	HWND hwnd = CreateWindowA("DX11BookWindowClass", "Blank Win32 Window",
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left,
		rc.bottom - rc.top, NULL, NULL, hInstance, NULL);       //創造窗口

	if (!hwnd)
		return -1;

	ShowWindow(hwnd, cmdShow);        //顯示窗口

	std::auto_ptr<Dx11DemoBase> demo(new Dx11DemoBase());
	
	// 初始化
	bool result = demo->Initialize(hInstance, hwnd);
	
	if (!result)
		return -1;

	//Demo 初始化
	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			//更新 畫圖
			demo->Update(0.0f);
			demo->Render();
		}
	}
	//Demo 關閉

	demo->Shutdown();
	return static_cast<int>(msg.wParam);
}

主程序就是一個基本的win32下面的窗口函數。簡單來說就是先聲明一個新窗口,對窗口這個類繼續賦值,比如大小,名字,回調函數等等,然後註冊窗口,再創造該窗口。這些代碼都很常見,也是模塊化的,不容易出錯。除去以上代碼段中有關DX的幾個語句,再編譯運行,顯示出來的就是一個空白窗口。

 

DX的初始化

頭文件包含的主要是DX主要頭文件,在自己編譯運行過程中,由於自己之前設置的庫的問題,會報錯,所以自己就把鏈接庫也加上了。DX功能對應的庫自己還不太熟悉,這個坑以後再來填。


#ifndef _DEMO_BASE_H_
#define _DEMO_BASE_H_
#include<d3d11.h>
#include<d3dx11.h>
#include<DxErr.h>

#pragma comment(lib, "DXGI.lib")
#pragma comment(lib, "D3D11.lib")
#pragma comment(lib, "D3DX11.lib")
#pragma comment(lib, "D3DX10.lib")
#pragma comment(lib, "dxerr.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")

class Dx11DemoBase
{
public:
	Dx11DemoBase();
	virtual ~Dx11DemoBase();
	bool Initialize(HINSTANCE hInstance, HWND hwnd);  //初始化
	void Shutdown();    //關閉釋放
    bool LoadContent();   //加載
	void UnloadContent();  //卸載
	void Update(float dt);  //更新
	void Render();   //渲染
protected:
	HINSTANCE hInstance_;    //窗口實例
	HWND hwnd_;              //窗口句柄
	D3D_DRIVER_TYPE driverType_;    //設備類型
	D3D_FEATURE_LEVEL featureLevel_;  //特徵等級
	ID3D11Device* d3dDevice_;         //D3D設備實際對象
	ID3D11DeviceContext* d3dContext_; //上下文,理解爲接口更好
	IDXGISwapChain* swapChain_;       //交換鏈
	ID3D11RenderTargetView* backBufferTarget_;    //後緩衝區目標視圖
};
#endif

頭文件就不說了,主要是定義了一些基本的函數,和一些DX的變量,比如設備號之類和feature level,看完初始化,再去看這些東西印象會更深刻一些。下面開始來說一下DX初始化的流程和代碼段。

bool Dx11DemoBase::Initialize(HINSTANCE hInstance, HWND hwnd)
hInstance_ = hInstance;        //獲得當前實例
hwnd_ = hwnd;                  //獲取當前窗口句柄

RECT dimensions;                
GetClientRect(hwnd, &dimensions);    //獲取當前client area大小(窗口大小)

初始化函數傳入就是當前窗口的實例和句柄,因爲顯示在原來定義的窗口上顯示。GetClientRect函數可以獲得窗口的大小,RECT結構表示的是窗口的四個點座標。

設置設備類型和特徵等級

    //設置設備類型和特徵等級(feature level)
        D3D_DRIVER_TYPE driverTypes[] =                     
     //設備類型(以下包括硬件,軟件,WARP,參考四種)
    {
	D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,
	D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE
    };

	unsigned int totalDriverTypes = ARRAYSIZE(driverTypes);     //ARRAYSIZE返回數組大小

	D3D_FEATURE_LEVEL featureLevels[] =      //feature level不同的硬件支持不同
		{
	  D3D_FEATURE_LEVEL_11_0,
	  D3D_FEATURE_LEVEL_10_1,
	  D3D_FEATURE_LEVEL_10_0
		};

	unsigned int totalFeatureLevels = ARRAYSIZE(featureLevels);   //ARRAYSIZE返回數組大小

D3D_DRIVER_TYPE和D3D_FEATURE_LEVEL分別就是設備類型和設備特徵等級。設備包括軟件、硬件、參考和WARP四種設備,特徵等級包括這三種。這裏用數組存儲了,是因爲後面讀取不同的電腦硬件時,可能會遇到支持的設備類型和等級不同,需要循環設置。只要有一個設備類型和等級,就可以成功創建設備和後續的交換鏈。

設備和交換鏈創建

這個代碼段主要是完成了交換鏈這個東西的一些參數的賦值,交換鏈是一個新概念,但是理解上來還是比較簡單的。

     //設備及交換鏈創建
DXGI_SWAP_CHAIN_DESC swapChainDesc;      //變量類型爲  交換鍊形容子(swap chain description)
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));   //用零填充該變量的所有區域
swapChainDesc.BufferCount = 1;               //緩衝區數(此處猜測爲1,則有兩個緩衝區,2有個)
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;            //緩衝區存儲的大小(和要顯示的一致)
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;   //緩衝區存儲圖像的格式()
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;        //刷新率,60/1表示60Hz
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;   //緩衝區使用
swapChainDesc.OutputWindow = hwnd;           //窗口,就是win32要做顯示的窗口句柄
swapChainDesc.Windowed = true;               //決定保存窗口或者到全屏的變量
swapChainDesc.SampleDesc.Count = 1;          
swapChainDesc.SampleDesc.Quality = 0;        //sample description

交換鏈:交換始於前緩衝區和後緩衝區的交換。在顯示器的顯示過程中,一般還會有渲染處理的過程。前緩衝區中存儲的就是現在正在顯示的圖像,後緩衝區是處理結束等待顯示的下一張圖像。當顯示下一張圖像時,前後緩衝區就會交換,前變成後,後變成前。在DX裏面,形容交換鏈的容器就是DXGI_SWAP_CHAIN_DESC。值得注意的是,後緩衝區可以是兩個也可以是一個。

上面代碼裏註釋都比較詳細,也沒有太多解釋和說明的,還有問題可以參考文章開頭給出的那本書。

下面代碼就是創建設備和交換鏈了,for循環檢索之前給的數組,有一個存在,就可以創建成功。當然,如果熟悉自己的硬件支持的設備和等級,也可以直接使用那個函數創建。

for (driver = 0; driver < totalDriverTypes; ++driver)  
//循環表示對所有類型設備都嘗試創建,有一個設備存在和特徵level存在,創建成功
{
//D3D11CreateDeviceAndSwapChain()創建交換鏈,設備,渲染context
      result = D3D11CreateDeviceAndSwapChain(0, driverTypes[driver], 0, creationFlags,
			   featureLevels, totalFeatureLevels,
				D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
				&d3dDevice_, &featureLevel_, &d3dContext_);

			if (SUCCEEDED(result))
			{
				driverType_ = driverTypes[driver];    
				break;
			}
		}

if (FAILED(result))
{
	DXTRACE_MSG("創建 Direct3D 設備失敗!");
	return false;
	}
  //渲染目標視圖創建Render Target View Creation
		ID3D11Texture2D* backBufferTexture;

		result = swapChain_->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferTexture);

		if (FAILED(result))
		{
			DXTRACE_MSG("獲取交換鏈後臺緩存失敗!");
			return false;
		}

		//創建渲染目標視圖
		result = d3dDevice_->CreateRenderTargetView(backBufferTexture, 0, &backBufferTarget_);

		if (backBufferTexture)
			backBufferTexture->Release(); //釋放緩衝區指針,防止內存泄漏

		if (FAILED(result))
		{
			DXTRACE_MSG("創建渲染目標視圖失敗!");
			return false;
		}

		//渲染到一個固定視圖
		d3dContext_->OMSetRenderTargets(1, &backBufferTarget_, 0);

		//視窗創建(定義了要顯示的視窗範圍)
		D3D11_VIEWPORT viewport;
		viewport.Width = static_cast<float>(width);   
		viewport.Height = static_cast<float>(height);     //高度和寬度
		viewport.MinDepth = 0.0f;            //深度範圍
		viewport.MaxDepth = 1.0f;
		viewport.TopLeftX = 0.0f;            //視窗的頂部左座標
		viewport.TopLeftY = 0.0f;

		d3dContext_->RSSetViewports(1, &viewport);      //設置到視窗範圍

初始化剩下的部分就是比較簡單:獲取內存,創建目標視圖數據,顯示等等。。這兒涉及到幾個device,context,發現一篇文章簡單的說明了一下,可以參考。

代碼主要側重點還是在設備的初始化部分,所以更深奧的部分還沒有涉及到。整理的渲染函數裏,就只是簡單的讓窗口顯示一個給定的顏色,由clearColor給定。另外一定是,顯示設備一般實際上有四個通道,分別是RGB和alpha,最後一個是透明度。

在render函數中,修改clearColor就可以得到不同的窗口顯示顏色。


	void Dx11DemoBase::Render()
	{
		if (d3dContext_ == 0)
			return;
		float clearColor[4] = { 0.5f, 0.5f, 0.25f, 1.0f };
		d3dContext_->ClearRenderTargetView(backBufferTarget_, clearColor);    //清除屏幕,顯示一個特別的顏色
		swapChain_->Present(0, 0);        //顯示後緩衝區的渲染場景,也就是新的
	}

在render函數中,修改clearColor就可以得到不同的窗口顯示顏色,如下圖所示。

 

總結

本文簡單介紹了一些D3D初始化的代碼,來源於《Beginning DirectX11 Game Programming》的第二章,簡單給代碼加上了註釋,並寫了一些自己的理解。初始化部分: 獲取當前設備的類型和特徵等級,設置交換鏈的參數,創建設備,設置後緩衝區內存等等。每一步都需要D3D11庫裏面的函數實現,主要的函數已經包括在代碼中,這部分函數知道固定的輸入參數即可。我覺得我將面臨的難點還是在後面移植算法過程中,如何用D3D11的功能實現算法。明天繼續學習這部分內容,希望自己有空就把自己做過的寫成博客,這樣就可以越寫越好了。

在寫博客的時候有些內容沒有想到,文章也一般,以後若是想到問題再回來修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章