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