Putting It All Together
Updated General-Purpose Game Class
列表11.10中列出了Game.h头文件的部分代码。在第10章“Project Setup and Window Initialization”的window initialization基础上增加了Direct3d initialization部分的代码。在本书配套网站上提供了完全的代码。
列表11.10 An Abbreviated Game.h Header File
#pragma once
#include "Common.h"
#include "GameClock.h"
#include "GameTime.h"
namespace Library
{
class Game
{
public:
/* ... Previously presented members removed for brevity ... */
ID3D11Device1* Direct3DDevice() const;
ID3D11DeviceContext1* Direct3DDeviceContext() const;
bool DepthBufferEnabled() const;
bool IsFullScreen() const;
const D3D11_TEXTURE2D_DESC& BackBufferDesc() const;
const D3D11_VIEWPORT& Viewport() const;
protected:
/* ... Previously presented members removed for brevity ... */
virtual void InitializeDirectX();
static const UINT DefaultFrameRate;
static const UINT DefaultMultiSamplingCount;
D3D_FEATURE_LEVEL mFeatureLevel;
ID3D11Device1* mDirect3DDevice;
ID3D11DeviceContext1* mDirect3DDeviceContext;
IDXGISwapChain1* mSwapChain;
UINT mFrameRate;
bool mIsFullScreen;
bool mDepthStencilBufferEnabled;
bool mMultiSamplingEnabled;
UINT mMultiSamplingCount;
UINT mMultiSamplingQualityLevels;
ID3D11Texture2D* mDepthStencilBuffer;
D3D11_TEXTURE2D_DESC mBackBufferDesc;
ID3D11RenderTargetView* mRenderTargetView;
ID3D11DepthStencilView* mDepthStencilView;
D3D11_VIEWPORT mViewport;
};
}
列表11.11 The Common.h Header File
#pragma once
#include <windows.h>
#include <exception>
#include <cassert>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <d3d11_1.h>
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#define DeleteObject(object) if((object) != NULL) { delete object; object = NULL; }
#define DeleteObjects(objects) if((objects) != NULL) { delete[] objects; objects = NULL; }
#define ReleaseObject(object) if((object) != NULL) { object->Release(); object = NULL; }
namespace Library
{
typedef unsigned char byte;
}
using namespace DirectX;
using namespace DirectX::PackedVector;
其中头文件d3d11_1.h,DirectXMath.h和DirectXPackedVector.h包含了Direct3D 11.1和DirectXMath的声明。DirectXMath包含于DirectX和DirectX::PackedVector命名空间中,因此包含了这两个命名空间的声明。
列表11.12列出了Game类修改后的实现代码。与列表11.10一样,还是只列出了新增的部分代码,略去了之前已经展示过的部分。
列表11.12 An Abbreviated Game.cpp File
#include "Game.h"
#include "GameException.h"
namespace Library
{
const UINT Game::DefaultScreenWidth = 1024;
const UINT Game::DefaultScreenHeight = 768;
const UINT Game::DefaultFrameRate = 60;
const UINT Game::DefaultMultiSamplingCount = 4;
Game::Game(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand)
: mInstance(instance), mWindowClass(windowClass), mWindowTitle(windowTitle), mShowCommand(showCommand),
mWindowHandle(), mWindow(),
mScreenWidth(DefaultScreenWidth), mScreenHeight(DefaultScreenHeight),
mGameClock(), mGameTime(),
mFeatureLevel(D3D_FEATURE_LEVEL_9_1), mDirect3DDevice(nullptr), mDirect3DDeviceContext(nullptr), mSwapChain(nullptr),
mFrameRate(DefaultFrameRate), mIsFullScreen(false),
mDepthStencilBufferEnabled(false), mMultiSamplingEnabled(false), mMultiSamplingCount(DefaultMultiSamplingCount), mMultiSamplingQualityLevels(0),
mDepthStencilBuffer(nullptr), mRenderTargetView(nullptr), mDepthStencilView(nullptr), mViewport()
{
}
void Game::Run()
{
InitializeWindow();
InitializeDirectX();
Initialize();
MSG message;
ZeroMemory(&message, sizeof(message));
mGameClock.Reset();
while(message.message != WM_QUIT)
{
if (PeekMessage(&message, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
else
{
mGameClock.UpdateGameTime(mGameTime);
Update(mGameTime);
Draw(mGameTime);
}
}
Shutdown();
}
void Game::Shutdown()
{
ReleaseObject(mRenderTargetView);
ReleaseObject(mDepthStencilView);
ReleaseObject(mSwapChain);
ReleaseObject(mDepthStencilBuffer);
if (mDirect3DDeviceContext != nullptr)
{
mDirect3DDeviceContext->ClearState();
}
ReleaseObject(mDirect3DDeviceContext);
ReleaseObject(mDirect3DDevice);
UnregisterClass(mWindowClass.c_str(), mWindow.hInstance);
}
void Game::InitializeDirectX()
{
HRESULT hr;
UINT createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevels[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
};
// Step 1: Create the Direct3D device and device context interfaces
ID3D11Device* direct3DDevice = nullptr;
ID3D11DeviceContext* direct3DDeviceContext = nullptr;
if (FAILED(hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlags, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &direct3DDevice, &mFeatureLevel, &direct3DDeviceContext)))
{
throw GameException("D3D11CreateDevice() failed", hr);
}
if (FAILED(hr = direct3DDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&mDirect3DDevice))))
{
throw GameException("ID3D11Device::QueryInterface() failed", hr);
}
if (FAILED(hr = direct3DDeviceContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(&mDirect3DDeviceContext))))
{
throw GameException("ID3D11Device::QueryInterface() failed", hr);
}
ReleaseObject(direct3DDevice);
ReleaseObject(direct3DDeviceContext);
// Step 2: Check for multisampling support
mDirect3DDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, mMultiSamplingCount, &mMultiSamplingQualityLevels);
if (mMultiSamplingQualityLevels == 0)
{
throw GameException("Unsupported multi-sampling quality");
}
DXGI_SWAP_CHAIN_DESC1 swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
swapChainDesc.Width = mScreenWidth;
swapChainDesc.Height = mScreenHeight;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
if (mMultiSamplingEnabled)
{
swapChainDesc.SampleDesc.Count = mMultiSamplingCount;
swapChainDesc.SampleDesc.Quality = mMultiSamplingQualityLevels - 1;
}
else
{
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
}
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// Step 3: Create the swap chain
IDXGIDevice* dxgiDevice = nullptr;
if (FAILED(hr = mDirect3DDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice))))
{
throw GameException("ID3D11Device::QueryInterface() failed", hr);
}
IDXGIAdapter *dxgiAdapter = nullptr;
if (FAILED(hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter),reinterpret_cast<void**>(&dxgiAdapter))))
{
ReleaseObject(dxgiDevice);
throw GameException("IDXGIDevice::GetParent() failed retrieving adapter.", hr);
}
IDXGIFactory2* dxgiFactory = nullptr;
if (FAILED(hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(&dxgiFactory))))
{
ReleaseObject(dxgiDevice);
ReleaseObject(dxgiAdapter);
throw GameException("IDXGIAdapter::GetParent() failed retrieving factory.", hr);
}
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc;
ZeroMemory(&fullScreenDesc, sizeof(fullScreenDesc));
fullScreenDesc.RefreshRate.Numerator = mFrameRate;
fullScreenDesc.RefreshRate.Denominator = 1;
fullScreenDesc.Windowed = !mIsFullScreen;
if (FAILED(hr = dxgiFactory->CreateSwapChainForHwnd(dxgiDevice, mWindowHandle, &swapChainDesc, &fullScreenDesc, nullptr, &mSwapChain)))
{
ReleaseObject(dxgiDevice);
ReleaseObject(dxgiAdapter);
ReleaseObject(dxgiFactory);
throw GameException("IDXGIDevice::CreateSwapChainForHwnd() failed.", hr);
}
ReleaseObject(dxgiDevice);
ReleaseObject(dxgiAdapter);
ReleaseObject(dxgiFactory);
// Step 4: Create the render target view
ID3D11Texture2D* backBuffer;
if (FAILED(hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer))))
{
throw GameException("IDXGISwapChain::GetBuffer() failed.", hr);
}
backBuffer->GetDesc(&mBackBufferDesc);
if (FAILED(hr = mDirect3DDevice->CreateRenderTargetView(backBuffer, nullptr, &mRenderTargetView)))
{
ReleaseObject(backBuffer);
throw GameException("IDXGIDevice::CreateRenderTargetView() failed.", hr);
}
ReleaseObject(backBuffer);
// Step 5: Create the depth-stencil view
if (mDepthStencilBufferEnabled)
{
D3D11_TEXTURE2D_DESC depthStencilDesc;
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
depthStencilDesc.Width = mScreenWidth;
depthStencilDesc.Height = mScreenHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
if (mMultiSamplingEnabled)
{
depthStencilDesc.SampleDesc.Count = mMultiSamplingCount;
depthStencilDesc.SampleDesc.Quality = mMultiSamplingQualityLevels - 1;
}
else
{
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
}
if (FAILED(hr = mDirect3DDevice->CreateTexture2D(&depthStencilDesc, nullptr, &mDepthStencilBuffer)))
{
throw GameException("IDXGIDevice::CreateTexture2D() failed.", hr);
}
if (FAILED(hr = mDirect3DDevice->CreateDepthStencilView(mDepthStencilBuffer, nullptr, &mDepthStencilView)))
{
throw GameException("IDXGIDevice::CreateDepthStencilView() failed.", hr);
}
}
// Step 6: Bind the render target and depth-stencil views to OM stage
mDirect3DDeviceContext->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);
mViewport.TopLeftX = 0.0f;
mViewport.TopLeftY = 0.0f;
mViewport.Width = static_cast<float>(mScreenWidth);
mViewport.Height = static_cast<float>(mScreenHeight);
mViewport.MinDepth = 0.0f;
mViewport.MaxDepth = 1.0f;
// Step 7: Set the viewport
mDirect3DDeviceContext->RSSetViewports(1, &mViewport);
}
}
列表11.12中的代码包含Game类的构造函数,Shutdown()函数和InitializeDirectX()函数。其中构造函数初始化所有的成员变量;Shutdown()函数释放在InitializeDirectX()函数中实例化的所有Direct3D对象。Run()函数中增加了对Direct initialization函数的调用。需要注意的是,InitializeDirectX(),Shutdown()和Run()函数都是虚函数,因此可以在派生类中覆盖。这些函数的实现代码是Game类的通用实现。
Specialized Game Class
列表11.13列出RenderGame类的头文件,该类继承自Library::Game类。RenderGame应该在Game project中创建,而不是在Library project中,Library project包含了目前大部分代码。
列表11.13 The RenderingGame.h File
#pragma once
#include "Common.h"
#include "Game.h"
using namespace Library;
namespace Rendering
{
class RenderingGame : public Game
{
public:
RenderingGame(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand);
~RenderingGame();
virtual void Initialize() override;
virtual void Update(const GameTime& gameTime) override;
virtual void Draw(const GameTime& gameTime) override;
private:
static const XMVECTORF32 BackgroundColor;
};
}
列表11.14 The RenderingGame.cpp File
#include "RenderingGame.h"
#include "GameException.h"
namespace Rendering
{
const XMVECTORF32 RenderingGame::BackgroundColor = { 0.392f, 0.584f, 0.929f, 1.0f };
RenderingGame::RenderingGame(HINSTANCE instance, const std::wstring& windowClass, const std::wstring& windowTitle, int showCommand)
: Game(instance, windowClass, windowTitle, showCommand)
{
mDepthStencilBufferEnabled = true;
mMultiSamplingEnabled = true;
}
RenderingGame::~RenderingGame()
{
}
void RenderingGame::Initialize()
{
Game::Initialize();
}
void RenderingGame::Update(const GameTime &gameTime)
{
Game::Update(gameTime);
}
void RenderingGame::Draw(const GameTime &gameTime)
{
mDirect3DDeviceContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&BackgroundColor));
mDirect3DDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
Game::Draw(gameTime);
HRESULT hr = mSwapChain->Present(0, 0);
if (FAILED(hr))
{
throw GameException("IDXGISwapChain::Present() failed.", hr);
}
}
}
RenderGame类在一个空白的屏幕上渲染一种cornflower blue颜色(矢车菊蓝)(或者通过指定BackgroundColor变量值使用其他的任何颜色)。这段代码显示了Game的派生类通用结构,通过扩展Initialize()和Update()函数可以可以创建功能更加丰富的应用程序。
在RenderGame类的构造函数中,通过设置变量的初始值,允许使用depth-stencil buffer以及multisampling(尽管在这个例子中并没具体用到)。在Draw()函数中,通过调用ID3D11DeviceContext1::ClearRenderTargetView()函数重置render target。该函数把整个render target设置为参数指定的颜色。接下来,调用ID3DDeviceContext1::ClearDepthStencilView()函数重置depth-stencil buffer。ClearDepathStencilView()函数的第二个参数值,由一个或多个D3D11_CLEAR_FLAG枚举常量通过按位于运算组合而成,用于指定depth-stencile buffer中要重置的部分。第三个参数指定depth buffer的重置值(一般为1.0),而第四个参数指定stencil buffer的重置值(一般为0)。
在重置了render target和depth-stencil view之后,就开始执行具体游戏的渲染函数。在列表14.14中,对Game::Draw()函数的调用是为了渲染所有可见的游戏组件。关于游戏组件的内容,将在下一章讨论。当完成了所有具体游戏的渲染操作之后,就可以调用IDXGISwapChain1::Present()函数对swap chain中的buffers进行翻转。Present()函数的第一个参数表示,相对于显示器的垂直刷新,该帧的显示方式。参数值0表示该帧会立即显示,而不用与显示器的垂直刷新同步。第二个参数是一组DXGI_PRSENT枚举常量按位于的计算结果。如果参数设为0,就简单地把图像帧显示到输出窗口上,而不需要任何特别的选项。在MSDN在线文档中,可以查看更多高级的垂直刷新同和显示设置。
Updated WinMain()
在运行这个示例应用程序之前,还有最后一步要完成,就是修改WinMain()函数,在函数中使用新的RenderGame类。修改非常简单,只需要使用RenderGame.h替换Game.h头文件并把变量game更新到对应的RenderingGame类的实例。列表11.15列出了Program.cpp文件中修改后的WinMain()代码。
列表11.15 The Program.cpp File
#include <memory>
#include "GameException.h"
#include "RenderingGame.h"
#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif
using namespace Library;
using namespace Rendering;
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previousInstance, LPSTR commandLine, int showCommand)
{
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
std::unique_ptr<RenderingGame> game(new RenderingGame(instance, L"RenderingClass", L"Real-Time 3D Rendering", showCommand));
try
{
game->Run();
}
catch (GameException ex)
{
MessageBox(game->WindowHandle(), ex.whatw().c_str(), game->WindowTitle().c_str(), MB_ABORTRETRYIGNORE);
}
return 0;
}
编译并运行示例程序,就能得到与图11.2所示的结果。
图11.2 A solid-color window rendered with Direct3D using the demo framework.
同样,该示例程序依然很单调,但是已经完成了大部分准备工作,下一阶段开始使用Direct3D进行渲染更丰富的场景。
总结
本章主要讲述了Direct3D的初始化。首先,详细描述了Direct3D C++ API,并使用这些API完成了渲染引擎的基础框架。虽然还有很多系统模块没有实现,但是已经成功完成了第一个Direct3D应用程序。
下一章,将创建game组件的接口,实现鼠标和键盘输入系统,创建一个动态的3D camera,并学习如何把2D文本渲染到屏幕上。
Exercises
understand the initialization process. You’ll find an associated demo application on the book’s
companion website.
本章配套学习代码