跳转到内容

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. 将显卡名称和内存写入文本文件。

华夏公益教科书