第十一章 Direct3D Initialization
本章将完成渲染引擎的基础部分的最后一个模块Direct3D的初始化,学习Direct3D C++ API,并完成一个Direct3D应用程序。在此过程中,会回顾以前章节讨论的知识,包含DirectX sawp chain和depth/stencil buffering。
Initializing Direct3D
根据下面的步骤执行Direct3D的初始化:
1.创建Direct3D device(ID3D11Device)和device context接口(ID3D11DeviceContext)。
2.检查multisampling的支持。
3.创建swap chain。
4.创建一个render target view。
5.创建一个depth-stencil view。
6.把render target view和depth-stencil view与Direct3D管线中的output-merger阶段相结合。
7.设置viewport。
接下来,每一节对应的讲述每一个步骤。
1.创建Direct3D device(ID3D11Device)和device context接口(ID3D11DeviceContext)。
2.检查multisampling的支持。
3.创建swap chain。
4.创建一个render target view。
5.创建一个depth-stencil view。
6.把render target view和depth-stencil view与Direct3D管线中的output-merger阶段相结合。
7.设置viewport。
接下来,每一节对应的讲述每一个步骤。
Creating the Device and Device Context
一个Direct3D device表示一个显卡适配器,是对计算机底层图形能力的一种抽象。PC中一般包含有多个adapters,包括显卡本身(速度较快)以及完全由软件实现的adpters(速度较慢)。可以使用ID3D11Device接口表示一个device,该接口用于创建资源,以及列举display adapter支持的功能特性。一个 device context也可以表示一个display adapter,但是是在一个特定的条件下。ID3D11DeviceContext接口中封装了device context,使用一个device的resources生成渲染命令。
通过调用函数D3D11CreateDevice()创建一个Direct3D device和一个device context,函数原型和参数如下:
HRESULT WINAPI D3D11CreateDevice(
IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
CONST D3D_FEATURE_LEVEL* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device** ppDevice,
D3D_FEATURE_LEVEL* pFeatureLevel,
ID3D11DeviceContext** ppImmediateContext);
pAdapter:一个指向device所表示的display adapter对象的指针。传递一个参数NULL表示使用默认的adapter。调用IDXGIFactory::EnumAdapters()函数可以列出所有可用的adapters。
DriverType:该device所使用的设备驱动类型。要使用硬件显卡,需要指定该参数为D3D_DRIVER_TYPE_HADRWARE,这种类型可以达到最高的性能。但是,指定参数为D3D_DRIVER_TYPE_REFERENCE或者D3D_DRIVER_TYPE_WARP也可以使用微软提供的软件实现。要获取完整的driver types列表,可以查看在线文档。本书中所有的例子都使用hardware driver。
Software:指向一个软件实现Direct3D的第三方DLL的句柄。只有DriverType参数设为D3D_DRIVER_TYPE_SOFTWARE才会使用该参数。
Flags:创建Direct3D device时用到的选项标示。该参数的值由枚举类型D3D11_CREATE_DEVICE_FLAG的部分常量通过按位与运算得到。一个通用的参数选项是D3D11_CREATE_DEVICE_DEBUG,该常量值表示创建一个支持调试的device。
pFeatureLevels:表示一个指向targeted feature levels数组的指针,该数组根据levels的使用优先级对这些feature levels进行排列。Feature levels描述了一个device所支持的功能级别,并对应于一个Direct3D的版本。如果对参数指定一个NULL值,就会使用device支持的最高级别的feature level,除非device支持Direct3D 11.1版本。因为在这种情况下,最多只能支持到11.0,而不是11.1版本。如果想使用Direct3D 11.1,必须把D3D_FEATURE_LEVEL_11_1显式添加到pFeatureLevels级数中。另外,如果把11.1作为targed feature level,但是device不支持该版本,那么调用D3D11CreateDevice()函数就会失败。表格11.1列出了可能的feature levels。
DriverType:该device所使用的设备驱动类型。要使用硬件显卡,需要指定该参数为D3D_DRIVER_TYPE_HADRWARE,这种类型可以达到最高的性能。但是,指定参数为D3D_DRIVER_TYPE_REFERENCE或者D3D_DRIVER_TYPE_WARP也可以使用微软提供的软件实现。要获取完整的driver types列表,可以查看在线文档。本书中所有的例子都使用hardware driver。
Software:指向一个软件实现Direct3D的第三方DLL的句柄。只有DriverType参数设为D3D_DRIVER_TYPE_SOFTWARE才会使用该参数。
Flags:创建Direct3D device时用到的选项标示。该参数的值由枚举类型D3D11_CREATE_DEVICE_FLAG的部分常量通过按位与运算得到。一个通用的参数选项是D3D11_CREATE_DEVICE_DEBUG,该常量值表示创建一个支持调试的device。
pFeatureLevels:表示一个指向targeted feature levels数组的指针,该数组根据levels的使用优先级对这些feature levels进行排列。Feature levels描述了一个device所支持的功能级别,并对应于一个Direct3D的版本。如果对参数指定一个NULL值,就会使用device支持的最高级别的feature level,除非device支持Direct3D 11.1版本。因为在这种情况下,最多只能支持到11.0,而不是11.1版本。如果想使用Direct3D 11.1,必须把D3D_FEATURE_LEVEL_11_1显式添加到pFeatureLevels级数中。另外,如果把11.1作为targed feature level,但是device不支持该版本,那么调用D3D11CreateDevice()函数就会失败。表格11.1列出了可能的feature levels。
表格11.1 Direct3D Feature Levels
FeatureLevels:指定pFeatureLevels数组中的元素个数。
SDKVersion:指定SDK的版本。总是指定为D3D11_SDK_VERSION。
ppDevice:返回所创建的device。
ppImmediateContext:返回所创建的device context。
列表11.1给出了一个调用D3D11CreateDevice()函数的例子。
列表11.1 An Example Call to D3D11CreateDevice()
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
};
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);
}
Checking for Multisampling Support
Multisample Anti-Aliasing(MSAA多重采样反走样)是一种提高图片质量的技术。Aliasing表示在计算机显示器上一条斜线或一个三角形边的锯齿外观。之所以会产生锯齿是因为显示器使用发光的离散点(像素点)来显示虚拟的objects。虽然只是如一个像素般大小,但也不是微不足道的。而Anti-Aliasing就是用于移除这些“锯齿”的技术。图11.1中显示了一个使用anti-aliasing之后以及使用之后的例子。图11.1 An example of a shape rendered before (top) and after (bottom) anti-aliasing is applied.
减少每一个像素的尺寸可以改善aliasing,因此可以提高显示器的分辨率。但是,即使显示器达到最大的分辨率,也无法完全避免aliasing artifacts。有一种称为
Supersampling技术通过增加render target的分辨率来减小一个像素的有效尺寸。因此,可以采样多个子像素进行平均(该操作被称为
downsampling)来生成最终要显示的每一个像素的颜色。Supersampling通用使用一个4倍于显示器分辨率大小的render target,尽管更高的分辨率会带来更多的内存和运算成本。使用supersampling的情况下,pixel shader会执行计算第一个子像素,因此,要生成一个4倍于显示器分辨率大小的render target,pixel shader通常需要4倍的运算。Multisample是对supersampling的一种优化,pixel shader只需要执行计算显示器分辨率大小的每一个pixel(位于像素的中心),并把该颜色值用于每一个子像素。所有的Direct3D 11 devices都需要支持4x MSAA,尽管它们可能支持更高的质量级别。通过ID3D11Device::CheckMultisampleQualityLevels()函数可以查询一个device支持的质量级别;函数原型和参数如下:
HRESULT CheckMultisampleQualityLevels(
DXGI_FORMAT Format,
UINT SampleCount,
UINT *pNumQualityLevels);
Format:纹理的格式。通常指定该参数为DXGI_FORMAT_R8G8B8A8_UNORM,这是一种red,green,blue和alpha通道都为8位的4分量格式。当然,也可以使用其他多种纹理格式,更多的格式可以查看在线文档。
SampleCount:每一个像素的采样数量(比如,4倍的multisampling的采样数量就是4,每个像素由4个子像素平均得到)。
pNumQualityLevels:在给定Format和SampleCount参数条件下device能支持的quality levels个数。针对不同的硬件厂商,“quality level”具有不同的含意。一般来说,更高的quality levels表示更好的渲染结果以及更高的性能成本。如果pNumQualityLevels返回0,表示该device不支持这种Format和SampleCount的组合。
在创建swap chain时才会使用multisampling质量值,因此关于multisampling的示例代码放到下一节。
SampleCount:每一个像素的采样数量(比如,4倍的multisampling的采样数量就是4,每个像素由4个子像素平均得到)。
pNumQualityLevels:在给定Format和SampleCount参数条件下device能支持的quality levels个数。针对不同的硬件厂商,“quality level”具有不同的含意。一般来说,更高的quality levels表示更好的渲染结果以及更高的性能成本。如果pNumQualityLevels返回0,表示该device不支持这种Format和SampleCount的组合。
在创建swap chain时才会使用multisampling质量值,因此关于multisampling的示例代码放到下一节。
Creating the Swap Chain(交换链)
一个
swap chain是一组用于把帧显示到显示器的缓存。在第4章,“Hello,Shaders!”部分讨论的双缓冲,就是一个包含两个缓存的swap chain,一个front buffer和一个back buffer。真正用于显示的是front buffer,back buffer用于缓存新的数据帧。当应用程序提交了新完成的back buffer,这两个缓存就会被交换,这种刷新交换的过程又会重新开始。
Populating a Swap Chain Description
创建swap chain之前,首先需要构造一个DXGI_SWAP_CHAIN_DESC1结构体实例,该结体的成员结构如下:typedef struct DXGI_SWAP_CHAIN_DESC1
{
UINT Width;
UINT Height;
DXGI_FORMAT Format;
BOOL Stereo;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
DXGI_SCALING Scaling;
DXGI_SWAP_EFFECT SwapEffect;
DXGI_ALPHA_MODE AlphaMode;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC1;
Width:缓存分辨率的宽度。
Height:缓存分辨率的高度。
Format:back buffer的显示格式,比如DXGI_FORMAT_R8G8B8A8_UNORM。
Stereo:back buffer是否用于stereoscopic rendering(立体渲染)。
SampleDesc:Multisample相关的参数。当启动multisampling时,使用经过ID3D11Device::CheckMultisampleQualityLevels()测试有效的数据作为该参数值。
BufferUsage:Buffer usage和CPU access选项。比如,back buffer通常用于render target的输出(DXGI_USAGE_RENDER_TARGET_OUTPUT),但是也可以用于shader input(DXGI_USAGE_SHADER_INPUT)。
BufferCount:swap chain中包含的back buffer数量。参数值为1表示双缓冲(一个front buffer和一个back buffer)。参数值为2表示两个back buffer(triple buffering)。
Scaling:当back buffer的尺寸与target ouput(比如一个窗口)的尺寸不相同时缩放的方式。例如,DXGI_SCALING_STRETCH表示把图片缩放到与目标输出一样的尺寸。
SwapEffect:一个buffer在提交之后,怎样处理所保存的数据。DXGI_SWAP_EFFECT_DISCARD选项是所有选项中最有效率的一个,而且是唯一一个支持multisampling的选项。
AlphaMode:back buffer的透明方式。
Flags:创建swap chain时用到的选项。该参数的值由枚举类型D3D11_SWAP_CHAIN_FLAG的部分常量通过按位与运算得到。通过在线文档查看所有可用选项。
列表11.2列出了构造一个DXGI_SWAP_CHAIN_DESC1结构实例的代码,以及对前面讲到的CheckMultisampleQualityLevels()函数的调用。
列表11.2 Populating a Swap Chain Description Structure
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;
在列表11.2的代码中,没有给成员Stereo,Scaling,AlphaMode和Flags显式赋值。因为这些成员的默认值刚好就是“0”值(由ZeroMemory置为0)。
Populating a Full-Screen Description
Swap chain可以定义为全屏模式或窗口模式。要创建一个全屏模式的swap chain,需要构造一个DXGI_SWAP_CHAIN_FULLSCREEN_DESC结构体的实例;该结构体的成员结构如下:typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
DXGI_RATIONAL RefreshRate;
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
DXGI_MODE_SCALING Scaling;
BOOL Windowed;
} DXGI_SWAP_CHAIN_FULLSCREEN_DESC;
RefreshRate:描述显示器的刷新频率,单位为赫兹(hertz)。DXGI_RATIONAL结构中有两个成员,Numerator和Denominator,都是unsigned int类型。
如果显示器的刷新频率为60Hz,那么Numerator赋值为60,Denominator赋值为1。
ScanlineOrdering:指定显示器绘制图片的方式。例如,DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE表示一行一行(光栅化)地连续绘制(不会跳过任意行,非隔行光栅化)。
Scaling:指定图片的拉伸方式,以便于适应显示器的分辨率。例如,DXGI_MODE_SCALING_CENTERED表示图片在显示器上居中显示,不执行缩放。
Windowed:指定swap chain是否处于窗口模式下。
列表11.3列出了构造DXGI_SWAP_CHAIN_FULLSCREEN_DESC实例的代码
列表11.3 Populating a Full-Screen Description Structure
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc;
ZeroMemory(&fullScreenDesc, sizeof(fullScreenDesc));
fullScreenDesc.RefreshRate.Numerator = mFrameRate;
fullScreenDesc.RefreshRate.Denominator = 1;
fullScreenDesc.Windowed = !mIsFullScreen;
DirectX Graphics Infrastructure Interfaces
构造完DXGI_SWAP_CHAIN_DESC1实例和DXGI_SWAP_CHAIN_FULLSCREEN_DESC实例之后,就可以调用IDXGIFactory2::CreateSwapChainForHwnd()创建swap chain了。CreateSwapChainForHwnd()函数原型和参数如下:
HRESULT CreateSwapChainForHwnd(
IUnknown *pDevice,
HWND hWnd,
const DXGI_SWAP_CHAIN_DESC1 *pDesc,
const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc,
IDXGIOutput *pRestrictToOutput,
IDXGISwapChain1 **ppSwapChain);
pDevice:Direct3D device。
hWnd:与swap chain关联的窗口句柄。参数值即为CreateWindow()函数的返回值。
pDesc:swap chain description structure,即DXGI_SWAP_CHAIN_DESC1结构体实例。
pFullScreenDesc:full-screen description structure,即DXGI_SWAP_CHAIN_FULLSCREEN_DESC结构体实例。可以把该参数设为NULL,直接使用窗口模式的swap chain,
或者把结构体中的成员Windowed设置为true。
pRestrictToOutput:支持把数据限定到一个特定的输出target中。如果该参数设为NULL,输出就没有限制。
ppSwapChain:返回所创建的swap chain对象。
在测试代码示例之前,先讨论一下CreateSwapChainForHwnd()函数,该函数包含在IDXGIFactory2接口中,但是到目前为止IDXGIFactory接口还没有显示定义。DXGI表示DirectX Graphics Infrastructure,负责把渲染的图像帧显示到屏幕上以及从窗口模式切换到全屏显示模式。这些任务通常需要调用大量的API,并独立与Direct3D。创建Direct3D device时就会实例化一个IDXGIFactory2对象,但是需要查询device以获取IDXGIFactory2接口,通过调用IUnknow::QueryInterface()完成查询操作。IUnknow是一个COM(组件对象模型)接口,COM接口具有与标准C++类完全不同的构造和析构方法。查询并获取一个COM接口后,在释放该接口的实例对象时调用IUnknow::Release()。列表11.4列出了获取IDXGIFactory2接口所需要的代码,以及调用该接口的成员CreateSwapChainForHwnd()函数。
列表11.4 Creating a Swap Chain
#define ReleaseObject(object) if((object) != NULL) { object->Release(); object = NULL; }
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);
Creating a Render Target View
创建完swap chain时,就建立了一个back buffer,也是一个用于渲染的纹理。目的是要把该纹理buffer与Direct3D管线中的output-merger阶段进行绑定。但是,该纹理资源并不是直接与管线某个阶段进行绑定,而是创建一个该纹理的resource view,并把resource view与管线绑定。通过调用ID3D11Device::CreateRenderTargetView()函数可以创建一个render target view,CreateRenderTargetView()函数原型和参数如下:HRESULT CreateRenderTargetView(
ID3D11Resource *pResource,
const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
ID3D11RenderTargetView **ppRTView);
pResource:render target resouce。
pDesc:一个render target view description 结构体(D3D11_RENDER_TARGET_VIEW_DESC),通过该结构体可以显示定义render target view。把该参数设置为NULL,会创建一个render target view,该view以mip-level 0访问所有的subresources。
ppRTView:创建完成的render target view。
首先调用IDXGISwapChain::GetBuffer()函数从swap chain中获取一个texture resouce,再把该resource用于CreateRenderTargetView()函数中。列表11.5列出了查询swap chain并创建一个render target view的代码。
列表11.5 Creating a 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);
Creating a Depth-Stencil View
在一个场景中的objects可以重叠。一个距离camera较近的object会部分或完全遮挡一个距离camera较远的object。把一个pixel输出到render target中时,同时会把depth(深度,表示到camera的距离)写到一个depth buffer中。Depth buffer是一个2D texture,但是存储了depths值而不是颜色值。在output-merger阶段,可以使用这些depths值确定是否应该用新的pixels值覆盖render target中已经存在的颜色值。该过程称为depth testing(深度测试);在第1章,“Introducing DirectX”部分已经讨论过。
Stencil testing(模板测试)使用一个mask纹理来确定哪些pixels需要更新。在概念上就好比,使用一个纸板或塑料模具在一个物理表面上绘画或者打印一个设计图。Depth和stencil缓存是连在一起的,因此称之为depth-stencil buffer和depth-stencil view。
Stencil testing(模板测试)使用一个mask纹理来确定哪些pixels需要更新。在概念上就好比,使用一个纸板或塑料模具在一个物理表面上绘画或者打印一个设计图。Depth和stencil缓存是连在一起的,因此称之为depth-stencil buffer和depth-stencil view。
Populating a 2D Texture Description
当创建完swap chain时,隐含地创建了一个与back buffer关联的2D texture,但是并没有创建一个depth buffer(深度缓存)。通过调用ID3D11Device::CreateTexture2D()函数可以创建一个与depth buffer关联的2D texture。与创建swap chain时一样,该函数也是使用description structure模式,需要先构造一个D3D11_TEXTURE2D_DESC结构体实例。D3D11_TEXTURE2D_DESC结构的成员结构如下:typedef struct D3D11_TEXTURE2D_DESC
{
UINT Width;
UINT Height;
UINT MipLevels;
UINT ArraySize;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
} D3D11_TEXTURE2D_DESC;
Width: texture的宽度
Height: texture的高度.
MipLevels: texture中所包含的mip-levels数量.
ArraySize: 一个texture数组中的textures个数.
Format: texture的格式。Back buffer存储颜色值,与此种格式不同的是,depth buffer存储depth和stencil数据。常用格式有DXGI_FORMAT_D24_UNORM_S8_UINT(包含一个24位的depth buffer和一个8位的stencil buffer)和DXGI_FORMAT_D32_FLOAT(所有32位都用于depth buffer)。
SampleDesc: Multisampling参数。
Usage: texture的读写方式.通常设置为D3D11_USAGE_DEFAULT。
BindFlags: D3D11_BIND_DEPTH_STENCIL表示绑定一个depth-stencil buffer。
CPUAccessFlags: 部分设置选项按位与运算的值,用于指定CPU是否可以读或写texture。设置为0表示CPU不能读也不能写。
MiscFlags: 用于一些不常用的resource选项的标志。不用于depth-stencil buffer中。
列表11.6显示了一个构造D3D11_TEXTURE2D_DESC结构体实例的示例代码。
列表11.6 Populating a 2D Texture Description Structure
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;
}
Creating a 2D Texture and Depth-Stencil View
使用D3D11_TEXTURE2D_DESC结构实例作为参数,调用ID3D11Device::CreateTexture2D()函数创建一个2D texture,CreateTexture2D()函数原型和参数如下:
HRESULT CreateTexture2D(
const D3D11_TEXTURE2D_DESC *pDesc,
const D3D11_SUBRESOURCE_DATA *pInitialData,
ID3D11Texture2D **ppTexture2D);
pDesc:2D texture description结构体(D3D11_TEXTURE2D_DESC)
pInitialData:创建texture时需要用到的初始数据。对于depth buffer则不需要初始数据,因此该参数可以设置为NULL。
ppTexture2D:返回创建好的2D texture。
最后,调用ID3D11Device::CreateDepthStencilView()创建depth-stencil view。该函数与CreateRenderTargetView()具有一组类似的参数。列表11.7列出了创建一个depth-stencil buffer和depth-stencil view的示例代码。
列表11.7 Creating a Depth Buffer and Depth-Stencil View
ID3D11Texture2D* mDepthStencilBuffer;
ID3D11DepthStencilView* mDepthStencilView;
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);
}
Associating the Views to the Output-Merger Stage
创建了render target view和depth-stencil view之后,就可以调用ID3D11Device::OMSetRenderTargets()函数把这两种views与Direct3D管线的output-merger阶段绑定。OMSetRenderTargets()函数原型和参数如下:void OMSetRenderTargets(
UINT NumViews,
ID3D11RenderTargetView *const *ppRenderTargetViews,
ID3D11DepthStencilView *pDepthStencilView);
NumViews:与管线绑定的render targets数量。第四部分,“Intermediate-Level Rendering Topics”,会讲解使用多个render targets。现在,把该参数设置为1,只绑定一个render target。
ppRenderTargetViews:与管线绑定的render targets(输入)数组(数组的元素个数必须与NumViews参数一致)。
pDepthStencilView:与管线绑定的depth-stencil view。
列表11.8列出了把这两种views绑定到管线的output-merger阶段的代码。
列表11.8 Binding the Views to the Output-Merger Stage
mDirect3DDeviceContext->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);
Setting the Viewport
Direct3D初始化过程的最终一步是设置一个viewport。一个viewport是一个矩形区域,可以容下back buffer全部或者部分;场景会被渲染到该区域中。Viewports通常用于多人分屏游戏中,在这种游戏中,游戏世界的不同视图分别在场景的不同区域显示。设置viewport之前需要先构造一个D3D11_VIEWPORT类型的实例,再调用ID3D11DeviceContext::RSSetViewports()函数。D3D11_VIEWPORT结构的成员结构如下:typedef struct D3D11_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D11_VIEWPORT;
TopLeftX, TopLeftY, Width, Height:用于定义viewport区域。
MinDepth, MaxDepth:depth buffer的最小和最大值。这两个变量通常分别设置为0.0和1.0。
RSSetViewports()函数只有两个参数:需要绑定的viewports的数量,以及viewport数组。列表11.9列出了创建一个D3D11_VIEWPORT实例,并把该实例与管线进行绑定的示例代码。
列表11.9 Setting the Viewport
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;
mDirect3DDeviceContext->RSSetViewports(1, &mViewport);