DirectX/10.0/Direct3D/初始化 DirectX
本教程将是使用 DirectX 11 的第一个介绍。我们将讨论如何初始化和关闭 Direct3D 以及如何渲染到窗口。
我们将添加另一个类到框架中,该类将处理所有 Direct3D 系统函数。我们将这个类命名为 D3DClass。我已在下面更新了框架图。
正如你所见,D3DClass 将位于 GraphicsClass 内部。上一教程中提到,所有新的图形相关类都将封装在 GraphicsClass 中,这就是为什么它成为新 D3DClass 的最佳位置。现在让我们看看对 GraphicsClass 做出的更改。
//////////////////////////////////////////////////////////////////////////////// // 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
如果你还记得上一教程,这个类是完全空的,没有任何代码。现在我们有了 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 头文件
//////////////////////////////////////////////////////////////////////////////// // 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 的人来说,你可能会注意到在这个类中我没有视图矩阵变量。原因是我将把它放在相机类中,我们将在以后的教程中介绍。
//////////////////////////////////////////////////////////////////////////////// // 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. 将显卡名称和内存写入文本文件。