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消息處理函數

接下來我們將了解一下如何處理一些我們要親自處理的消息,實際上應用程序代碼都是在沒有窗口信息可以處理時執行的,但是有一些重要的信息需要我們親自去處理

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

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

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

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

5、WM_DESTROY:當窗口被銷毀時發送WM_DESTROY消息

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

7、WM_LBUTTONUP/WM_MBUTTONUP/WN_RBUTTONUP:松開鼠標左鍵/中鍵/右鍵時


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM