绝大部分内容来自于《Introduction to 3D Game Programming with DirectX12 Frank D. Luna》
- Direct3D 初始化 Direct3D Initialization
- 1 基本概念
- 11 Direct3D 12 概述
- 12 COM
- 13 纹理格式Textures Formats
- 14 交换链Swap Chain和页面翻转Page Flipping
- 15 深度缓冲Depth Buffering
- 16 资源Resources与描述符Descriptors
- 17 多重采样Multisampling
- 18 Direct3D 中的多重采样
- 19 特征级Feature Levels
- 110 DirectX 图形基础结构DirectX Graphics Infrastructure
- 111 检查特征支持Checking Feature Support
- 112 资源驻留Residency
- 2 CPU 与 GPU 的交互CPUGPU INTERACTION
- 1 基本概念
4. Direct3D 初始化 (Direct3D Initialization)
从此之后,所接触的内容不再是一些枯燥的数学题了,而是和 DirectX 本身切实相关。在本章中,所要了解的内容包括熟悉 Direct3D 中所包含的类型,基本图形概念,以及 Direct3D 初始化的一系列步骤等等。
所要了解的内容有以下几个:
1. 需要对 Direct3D 在硬件中的编程所扮演的角色有一个基本的了解;
2. COM 在Direct3D 中起到的作用;
3. 了解基本的图形概念。这其中包括 2D 图像的存储,页面翻转,深度缓冲,多重采样,以及 CPU 和 GPU 之间是如何协调工作的;
4. 要如何进行 Direct3D 初始化;
5. 在本章中会展示一个通用的框架结构,此框架会贯穿全书,所以要尽可能了解它。
4.1 基本概念
4.1.1 Direct3D 12 概述
Direct3D是工作在图形处理单元(GPU:Graphics Processing Unit)上的底层应用程序接口(API:Application Programming Interface),所以当我们渲染3D世界图形时,能够使用硬件条件进行加速。每当 Direct3D 应用程序执行函数时,Direct3D 硬件驱动程序会直接将命令转换为GPU所能理解的指令进行执行,因此不必担心 GPU 的工作细节,只需要注意 GPU 所能支持的 Direct3D 版本就可以了。
Direct3D 12 概述:https://msdn.microsoft.com/zh-cn/communitydocs/game-development/directx-12-white-paper/ta15073002
4.1.2 COM
组件对象模型(COM:Component Object Model)允许 DirectX 独立为一种新的编程语言(理解上可以这么说)并能够向下兼容,换句话说就是面向组件编程。通常来说,我们把 COM 对象称之为接口,而这些接口的细节大多是以 C++ 语言来完成的。使用 COM 组件我们可以通过特殊的方式或者函数来获取 COM 接口的指针,而不必再使用 C++ 的 new
关键字了。此外,COM 对象使用的是引用计数,当我们完成了一个过程需要释放接口对象时,就需要调用 Release
函数(相当于 C++ 的 delete
关键字)来完成释放内存的操作,并将引用计数置为 0 。
为此,我们使用Windows运行时库(WRL:Windows Runtime Library)的 Microsoft::WRL::ComPtr
类(#include <wrl.h>
)来管理COM对象的生命周期。当一个 ComPtr 超出生命周期之外,则Release
函数会被自动调用来释放 COM 对象,这时就不需要去手动调用它。
ComPtr主要有3个函数被经常使用:
1. Get
:返回一个COM对象的指针。一般情况下,当一个 COM 对象需要作为参数传递给函数时调用Get
。例如:
ComPtr<ID3D12RootSignature> mRootSignature;
...
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
2. GetAddressOf
:返回一个 COM 对象地址的指针。可以在某个函数需要 COM 对象的指针时,使用GetAddressOf
来获得指针:
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
...
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.GetAddressOf()
)
);
3. Reset
:将一个 ComPtr 置为 nullptr(空指针),并将 COM 引用计数为0。
4.1.3 纹理格式(Textures Formats)
对于一个 2D 纹理(Textures)来说,它可以看作是一个矩阵,矩阵中的每一个元素都是一个数据点,而每个数据点都是这个 2D 图片的颜色像素。然而在映射中,上述矩阵的每一个元素并非一个颜色像素,而是一个 3D 向量,这样,就可以将一个 2D 的图像映射到 3D 物体表面,这就相当于把一张平面图贴在 3D 物体上。
当然,纹理不仅仅是一个二维数据组成的数据结构那么简单。它可以被GPU进行特殊处理,让它能够被应用过滤器和多重采样等功能中。
而且,纹理的数据元素不能是任意类型,它的类型是有限的(以下是DXGI_FORMAT
枚举类型的部分数据成员):
1. DXGI_FORMAT_R32G32B32_FLOAT
:每个数据元素是由3个32位的浮点数组成。
2. DXGI_FORMAT_R16G16B16A16_UNORM
:每个数据元素是由4个16位映射到 [0,1] 范围数字组成。
3. DXGI_FORMAT_R32G32_UINT
:每个数据元素为2个32位的无符号整数。
4. DXGI_FORMAT_R8G8B8A8_UNORM
:每个数据元素映射至 [0,1] 范围,由4个8位无符号数组成。
5. DXGI_FORMAT_R8G8B8A8_SNORM
:每个数据元素映射至 [-1,1] 范围,由4个8位有符号数组成。
6. DXGI_FORMAT_R8G8B8A8_SINT
:4个8位有符号整数、每个元素映射至 [-128,127] 。
7. DXGI_FORMAT_R8G8B8A8_UINT
:4个8位无符号整数、每个元素映射至 [0,255] 。
DXGI_FORMAT enumeration: https://msdn.microsoft.com/en-us/library/windows/desktop/bb173059
4.1.4 交换链(Swap Chain)和页面翻转(Page Flipping)
为了避免画面闪烁,最好将整个画面帧都写入一个被叫做后缓冲(back buffer)里。当一个画面帧存在于后缓冲中,这个画面就不会在屏幕展示出来,而是作为备用的帧在后端。当前缓冲(front buffer)存储的画面帧在显示器上显示完成,则前后缓冲执行翻转操作,前后缓冲区指针调换,将后缓冲作为前缓冲,存储的画面帧进行显示,此过程被称之为呈现(presenting);前缓冲作为后缓冲,准备下一帧将要显示的画面。
上述两个缓冲区组成交换链(Swap Chain)。在 Direct3D 中,交换链是由 IDXGISwapChain
接口表示。该接口存储前后缓冲的纹理,并且提供了设置缓冲区大小的函数 IDXGISwapChain::ResizeBuffers
和呈现函数 IDXGISwapChain::Present
。
利用两个缓冲区(前后缓冲)组成交换链的方式叫做双重缓冲(double buffering),也就是说,交换链不是必须要用两个缓冲区才可以,也可以用两个数量以上的缓冲,但是通常情况下两个就已经绰绰有余了。
尽管后缓冲区存储的是一个纹理,但是它却没有存储颜色信息——也就是说,它的每个数据元素并非是一个像素(pixel),而是一个“纹素(texel)”。
4.1.5 深度缓冲(Depth Buffering)
深度缓冲同样是纹理,而且同样不包含图像信息,它包含像素有关“深度”的信息。深度值的范围在 0.0 与 1.0 之间,其中 0.0 表示是最接近显示器屏幕,1.0 是距离显示器屏幕最远,这样就能达到 3D 物体在 2D 屏幕上显示的遮盖以及近大远小等效果。深度缓冲存储的信息数目与分辨率相同,每一个像素都有属于自己的深度缓冲信息。
由于深度缓冲属于纹理,所以它必须被特殊的数据格式来定义:
DXGI_FORMAT_D32_FLOAT_S8X24_UINT
:指定一个32位浮点数深度缓冲,保留8位(unsigned integer,[0, 255])为模版缓冲(stencil buffer)使用,其余的24位不用于填充。DXGI_FORMAT_D32_FLOAT
:指定一个32位浮点数深度缓冲。DXGI_FORMAT_D24_UNORM_S8_UINT
:指定一个无符号24位([0, 1])深度缓冲,8位(unsigned integer,[0, 255])为模版缓冲(stencil buffer)使用。DXGI_FORMAT_D16_UNORM
:指定一个无符号16位([0, 1])的深度缓冲。
一个应用程序一般不需要模版缓冲(stencil buffer),但如果它一旦存在,它一定是附加到了深度缓冲中。例如
DXGI_FORMAT_D24_UNORM_S8_UINT
使用了24位为深度缓冲使用,而其余8位为模版缓冲使用,所以,深度缓冲的更好说法是“深度/模版缓冲(