跳轉到內容

DirectX/10.0/Direct3D/初始化 DirectX

來自 Wikibooks,開放世界中的開放書籍

本教程將是使用 DirectX 11 的第一個介紹。我們將討論如何初始化和關閉 Direct3D 以及如何渲染到視窗。

更新框架

[編輯 | 編輯原始碼]

我們將新增另一個類到框架中,該類將處理所有 Direct3D 系統函式。我們將這個類命名為 D3DClass。我已在下面更新了框架圖。

正如你所見,D3DClass 將位於 GraphicsClass 內部。上一教程中提到,所有新的圖形相關類都將封裝在 GraphicsClass 中,這就是為什麼它成為新 D3DClass 的最佳位置。現在讓我們看看對 GraphicsClass 做出的更改。

Graphicsclass.h

[編輯 | 編輯原始碼]
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

這是第一個更改。我們移除了對 windows.h 的包含,而是包含了新的 d3dclass.h。

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render();

private:

第二個更改是新的指向 D3DClass 的私有指標,我們將其命名為 m_D3D。如果你想知道,我在所有類變數前使用 m_。這樣在編碼時,我可以快速記住哪些變數是類的成員,哪些不是。

	D3DClass* m_D3D;
};

#endif

Graphicsclass.cpp

[編輯 | 編輯原始碼]

如果你還記得上一教程,這個類是完全空的,沒有任何程式碼。現在我們有了 D3DClass 成員,我們將開始在 GraphicsClass 內部填充一些程式碼來初始化和關閉 D3DClass 物件。我們還將在 Render 函式中新增對 BeginScene 和 EndScene 的呼叫,以便我們現在可以使用 Direct3D 繪製到視窗。

因此第一個更改是在類建構函式中。在這裡,我們出於安全原因將指標初始化為 null,就像我們對所有類指標所做的那樣。

GraphicsClass::GraphicsClass()
      :m_D3D(NULL)
{
}

第二個更改是在 GraphicsClass 內部的 Initialize 函式中。在這裡,我們建立了 D3DClass 物件,然後呼叫 D3DClass Initialize 函式。我們將螢幕寬度、螢幕高度、視窗控制代碼以及來自 Graphicsclass.h 檔案的四個全域性變數傳送到此函式。D3DClass 將使用所有這些變數來設定 Direct3D 系統。一旦我們檢視 d3dclass.cpp 檔案,我們將更詳細地討論這一點。

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
		return false;
	}

	return true;
}

下一個更改是在 GraphicsClass 內部的 Shutdown 函式中。所有圖形物件的關閉都發生在這裡,因此我們將 D3DClass 關閉放在此函式中。請注意,我會檢查指標是否已初始化。如果它沒有初始化,我們可以假設它從未被設定,並且不會嘗試關閉它。這就是在類建構函式中將所有指標設定為 null 的重要原因。如果它發現指標已初始化,那麼它將嘗試關閉 D3DClass,然後清除後面的指標。

void GraphicsClass::Shutdown()
{
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}

Frame 函式已更新,現在它在每一幀都呼叫 Render 函式。

bool GraphicsClass::Frame()
{
	// Render the graphics scene.
	if(!Render())
	{
		return false;
	}

	return true;
}

對這個類的最後一個更改是在 Render 函式中。我們呼叫 D3D 物件以將螢幕清除為灰色。之後,我們呼叫 EndScene,以便將灰色呈現到視窗。

bool GraphicsClass::Render()
{
	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);


	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}

現在讓我們看看新的 D3DClass 標頭檔案

D3dclass.h

[編輯 | 編輯原始碼]
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_

在標頭檔案中的第一件事是指定在使用此物件模組時要連結的庫。這些庫包含所有 Direct3D 功能,用於在 DirectX 中設定和繪製 3D 圖形,以及與計算機上的硬體互動以獲取有關顯示器重新整理率、正在使用的顯示卡等的工具。你會注意到,一些 DirectX 10 庫仍然在使用,這是因為這些庫從未升級到 DirectX 11,因為它們的功能不需要更改。

/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")

接下來,我們將包含與這些庫關聯的標頭檔案,我們正在連結到此物件模組,以及 DirectX 型別定義等的標頭檔案。

//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>

這裡 D3DClass 的類定義保持儘可能簡單。它具有常規的建構函式、複製建構函式和解構函式。更重要的是,它具有 Initialize 和 Shutdown 函式。這將是我們在這個教程中主要關注的。除此之外,我還有一些輔助函式,這些函式對於本教程並不重要,以及一些私有成員變數,這些變數將在我們檢查 d3dclass.cpp 檔案時檢視。現在只需要意識到 Initialize 和 Shutdown 函式是我們關心的。

////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
	D3DClass();
	D3DClass(const D3DClass&);
	~D3DClass();

	bool Initialize(int, int, bool, HWND, bool, float, float);
	void Shutdown();
	
	void BeginScene(float, float, float, float);
	void EndScene();

	ID3D11Device* GetDevice();
	ID3D11DeviceContext* GetDeviceContext();

	void GetProjectionMatrix(D3DXMATRIX&);
	void GetWorldMatrix(D3DXMATRIX&);
	void GetOrthoMatrix(D3DXMATRIX&);

	void GetVideoCardInfo(char*, int&);

private:
	bool m_vsync_enabled;
	int m_videoCardMemory;
	char m_videoCardDescription[128];
	IDXGISwapChain* m_swapChain;
	ID3D11Device* m_device;
	ID3D11DeviceContext* m_deviceContext;
	ID3D11RenderTargetView* m_renderTargetView;
	ID3D11Texture2D* m_depthStencilBuffer;
	ID3D11DepthStencilState* m_depthStencilState;
	ID3D11DepthStencilView* m_depthStencilView;
	ID3D11RasterizerState* m_rasterState;
	D3DXMATRIX m_projectionMatrix;
	D3DXMATRIX m_worldMatrix;
	D3DXMATRIX m_orthoMatrix;
};

#endif

對於已經熟悉 Direct3D 的人來說,你可能會注意到在這個類中我沒有檢視矩陣變數。原因是我將把它放在相機類中,我們將在以後的教程中介紹。

D3dclass.cpp

[編輯 | 編輯原始碼]
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"

所以,像大多數類一樣,我們從在類建構函式中將所有成員指標初始化為 null 開始。來自標頭檔案的所有指標都在這裡被考慮了。

D3DClass::D3DClass()
{
	m_swapChain = 0;
	m_device = 0;
	m_deviceContext = 0;
	m_renderTargetView = 0;
	m_depthStencilBuffer = 0;
	m_depthStencilState = 0;
	m_depthStencilView = 0;
	m_rasterState = 0;
}


D3DClass::D3DClass(const D3DClass& other)
{
}


D3DClass::~D3DClass()
{
}

Initialize 函式是完成 DirectX 11 中 Direct3D 的整個設定的函式。我將所有必要的程式碼放在這裡,以及一些將簡化未來教程的內容。我本可以簡化它並刪除一些專案,但最好將所有這些內容包含在一個專門介紹它們的教程中。

傳遞給此函式的 screenWidth 和 screenHeight 變數是我們之前在 SystemClass 中建立的視窗的寬度和高度。Direct3D 將使用這些變數來初始化並使用相同的視窗尺寸。hwnd 變數是視窗的控制代碼。Direct3D 需要這個控制代碼來訪問之前建立的視窗。fullscreen 變數是我們是否以視窗模式或全屏模式執行。Direct3D 也需要它來建立具有正確設定的視窗。screenDepth 和 screenNear 變數是我們將在視窗中渲染的 3D 環境的深度設定。vsync 變量表示我們是否希望 Direct3D 根據使用者的顯示器重新整理率渲染,或者儘可能快地渲染。

bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, 
			  float screenDepth, float screenNear)
{
	HRESULT result;
	IDXGIFactory* factory;
	IDXGIAdapter* adapter;
	IDXGIOutput* adapterOutput;
	unsigned int numModes, i, numerator, denominator, stringLength;
	DXGI_MODE_DESC* displayModeList;
	DXGI_ADAPTER_DESC adapterDesc;
	int error;
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	D3D_FEATURE_LEVEL featureLevel;
	ID3D11Texture2D* backBufferPtr;
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	D3D11_RASTERIZER_DESC rasterDesc;
	D3D11_VIEWPORT viewport;
        int aspectConstraint;
	float fieldOfView, screenZoom, screenAspect, screenAspectOrigin;


	// Store the vsync setting.
	m_vsync_enabled = vsync;

在我們可以初始化 Direct3D 之前,我們必須從顯示卡/顯示器獲取重新整理率。每臺計算機可能略有不同,因此我們需要查詢這些資訊。我們查詢分子和分母值,然後在設定過程中將它們傳遞給 DirectX,它將計算正確的重新整理率。如果我們沒有這樣做,而是將重新整理率設定為可能並非所有計算機都存在的預設值,那麼 DirectX 將透過執行 blit 而不是緩衝區翻轉來響應,這將降低效能並在除錯輸出中給我們帶來令人討厭的錯誤。

	// Create a DirectX graphics interface factory.
	result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
	if(FAILED(result))
	{
		return false;
	}

	// Use the factory to create an adapter for the primary graphics interface (video card).
	result = factory->EnumAdapters(0, &adapter);
	if(FAILED(result))
	{
		return false;
	}

	// Enumerate the primary adapter output (monitor).
	result = adapter->EnumOutputs(0, &adapterOutput);
	if(FAILED(result))
	{
		return false;
	}

	// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Create a list to hold all the possible display modes for this monitor/video card combination.
	displayModeList = new DXGI_MODE_DESC[numModes];
	if(!displayModeList)
	{
		return false;
	}

	// Now fill the display mode list structures.
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
	if(FAILED(result))
	{
		return false;
	}

	// Now go through all the display modes and find the one that matches the screen width and height.
	// When a match is found store the numerator and denominator of the refresh rate for that monitor.
	for(i=0; i<numModes; i++)
	{
		if(displayModeList[i].Width == (unsigned int)screenWidth)
		{
			if(displayModeList[i].Height == (unsigned int)screenHeight)
			{
				numerator = displayModeList[i].RefreshRate.Numerator;
				denominator = displayModeList[i].RefreshRate.Denominator;
			}
		}
	}

我們現在有了重新整理率的分子和分母。使用介面卡檢索的最後一件事是顯示卡的名稱和顯示卡上的記憶體量。

	// Get the adapter (video card) description.
	result = adapter->GetDesc(&adapterDesc);
	if(FAILED(result))
	{
		return false;
	}

	// Store the dedicated video card memory in megabytes.
	m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

	// Convert the name of the video card to a character array and store it.
	error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
	if(error != 0)
	{
		return false;
	}

現在我們已經儲存了重新整理率的分子和分母以及顯示卡資訊,我們可以釋放用於獲取這些資訊的結構和介面。

	// Release the display mode list.
	delete [] displayModeList;
	displayModeList = 0;

	// Release the adapter output.
	adapterOutput->Release();
	adapterOutput = 0;

	// Release the adapter.
	adapter->Release();
	adapter = 0;

	// Release the factory.
	factory->Release();
	factory = 0;

現在我們已經從系統獲得了重新整理率,我們可以開始 DirectX 初始化。我們要做的第一件事是填寫交換鏈的描述。交換鏈是將繪製圖形的前後緩衝區。通常你使用一個單獨的後緩衝區,在其中進行所有繪製,然後將其交換到前緩衝區,然後在前緩衝區上顯示在使用者的螢幕上。這就是為什麼它被稱為交換鏈的原因。

	// Initialize the swap chain description.
	ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

	// Set to a single back buffer.
	swapChainDesc.BufferCount = 1;

	// Set the width and height of the back buffer.
	swapChainDesc.BufferDesc.Width = screenWidth;
	swapChainDesc.BufferDesc.Height = screenHeight;

	// Set regular 32-bit surface for the back buffer.
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

交換鏈描述的下一部分是重新整理率。重新整理率是每秒將後緩衝區繪製到前緩衝區的次數。如果在我們的 graphicsclass.h 標頭檔案中將 vsync 設定為 true,那麼這將將重新整理率鎖定到系統設定(例如 60hz)。這意味著它每秒只繪製螢幕 60 次(或者如果系統重新整理率超過 60 次,則繪製更多次)。但是,如果我們將 vsync 設定為 false,那麼它將盡可能快地繪製螢幕,但是這可能會導致一些視覺偽影。

	// Set the refresh rate of the back buffer.
	if(m_vsync_enabled)
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
	}
	else
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	}

	// Set the usage of the back buffer.
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

	// Set the handle for the window to render to.
	swapChainDesc.OutputWindow = hwnd;

	// Turn multisampling off.
	swapChainDesc.SampleDesc.Count = 1;
	swapChainDesc.SampleDesc.Quality = 0;

	// Set to full screen or windowed mode.
	if(fullscreen)
	{
		swapChainDesc.Windowed = false;
	}
	else
	{
		swapChainDesc.Windowed = true;
	}

	// Set the scan line ordering and scaling to unspecified.
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// Discard the back buffer contents after presenting.
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

	// Don't set the advanced flags.
	swapChainDesc.Flags = 0;

在設定交換鏈描述後,我們還需要設定另一個稱為功能級別的變數。此變數告訴 DirectX 我們計劃使用哪個版本。在這裡我們將功能級別設定為 11.0,即 DirectX 11。如果您計劃支援多個版本或在低端硬體上執行,可以將其設定為 10 或 9 以使用較低版本的 DirectX。

	// Set the feature level to DirectX 11.
	featureLevel = D3D_FEATURE_LEVEL_11_0;

現在交換鏈描述和功能級別已填寫完畢,我們可以建立交換鏈、Direct3D 裝置和 Direct3D 裝置上下文。Direct3D 裝置和 Direct3D 裝置上下文非常重要,它們是所有 Direct3D 函式的介面。從現在開始,我們將幾乎所有操作都使用裝置和裝置上下文。

熟悉以前版本的 DirectX 的讀者會認識到 Direct3D 裝置,但對新的 Direct3D 裝置上下文會感到陌生。基本上,它們將 Direct3D 裝置的功能拆分為兩個不同的裝置,因此您現在需要同時使用它們。

請注意,如果使用者沒有 DirectX 11 顯示卡,此函式呼叫將無法建立裝置和裝置上下文。此外,如果您自己測試 DirectX 11 功能,並且沒有 DirectX 11 顯示卡,那麼您可以將 D3D_DRIVER_TYPE_HARDWARE 替換為 D3D_DRIVER_TYPE_REFERENCE,DirectX 將使用您的 CPU 繪製而不是顯示卡硬體。請注意,這會降低 1/1000 的速度,但對於那些在所有機器上還沒有 DirectX 11 顯示卡的人來說很有用。

	// Create the swap chain, Direct3D device, and Direct3D device context.
	result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, 
					       D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
	if(FAILED(result))
	{
		return false;
	}

有時,如果主顯示卡與 DirectX 11 不相容,建立裝置的此呼叫將失敗。某些機器的主顯示卡可能是 DirectX 10 顯示卡,而副顯示卡可能是 DirectX 11 顯示卡。一些混合顯示卡也是這樣工作的,主顯示卡是低功耗的英特爾顯示卡,而副顯示卡是高功耗的英偉達顯示卡。要解決此問題,您需要不使用預設裝置,而是列舉機器中的所有顯示卡,讓使用者選擇要使用哪個顯示卡,然後在建立裝置時指定該顯示卡。

現在我們有了交換鏈,我們需要獲取指向後備緩衝區的指標,然後將其附加到交換鏈。我們將使用 CreateRenderTargetView 函式將後備緩衝區附加到我們的交換鏈。

	// Get the pointer to the back buffer.
	result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
	if(FAILED(result))
	{
		return false;
	}

	// Create the render target view with the back buffer pointer.
	result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
	if(FAILED(result))
	{
		return false;
	}

	// Release pointer to the back buffer as we no longer need it.
	backBufferPtr->Release();
	backBufferPtr = 0;

我們還需要設定深度緩衝區描述。我們將使用它來建立深度緩衝區,以便我們的多邊形可以在 3D 空間中正確渲染。同時,我們將深度緩衝區附加一個模板緩衝區。模板緩衝區可用於實現運動模糊、體積陰影等效果。

	// Initialize the description of the depth buffer.
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

	// Set up the description of the depth buffer.
	depthBufferDesc.Width = screenWidth;
	depthBufferDesc.Height = screenHeight;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc.Count = 1;
	depthBufferDesc.SampleDesc.Quality = 0;
	depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;

現在,我們使用該描述建立深度/模板緩衝區。您會注意到我們使用 CreateTexture2D 函式來製作緩衝區,因此緩衝區只是一個 2D 紋理。這是因為,一旦您的多邊形排序並光柵化,它們最終就變成了此 2D 緩衝區中的彩色畫素。然後,此 2D 緩衝區被繪製到螢幕上。

	// Create the texture for the depth buffer using the filled out description.
	result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
	if(FAILED(result))
	{
		return false;
	}

現在,我們需要設定深度模板描述。這使我們能夠控制 Direct3D 對每個畫素執行的深度測試型別。

	// Initialize the description of the stencil state.
	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

	// Set up the description of the stencil state.
	depthStencilDesc.DepthEnable = true;
	depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;

	// Stencil operations if pixel is front-facing.
	depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	// Stencil operations if pixel is back-facing.
	depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

填寫描述後,我們現在可以建立一個深度模板狀態。

	// Create the depth stencil state.
	result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
	if(FAILED(result))
	{
		return false;
	}

有了建立的深度模板狀態,我們現在可以設定它使其生效。請注意,我們使用裝置上下文來設定它。

	// Set the depth stencil state.
	m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

接下來我們需要建立的是深度模板緩衝區檢視的描述。我們這樣做是為了讓 Direct3D 知道將深度緩衝區用作深度模板紋理。填寫描述後,我們呼叫 CreateDepthStencilView 函式來建立它。

	// Initailze the depth stencil view.
	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

	// Set up the depth stencil view description.
	depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;

	// Create the depth stencil view.
	result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
	if(FAILED(result))
	{
		return false;
	}

建立完成後,我們現在可以呼叫 OMSetRenderTargets。這會將渲染目標檢視和深度模板緩衝區繫結到輸出渲染管道。這樣,管道渲染的圖形就會被繪製到我們之前建立的後備緩衝區。將圖形寫入後備緩衝區後,我們可以將其交換到前臺並在使用者的螢幕上顯示我們的圖形。

	// Bind the render target view and depth stencil buffer to the output render pipeline.
	m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

現在渲染目標已設定完畢,我們可以繼續進行一些額外的函式,這些函式將在以後的教程中為我們提供更多對場景的控制。第一件事是我們將建立一個光柵化器狀態。這將使我們能夠控制多邊形的渲染方式。我們可以執行諸如使場景以線框模式渲染或讓 DirectX 繪製多邊形的正面和背面等操作。預設情況下,DirectX 已經設定了一個光柵化器狀態,並且與下面的狀態完全相同,但是除非您自己設定一個,否則您無法控制更改它。

	// Setup the raster description which will determine how and what polygons will be drawn.
	rasterDesc.AntialiasedLineEnable = false;
	rasterDesc.CullMode = D3D11_CULL_BACK;
	rasterDesc.DepthBias = 0;
	rasterDesc.DepthBiasClamp = 0.0f;
	rasterDesc.DepthClipEnable = true;
	rasterDesc.FillMode = D3D11_FILL_SOLID;
	rasterDesc.FrontCounterClockwise = false;
	rasterDesc.MultisampleEnable = false;
	rasterDesc.ScissorEnable = false;
	rasterDesc.SlopeScaledDepthBias = 0.0f;

	// Create the rasterizer state from the description we just filled out.
	result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
	if(FAILED(result))
	{
		return false;
	}

	// Now set the rasterizer state.
	m_deviceContext->RSSetState(m_rasterState);

還需要設定視口,以便 Direct3D 可以將裁剪空間座標對映到渲染目標空間。將其設定為視窗的整個大小。

	// Setup the viewport for rendering.
	viewport.Width = (float)screenWidth;
	viewport.Height = (float)screenHeight;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;

	// Create the viewport.
	m_deviceContext->RSSetViewports(1, &viewport);

現在我們將建立投影矩陣。投影矩陣用於將 3D 場景轉換為我們之前建立的 2D 視口空間。我們需要保留此矩陣的副本,以便我們可以將其傳遞到用於渲染場景的著色器。

	// Setup the projection matrix.
	fieldOfView = (float)D3DX_PI / 4.0f;
	screenZoom = 1.0f;
	screenAspect = (float)screenWidth / (float)screenHeight;
        screenAspectOrigin = 16.0f / 9.0f;
        aspectConstraint = 1;
        
        switch (aspectConstraint)
	{
		case 1:
		if (screenAspect < screenAspectOrigin)
		{
			screenZoom *= screenAspect / screenAspectOrigin;
		}
		break;
		case 2:
		screenZoom *= screenAspect / screenAspectOrigin;
	}

	// Create the projection matrix for 3D rendering.
	D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, 2.0f * std::atan(std::tan(fieldOfView / 2.0f) / screenZoom), screenAspect, screenNear, screenDepth);

我們還將建立另一個矩陣,稱為世界矩陣。此矩陣用於將物件的頂點轉換為 3D 場景中的頂點。此矩陣還將用於在 3D 空間中旋轉、平移和縮放物件。從一開始,我們將矩陣初始化為單位矩陣,並在該物件中保留其副本。該副本需要傳遞到著色器中進行渲染。

	// Initialize the world matrix to the identity matrix.
	D3DXMatrixIdentity(&m_worldMatrix);

這是您通常建立檢視矩陣的地方。檢視矩陣用於計算我們從哪裡檢視場景的視角。您可以將其視為相機,您只能透過該相機檢視場景。由於其用途,我將在以後的教程中在相機類中建立它,因為從邏輯上講它更適合在那裡,而現在跳過它。

我們將在 Initialize 函式中設定的最後一件事是正交投影矩陣。此矩陣用於渲染 2D 元素(如螢幕上的使用者介面),使我們能夠跳過 3D 渲染。您將在以後的教程中看到它,當我們檢視將 2D 圖形和字型渲染到螢幕時。

	// Create an orthographic projection matrix for 2D rendering.
	D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);

	return true;
}

Shutdown 函式將釋放和清理在 Initialize 函式中使用的所有指標,這非常簡單。但是,在執行此操作之前,我在呼叫中加入了一個強制交換鏈首先進入視窗模式,然後才能釋放任何指標。如果這樣做沒有完成,並且您嘗試在全屏模式下釋放交換鏈,它將丟擲一些異常。因此,為了避免這種情況發生,我們始終在關閉 Direct3D 之前強制進入視窗模式。

void D3DClass::Shutdown()
{
	// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
	if(m_swapChain)
	{
		m_swapChain->SetFullscreenState(false, NULL);
	}

	if(m_rasterState)
	{
		m_rasterState->Release();
		m_rasterState = 0;
	}

	if(m_depthStencilView)
	{
		m_depthStencilView->Release();
		m_depthStencilView = 0;
	}

	if(m_depthStencilState)
	{
		m_depthStencilState->Release();
		m_depthStencilState = 0;
	}

	if(m_depthStencilBuffer)
	{
		m_depthStencilBuffer->Release();
		m_depthStencilBuffer = 0;
	}

	if(m_renderTargetView)
	{
		m_renderTargetView->Release();
		m_renderTargetView = 0;
	}

	if(m_deviceContext)
	{
		m_deviceContext->Release();
		m_deviceContext = 0;
	}

	if(m_device)
	{
		m_device->Release();
		m_device = 0;
	}

	if(m_swapChain)
	{
		m_swapChain->Release();
		m_swapChain = 0;
	}

	return;
}

在 D3DClass 中,我有一些幫助函式。前兩個是 BeginScene 和 EndScene。每當我們要繪製一個新的 3D 場景(在每一幀的開頭)時,都會呼叫 BeginScene。它所做的只是初始化緩衝區,使其空白並準備繪製。另一個函式是 Endscene,它告訴交換鏈在每一幀的所有繪製完成後顯示我們的 3D 場景。

void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
	float color[4];


	// Setup the color to clear the buffer to.
	color[0] = red;
	color[1] = green;
	color[2] = blue;
	color[3] = alpha;

	// Clear the back buffer.
	m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);
    
	// Clear the depth buffer.
	m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

	return;
}


void D3DClass::EndScene()
{
	// Present the back buffer to the screen since rendering is complete.
	if(m_vsync_enabled)
	{
		// Lock to screen refresh rate.
		m_swapChain->Present(1, 0);
	}
	else
	{
		// Present as fast as possible.
		m_swapChain->Present(0, 0);
	}

	return;
}

接下來的這些函式只是獲取指向 Direct3D 裝置和 Direct3D 裝置上下文的指標。這些幫助函式將經常由框架呼叫。

ID3D11Device* D3DClass::GetDevice()
{
	return m_device;
}


ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
	return m_deviceContext;
}

接下來的三個幫助函式向呼叫函式提供投影、世界和正交矩陣的副本。大多數著色器都需要這些矩陣進行渲染,因此需要有一種簡單的方法讓外部物件獲取它們的副本。在本教程中我們不會呼叫這些函式,我只是解釋為什麼它們在程式碼中。

void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
	projectionMatrix = m_projectionMatrix;
	return;
}


void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
	worldMatrix = m_worldMatrix;
	return;
}


void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
	orthoMatrix = m_orthoMatrix;
	return;
}

最後一個幫助函式透過引用返回顯示卡的名稱和顯示卡上的專用記憶體量。瞭解顯示卡名稱和視訊記憶體大小有助於在不同配置下進行除錯。

void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
	strcpy_s(cardName, 128, m_videoCardDescription);
	memory = m_videoCardMemory;
	return;
}

總結

[edit | edit source]

因此,我們終於能夠初始化和關閉 Direct3D,以及將顏色渲染到視窗。編譯並執行程式碼將產生與上次教程相同的視窗,但 Direct3D 已初始化,並且視窗被清除為灰色。編譯並執行程式碼還將顯示您的編譯器是否已正確設定,以及它是否可以看到 DirectX SDK 中的標頭檔案和庫檔案。

待辦事項練習

[edit | edit source]

1. 重新編譯程式碼並執行程式以確保 DirectX 工作正常,如果沒有,請檢視第一個教程中的步驟。視窗顯示後按 Esc 鍵退出。

2. 將 graphicsclass.h 中的全域性變數更改為全屏,然後重新編譯/執行。

3. 將 GraphicsClass::Render 中的清除顏色更改為黃色。

4. 將顯示卡名稱和記憶體寫入文字檔案。

華夏公益教科書