DirectX12 3D 遊戲開發與實戰第四章內容(下)

#Direct3D的初始化(下) ##學習目標編程

  1. 瞭解Direct3D在3D編程中相對於硬件所扮演的角色
  2. 理解組件對象模型COM在Direct3D中的做用
  3. 掌握基礎的圖像學概念,例如2D圖像的存儲方式,頁面翻轉,深度緩衝,多重採樣以及CPU和GPU之間的交互
  4. 學習使用性能計數器函數,依次讀取高精度計時器的數值
  5. 瞭解Direct3D的初始化過程
  6. 熟悉本書應用程序框架的總體結構,在後續的演示程序中能夠常常看到應用程序框架的總體結構

##4.3初始化Direct3D 對Direct3D進行初始化能夠分爲如下幾個步驟數組

  1. 用D3D12CreateDevice函數建立ID3D12Device接口實例
  2. 建立一個ID3D12Fence對象,並查詢描述符的大小
  3. 檢測用戶設備對4X MSAA質量級別的支持狀況
  4. 依次建立命令隊列,命令列表分配器,主命令列表
  5. 描述並建立交換鏈
  6. 建立應用程序須要的描述符堆
  7. 調整後臺緩衝區的大小,併爲它建立渲染目標視圖
  8. 建立深度/模板緩衝區以及與之關聯的深度/模板緩衝區視圖
  9. 設置視口(viewport)和裁剪矩形(scissor rectangle)

##4.3.1建立設備 要初始化Direct3D,必須先建立Direct3D12設備。Direct3D12設備至關於一個顯示適配器,顯示適配器通常都是一種3D圖像硬件(如顯卡),但也能夠用軟件顯示適配器來模擬3D圖形硬件功能,該設備能夠檢測系統環境對功能的支持狀況,又能夠用來建立全部其餘的Direct3D接口對象(如資源,命令列表,視圖(描述符)等等)。咱們能夠經過一下函數建立Direct3D12設備:緩存

//@param:指定在建立設備的時候所用的顯示適配器,若是把該指針設爲空,則默認使用主顯示適配器
//@param:應用程序須要硬件所支持的最低功能級別
//@parma:該ID3DDevice接口的COM ID
//@parma:返回所建立的Direct3D12設備
HRESULT WINAPI mD3D12CreateDevice(
	IUnknown* pAdapter,
	D3D_FEATURE_LEVEL MinimumFeatureLevel,
	REFIID riid,
	void** ppDevice
);

##4.3.2建立圍欄並獲取描述符的大小 建立好設備以後,咱們即可覺得CPU和GPU的同步建立圍欄了。另外若是須要使用描述符進行工做,咱們還須要瞭解它們的大小。但描述符在不一樣的GPU上大小是不一樣的,因此須要咱們在建立圍欄的時候順便去查詢相關的信息,而後將描述符的大小緩存起來,以便在須要的時候直接進行引用。框架

ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
//渲染目標視圖(描述符)大小
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
//深度/模板視圖(描述符)大小
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
//常量緩衝區/着色器資源/無序訪問視圖(描述符)大小
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

##4.3.3檢測對4X MSAA質量級別的支持 凡是支持Direct3D11的硬件,均可以支持多重採樣技術的開啓。因此咱們能夠不用對此進行檢測,可是,對質量級別的檢測仍是必不可少的。咱們能夠採用下面的方法進行檢測:函數

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels,
	sizeof(msQualityLevels)));

##4.3.4建立命令隊列和命令列表 在前面的章節可知,ID3D12CommandQueue接口表示命令隊列,ID3D12CommandAllocator接口表示命令分配器,ID3D12CommandList接口表示命令列表,下面咱們將分別展現這幾種對象的建立流程:佈局

ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12CommandList> mCommandList;

void D3DApp::CreateCommandObjects()
{
	//建立命令隊列對象
	D3D12_COMMAND_QUEUE_DESC queueDesc = {};
	queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
	queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
	//建立命令分配器
	ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, 
		IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
	//建立命令列表
	ThrowIfFailed(md3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mDirectCmdListAlloc.Get(),
		nullptr, IID_PPV_ARGS(mCommandList.GetAddressOf())));

	//首先要把命令列表關閉,由於第一次使用命令列表時咱們要把命令列表重置,重置以前必須確保命令列表
	//已經關閉
	mCommandList->Close();
	}

##4.3.5描述並建立交換鏈 在建立交換鏈以前,咱們要先填寫一份DXGI_SWAP_CHAIN_DESC結構體實例,用它來描述即將建立的交換鏈的特性。此結構體定義以下:性能

typedef struct DXGI_SWAP_CHAIN_DESC {
	DXGI_MODE_DESC BufferDesc;
	DXGI_SAMPLE_DESC SampleDesc;
	DXGI_USAGE BufferUsage;
	UINT BufferCount;
	HWND OutputWindow;
	BOOL Windowed;
	DXGI_SWAP_EFFECT SwapEffect;
	UINT Flags;
}DXGI_SWAP_CHAIN_DESC;

其中DXGI_MODE_DESC則是另外一個結構體,該結構體定義以下:學習

typedef struct DXGI_MODE_DESC {
	UINT Width;
	UINT Height;
	DXGI_RATIONAL refreshRate;
	DXGI_FORMAT Format;
	DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
	DXGI_MODE_SCALING scaling;
}DXGI_MODE_DESC;

下面的代碼將會展現如何在本書的演示框架下方便的建立交換鏈:優化

void D3DApp::CreateSwapChain()
{

	//釋放以前建立的交換鏈,而後進行重建
	mSwapChain.Reset();

	DXGI_SWAP_CHAIN_DESC sd;
	sd.BufferDesc.Width = mClientWidth;
	sd.BufferDesc.Height = mClientHeight;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferDesc.Format = mBackBufferFormat;
	sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.BufferCount = SwapChainBufferCount;
	sd.OutputWindow = mhMainWnd;
	sd.Windowed = true;
	sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
	sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

	//注意,交換鏈須要經過命令隊列才能刷新
	ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(),
		&sd, mSwapChain.GetAddressOf()));

}

##4.3.6建立描述符堆 在程序中,咱們須要建立描述符堆來存儲程序中須要用到的描述符(視圖),在Direct12中,ID3D12DescriptorHeap接口表示描述符堆,並用ID3D12Device::CreateDescriptorHeap方法來建立描述符堆,在下面的演示代碼中,咱們將建立兩個描述符堆,一個用來存儲SwapChainBufferCount個渲染目標視圖(Render Target View),還有一個用來存儲1個深度/模板視圖(Depth/Stencil View)。動畫

ComPtr<ID3D12DescriptorHeap> mRtvHeap;
ComPtr<ID3D12DescriptorHeap> mDsvHeap;

void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
	rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
	rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	dsvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

##4.3.7建立渲染目標視圖 因爲資源不能直接和渲染流水線直接進行綁定,因此咱們須要先爲資源建立視圖(描述符),並將其綁定到渲染流水線中。爲了向後臺緩衝區建立一個渲染目標視圖,咱們須要先得到交換鏈中的緩衝區資源。

咱們能夠經過IDXGISwapChain::GetBuffer()方法後去交換鏈中的緩衝區資源,每次調用該方法以後,會增長相關後臺緩衝區的引用次數,因此在每一次使用後都要釋放,咱們能夠經過Comptr自動實現這個功能。

接下來,咱們可使用ID3D12Device::CreateRenderTargetView()方法來爲獲取的後臺緩衝區資源建立渲染目標視圖。

如下實例將會經過調用這兩個方法爲交換鏈中的每個緩衝區都建立一個RTV:

ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(
	mRtvHeap->GetCPUDescriptorHandleForHeapStart()
);
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
	//獲取交換鏈中第i個緩衝區
	ThrowIfFailed(mSwapChain->GetBuffer(i,
		IID_PPV_ARGS(&mSwapChainBuffer[i])));

	//爲此緩衝區建立一個RTV
	md3dDevice->CreateDepthStencilView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);

	//偏移到描述符的下一個緩衝區
	rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}

##4.3.8建立深度/模板緩衝區及其視圖(描述符) 深度緩衝區是一種紋理資源,它存儲着離觀察者最近的可視對象的深度信息(若是使用了模板,還有附有模板信息)。由於紋理是一種資源,因此咱們須要經過填寫D3D12_RESOURCE_DESC結構體來描述紋理資源,在使用ID3D12DeveiceCreateCommittedResource方法來建立它。如下代碼爲D3D12_RESOURCE_DESC結構體的定義:

typedef struct D3D12_RESOURCE_DESC
{
	D3D12_RESOURCE_DIMENSION Dimension;
	UINT64 Alignment;
	UINT64 Width;
	UINT Height;
	UINT16 DepthOrArraySize;
	DXGI_FORMAT Format;
	DXGI_SAMPLE_DESC SampleDesc;
	D3D12_TEXTURE_LAYOUT Layout;
	D3D12_RESOURCE_FLAGS Flags;
}D3D12_RESOURCE_DESC;

GPU資源都存於堆(Heap)中,其本質是具備特定屬性的GPU顯存塊,ID3D12Device::CreateCommittedResource將根據咱們所提供的屬性,建立一個資源和一個堆,並把該資源提交到這個堆中。

HRESULT ID3D12Device::CreateCommittedResource(
		const D3D12_HEAP_PROPERTIES * pHeapProperties,
		D3D12_HEAP_FLAGS HeapFlags,
		const D3D12_RESOURCE_DESC * pDesc,
		D3D12_RESOURCE_STATES InitialResourceState,
		const D3D12_CLEAR_VALUE * pOptimizedClearValue,
		REFIID riidResource,
		void ** ppvResource
		);

	typedef	struct D3D12_HEAP_PROPERTIES {
		D3D12_HEAP_TYPE Type;
		D3D12_CPU_PAGE_PROPERTY CPUPageProperty;
		D3D12_MEMORY_POOL MemoryPoolPreference;
		UINT CreationNodeMask;
		UINT VisibleNodeMask;
	}D3D12_HEAP_PROPERTIES;

在使用深度/模板緩衝區以前,必定要先建立相關的深度/模板緩衝區視圖(描述符),並將它綁定到渲染流水線中。下面的代碼將會展現如何建立深度/模板紋理資源以及相對應的深度/模板緩衝區視圖(描述符)

//建立深度/模板緩衝區視圖
D3D12_RESOURCE_DESC depthStencilDesc;
//資源的維度
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
//以紋素爲單位來表示紋理寬度(若是是緩衝區資源,此項表示緩衝區佔用的字節數)
depthStencilDesc.Width = mClientWidth;
//以紋素爲單位來表示紋理高度
depthStencilDesc.Height = mClientHeight;
//以紋素爲單位來表示紋理深度
depthStencilDesc.DepthOrArraySize = 1;
//mipmap層級的數量(後續講紋理時會介紹mipmap)
depthStencilDesc.MipLevels = 1;
//DXGI_FORMAT枚舉類型中的成員之一,用於指定紋素的格式
depthStencilDesc.Format = mDepthStencilFormat;
//多重採樣的質量級別以及對每個像素的採樣次數
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
//D3D12_TEXTURE_LAYOUT枚舉類型的成員之一,用來指定紋理的佈局
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
//與資源有關的雜項標誌
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

//建立一個指向一個D3D12_CLEAR_VALUE對象的指針,該指針描述了一個用於清除資源的優化值,
//選擇適當的優化值能夠提升清除操做的效率,若是不但願指定優化值,也能夠不建立。
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;

//建立深度/模板緩衝區
ThrowIfFailed(md3dDevice->CreateCommittedResource(
	&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
	D3D12_HEAP_FLAG_NONE,
	&depthStencilDesc,
	D3D12_RESOURCE_STATE_COMMON,
	&optClear,
	IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())
));

//利用此資源的格式,爲整個資源的第0層mip建立描述符
md3dDevice->CreateDepthStencilView(
	mDepthStencilBuffer.Get(),
	nullptr,
	DepthStencilView()
);

//將資源從初始狀態轉換到深度緩衝區
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
		D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));

##4.3.9設置視口 視口:咱們一般會把3D場景繪製到整個屏幕中,或整個窗口工做區大小至關的後臺緩衝區中,可是,有些時候咱們只但願把3D場景繪製到後臺緩衝區中的某一個矩形子區域中,而這個矩形子區域就稱爲視口

視口的結構體定義以下:

typedef struct D3D12_VIEWPORT {
	FLOAT TopLeftX;
	FLOAT TopLeftY;
	FLOAT Width;
	FLOAT Height;
	FLOAT MinDepth;
	FLOAT MaxDepth;
};

填寫好D3D12_VIEWPORT結構體以後,咱們即可以經過ID3D12GraphicsComandList::RSSetViewPort()方法來設置Direct3D中的視口了。下面將會展現經過建立和設置一個視口,把場景繪製到整個後臺緩衝區中

D3D12_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = static_cast<float>(mClientWidth);
vp.Height = static_cast<float>(, mClientHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;

//@param:綁定的視口數量
//@param:指向視口數組的指針
mCommandList->RSSetViewports(1, &vp);

4.3.10設置裁剪矩形 咱們能夠在相對於後臺緩衝區定義一個裁剪矩形,在這個矩形以外的像素都不會被光柵化到後臺緩衝區(被剔除),這個方法能夠優化程序的性能。好比咱們在遊戲界面放置了一個UI,咱們能夠經過設置裁剪矩形使程序沒必要對3D空間中那些被它遮擋的像素進行處理了。設置裁剪矩形和設置視口同樣,要先填寫一個D3D12_RECT結構體,該結構體由類型爲RECT的D3D12結構體定義而成:

typedef struct tagRECT {
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
}RECT;

在Direct3D中,要用ID3D12GraphicsCommandList::RSSetScisorRects方法來設置裁剪矩形,下面的實例展示瞭如何建立並設置一個覆蓋後臺緩衝區左上角四分之一區域的裁剪矩形

mScissorRect = { 0,0,mClientWidth / 2,mClientHeight / 2 };
mCommandList->RSSetScissorRects(1, &mScissorRect);

##4.4計時與動畫 爲了製做出精準的動畫效果就須要精確到計量時間,特別是要準確的度量出動畫每幀畫面之間的時間間隔,因此瞭解相關的知識是十分必要的

##4.4.1性能計時器 爲了精確的計量時間,咱們將採用性能計時器(performance timer),性能計時器的單位是計數,能夠經過QueryPerformanceCounter函數來獲取性能計時器測量的當前時刻值(以計數爲單位)

用QueryPerformanceFrequency函數獲取性能計時器的頻率(單位:計數/秒),經過單位能夠看出,若是但願把QueryPerformanceCounter函數獲取的時刻值的單位轉換爲秒,能夠經過**時刻值(計數)/性能計時器的頻率(計數/秒)**獲得

##4.4.2遊戲計時類

class GameTimer
{
public:
	GameTimer();

	float TotlaTime()const;	//以秒爲單位(總時間,不計暫停的時間)
	float DeltaTime()const;	//以秒爲單位(本幀與前一幀的時間差)

	void Reset();		//在開始消息循環以前調用
	void Start();		//解除計時器暫停時調用
	void Stop();		//暫停計時器時調用
	void Tick();		//每一幀都要調用

private:
	double mSecondPerCount;
	double mDeltaTime;

	__int64 mBastTime;			//應用程序開始運行的時間
	__int64 mPauseTime;			//全部暫停時間的總和
	__int64 mPauseTime;			//暫停的時刻
	__int64 mPrevTime;			//前一幀的時刻
	__int64 mCurrTime;			//當前的時刻

	bool mStopped;

};

下面是遊戲計時類的構造函數的代碼:

GameTimer::GameTimer() :mSecondsPerCount(0.0f), mPrevTime(0), mCurrTime(0), mStopped(false)
{
	__int64 countPersec;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countPersec);
	mSecondsPerCount = 1.0 / (double)countPersec;
}

剩下的一些方法咱們會在下面幾節介紹。

##4.4.3幀與幀之間的間隔 當渲染動畫幀時,咱們須要知道幀與幀之間的間隔,以此來根據時間的流逝對遊戲對象進行更新。因此咱們要計算出前一幀t1和後一幀t2的差值,即t2 - t1。計算差值的代碼以下:

void GameTimer::Tick()
{
	if (mStopped)
	{
		mDeltaTime = 0.0f;
		return;
	}

	//得到本幀開始顯示的時間
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)currTime);
	mCurrTime = currTime;

	//本幀與前一幀的時間差
	mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;

	//準備計算下一幀的時間差
	mPrevTime = mCurrTime;

	if (mCurrTime < 0)
	{
		mCurrTime = 0;
	}
}

float GameTimer::DeltaTime()const
{
	return (float)mDeltaTime;
}

Tick函數被調用於應用程序的消息循環之中

int D3DApp::Run()
{
	MSG msg = { 0 };
	
	mTimer.Reset();

	while (msg.message != WM_QUIT)
	{
		//若是有窗口信息就進行處理
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		//不然就執行遊戲邏輯
		else
		{
			mTimer.Tick();
			if (mAppPaused)
			{
				CalculateFrameStats();
				Update(mTimer);
				Draw(mTimer);
			}
			else
			{
				Sleep(100);
			}
		}
	}
}

下面是GameTimer::Reset()方法的實現:

void GameTimer::Reset()
{
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

	mBaseTime = currTime;
	mPrevTime = currTime;
	mPausedTime = 0;
	mStopped = false;
}

##總時間 總時間:總時間是一種自應用程序開始,不計中間暫停時間的時間總和。下面代碼將會分別展現遊戲計時類的Stop(),Start()和TotalTime()三個方法。

void GameTimer::Stop()
{
	//若是已經處於中止狀態,則直接退出函數
	if (mStopped)
	{
		return;
	}
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)currTime);
	
	mStopTime = currTime;
	mStopped = true;
}

void GameTimer::Start()
{
	__int64 startTime;
	QueryPerformanceCounter((LARGE_INTEGER*)startTime);

	//若是處於暫停狀態,則更新相關變量
	if (mStopped)
	{
		mPausedTime += (startTime - mStopTime);

		mPrevTime = startTime;

		mStopTime = 0;
		mStopped = false;

	}
}

float GameTimer::TotalTime()const
{
	//若是是暫停狀態
	if (mStopped)
	{
		return (float)((mStopTime - mPausedTime) - mBaseTime)*mSecondsPerCount;
	}
	else 
	{
		return (float)((mCurrTime - mPausedTime) - mBaseTime)*mSecondsPerCount;
	}
}

##4.5應用程序框架 略

##4.5.1D3DApp類 D3DApp類是一種基礎的Direct3D應用程序類,它提供了建立應用程序主窗口,運行程序消息循環,處理窗口信息以及初始化Direct3D等多種功能的函數,並且它爲應用程序例程定義了一組框架函數,咱們能夠根據需求經過實例化一個繼承自D3DApp的類,重寫框架裏的虛函數,即可以從D3DApp類中派生出自定義的用戶代碼。若是想要查看D3DApp類的定義,能夠在GitHub上自行查找。

##4.5.2非框架方法 略

##4.5.3框架內容 略

##4.5.4幀的統計信息 遊戲和圖形應用程序每每都會測量每秒渲染的幀數(frame per second,FPS)做爲一種畫面流暢度的標杆,所以,咱們須要統計在特定時間t內所處理的幀數n,則時間t內的平均幀數(FPS)爲n/t。在Direct3D中,提供了D3DApp::CalculateFrameStats方法來計算FPS的相關信息。D3DAPP::CalculateFrameStats()方法的定義以下

void D3DApp::CalculateFrameStats()
{
	static int frameCnt = 0;
	static float timeElapsed = 0;

	//以1秒爲統計週期來計算每一幀的平均幀數以及每一幀所花費的渲染時間
	if ((mTimer.TotalTime() - timeElapsed) >= 1.0f)
	{
		float fps = (float)frameCnt;
		//計算渲染一幀所花費的時間(以毫秒爲單位)
		float mspf = 1000.0f / fps;

		wstring fpsStr = to_wstring(fps);
		wstring mspfStr = to_wstring(mspf);

		wstring windowText = mMainWndCaption + L"fps:" + fpsStr + L"mspf:" + mspfStr;

		//爲計算下一幀重置相關值
		frameCnt = 0;
		timeElapsed += 1.0f;
	}
}

##4.5.5消息處理函數 接下來咱們將瞭解一下如何處理一些咱們要親自處理的消息,實際上應用程序代碼都是在沒有窗口信息能夠處理時執行的,可是有一些重要的信息須要咱們親自去處理

一、WM_ACTIVATE:當一個程序被激活或者進入非活動狀態時會發送此消息

二、WM_SIZE:當用戶調整窗口的大小的時候會發送此消息,主要目的是可讓咱們每次隨着用戶的調整而對後臺緩衝區和深度/模板緩衝區的大小和工做區矩形範圍的大小保持一致(這樣便不會出現圖形拉伸的bug了)

三、WM_ENTERSIZEMOVE:當用戶抓取調整欄時發送WM_ENTERSIZEMOVE消息。

四、WM_EXITSIZEMOVE:當用戶釋放調整欄時發送WM_EXITSIZEMOVE消息。

五、WM_DESTROY:當窗口被銷燬時發送WM_DESTROY消息

六、WM_LBUTTONDOWN/WM_MBUTTONDOWN/WM_RBUTTONDOWN:按下鼠標左鍵/中鍵/右鍵時

七、WM_LBUTTONUP/WM_MBUTTONUP/WN_RBUTTONUP:鬆開鼠標左鍵/中鍵/右鍵時

相關文章
相關標籤/搜索