Direct3D 11 - Overview


Direct3D概述

Direct3D 是一个 底层(low-level)图形API,可以是我们获得3D硬件加速功能,提高3D场景渲染效率。D3D提供一组软件接口用来控制图形硬件(graphics hardware)。比如,命令图形硬件clear渲染目标(render target),我们可以调用 ID3D11DeviceContext::ClearRenderTargetView 方法。

支持Direct3D 11 的设备必须支持 Direct3D 11 规定的完整的功能集合(capability set),除了少数功能(比如多重采样个数仍然需要查询完成),而Direct3D 9 允许硬件设备只支持部分功能,Direct3D 9 应用使用某些特性时必须先检查硬件是否支持该特性。

COM

组件对象模型COM(Component Object Model)技术允许DirectX独立于任何编程语言,同时具有向后兼容性。我们经常把COM对象当作接口,可以认为是一个C++类。当使用C++编写DirectX程序时,COM的大多数底层细节被隐藏。我们需要知道的是,我们必须通过特定的方法或者COM接口方法来获取指向COM接口的指针,而不能使用 new 关键字来创建一个COM接口。当不再使用某个COM接口时,需要调用 Release 方法释放它,同样不能使用delete语句,COM对象在其自身内部实现所有的内存管理工作。(所有的COM接口都继承IUnkown接口,Release方法是IUnkown接口的成员函数。)

纹理和数据资源格式

2D纹理(texture)是一个数据矩阵。2D纹理的用途之一是存储2D图像数据,在纹理的每一个元素存储一个像素颜色。不仅如此,还有其他用途。比如法线贴图映射技术(technique),存储的并不是颜色,而是3D向量。从通常意义来讲,纹理用来存储图像数据,1D Texture 类似于 1D 数组,3D Texture 类似于 3D 数组。纹理不仅是一个数据数组,纹理还可以带有多级渐近纹理层(mipmap level),GPU可以在它上面执行特定的运算,比如施加过滤和多重采样。此外,不是任何类型数据都能存储到纹理,纹理只支持特定格式的数据存储,这些格式由 DXGI_FORMAT 枚举类型描述:

- DXGI_FORMAT_R32G32B32_FLOAT :每个元素是一个32位浮点数  
- DXGI_FORMAT_R16G16B16A16_UNORM :每一个元素包含4个16位分量,分量的取值范围在 [0, 1] 区间  
- DXGI_FORMAT_R32G32_UINT :每个元素包含两个32位无符号整数分量  
- DXGI_FORMAT_R8G8B8A8_UNORM:每个元素包含4个8位无符号分量,分量的取值范围在 [0, 1] 区间  
- DXGI_FORMAT_R8G8B8A8_SNORM : 每个元素包含4个8位有符号分量,分量的取值范围在 [-1, 1] 区间    
- DXGI_FORMAT_R8G8B8A8_SINT :每个元素包含4个8位有符号整数分量,分量的取值范围在 [−128, 127] 区间  
- DXGI_FORMAT_R8G8B8A8_UINT :每个元素包含4个8位无符号整数分量,分量的取值范围在 [0, 255] 区间    

交换链(Swap Chain)和页面翻转(Page Flipping)

为了避免动画闪烁,硬件会自动维护两个内置的纹理缓冲区(buffer)实现这一功能,分别称为前台缓冲区(front buffer)和后台缓冲区(back buffer),前台缓冲区存储了当前显示在屏幕的像素,而动画的下一帧会在后台缓冲区执行绘制。当下一帧绘制完成,前后两个缓冲区发生翻转:后台缓冲区变前台缓冲区,前台缓冲区变后台缓冲区。这个操作只需要把指向前台缓冲区和指向后台缓冲区的指针交换即可。翻转操作又称作为提交(presenting)。

SwapChain

前后缓冲区形成一个交换链。在Direct3D种,交换链由 IDXSwapChain 接口表示。该接口保存了前后缓冲区纹理,并提供了调整缓冲区尺寸的方法(IDXSwapChain::ResizeBuffers)和 presenting(IDXSwapChain::Present)。

深度缓冲区

一个包含每一个像素深度值的纹理对象,D3D中,深度范围在 [0, 1] 之间,用来做深度测试。数据格式有:

- DXGI_FORMAT_D32_FLOAT_S8X24_UINT :32位浮点深度缓冲区,为模板缓冲区预留8位(无符号整数),每个模板值的取值范围为 [0, 255]。其余24位闲置  
- DXGI_FORMAT_D32_FLOAT : 32位浮点数深度缓冲区  
- DXGI_FORMAT_UNORM_S8_UINT : 无符号24位深度缓冲区,每个深度值的取值范围为[0, 1]。为模板缓冲区预留8位(无符号整数),每个模板值的取值范围为[0, 255]  
- DXGI_FORMAT_D16_UNORM :无符号16位深度缓冲区,每个深度的取值范围是[0, 1]  

注意模板缓冲区对应用程序来说不是必须的,但是如果用到了模板缓冲区,那么模板缓冲区必定是与深度缓冲区存储在一起。

纹理资源视图

纹理对象可以绑定到 渲染管线 (rendering pipeline)的不同阶段(stage),例如,比较常见的,将纹理作为渲染目标(rendering target)或者将纹理作为着色器(shader)的资源。当创建用于以上两种目的纹理资源时,使用绑定标志:

D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE

事实上,资源不能直接绑定到一个管线阶段,我们只能把与资源相关的资源视图(resource views)绑定到不同的管线阶段。无论以哪种方式使用纹理,Direct3D始终要求我们在初始化时为纹理创建相关的资源视图,因为使用视图有助于提高效率,正如SDK文档指出,“运行时环境与驱动程序可以在创建时执行相应的验证和映射,减少绑定时的类型检查…”。当把纹理作为一个渲染目标和着色器资源时,我们要为它创建两种视图:渲染目标视图(ID3D11RenderTargetView)和着色器资源视图(ID3D11ShaderResourceView)。资源视图有两个主要功能:

  1. 告诉Direct3D如何使用资源(即,指定资源所要绑定的管线阶段)
  2. 如果在创建资源时指定的是弱类型格式,那么为它创建视图时必须指定明确的资源类型。对于弱类型格式,纹理元素可能会在一个管线阶段中视为浮点数,而在另一个管线阶段中视为整数。

为了给资源创建一个特定的视图,资源的创建必须使用特定的标志值。例如,如果在创建资源时没有使用 D3D11_BIND_DEPTH_STENCIL 绑定标志值(该标志值表示纹理将绑定到管线的 深度/模板 缓冲区),那么我们无法为该资源创建 ID3D11DepthStencilView11 视图。

2009年8月SDK文档指出:“当创建资源时,为资源指定强类型(fully-typed)格式,把资源的用途限制在格式规定的范围内,有利于运行时优化资源访问…”,所以,你只应该在真正需要弱类型资源时,才创建弱类型资源;否则,应尽量创建强类型资源。

多重采样

多重采样是为了缓解锯齿效果。使用多重采样,需要填充一个 DXGI_SAMPLE_DESC 结构体:

typedef struct {
     UINT Count;
     UINT Quality;
} DXGI_SAMPLE_DESC, * LPDXGI_SAMPLE_DESC;

Count 成员用于指定每个像素的采样数量,Quality 成员用于指定希望得到的质量级别。质量级别越高,占用的系统资源越多。所以我们必须在质量和速度之间权衡利弊。质量级别的取值范围由纹理格式和单个像素的采样数量决定。可以使用下面的方法,通过指定纹理格式和采样点数量来查询相应的质量级别:

HRESULT ID3D11Device::CheckMultisampleQualityLevels(
DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels);

如果纹理格式和采样数量的组合不被设备支持,则返回 0 ;否则,通过 pNumQualityLevels 参数返回符合条件的质量等级数值,有效的质量级别范围为0到pNumQualityLevels−1。

每一个像素的最大采样个数宏定义:

#define D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT ( 32 )

然而,考虑到设备性能以及内存开销,采样数量一般设置为 4 或者 8。如果不想使用多重采样,可以将采样数量设置为 1,将质量级别设为 0。

注意我们需要为交换链缓冲区和深度缓冲区各填充一个 DXGI_SAMPLE_DESC 结构体。当创建后台缓冲区和深度缓冲区时,必须使用相同的多重采样设置。

特征级别

特征级别定义了一系列支持不同d3d功能的相应等级,如果一个用户的硬件不支持某一特征等级,程序可以选择较低的等级来运行。

下面是d3d定义的几个不同级别代表不同的d3d版本。

typedef enum D3D_FEATURE_LEVEL
{
    D3D_FEATURE_LEVEL_9_1 = 0x9100,
    D3D_FEATURE_LEVEL_9_2 = 0x9200,
    D3D_FEATURE_LEVEL_9_3 = 0x9300,
    D3D_FEATURE_LEVEL_10_0 = 0xa000,
    D3D_FEATURE_LEVEL_10_1 = 0xa100,
    D3D_FEATURE_LEVEL_11_0 = 0xb000,
} D3D_FEATURE_LEVEL;

在初始化过程中,我们可以提供一组不同的特征等级,程序会从第一个开始逐个检测,碰到第一个合适的来创建设备。因此我们在数组中从高到低放置特征等级提供给初始化程序。 比如,我们定义:

D3D_FEATURE_LEVEL featureLevels[4] =
{
    D3D_FEATURE_LEVEL_11_0, // First check D3D 11 support
    D3D_FEATURE_LEVEL_10_1, // Second check D3D 10.1 support
    D3D_FEATURE_LEVEL_10_0, // Next, check D3D 10 support
    D3D_FEATURE_LEVEL_9_3 // Finally, check D3D 9.3 support
};