本文的主要内容
刚开始学习DirectX 11
时,看了诸多的资料包括源码后,仍是一头雾水,花费了大量的时间不断地理解代码到底是如何作用于我想要的结果的。本文旨在记录和阐述各个代码在整个程序中扮演的角色。并以天空盒的实现作为了一个小小的例子。本人也是初学这部分内容,若有说的错误之处,还望指正。
源码链接
一、准备知识
想要能上手Direct3D,必然不能是零基础的,在开始学习之前,主要掌握或者了解以下知识:
- 渲染流水线的大致步骤。
- 空间几何,线性代数。
掌握上述知识后,就可以开始看别人的代码跟着学了。本文以天空盒的实现为例,大致勾画出一个完整的Direct3D程序应有的步骤。
至于天空盒,是利用立方体映射技术将一个贴着天空纹理的立方体映射到一个球面上,然后通过将天空盒的圆心位置和摄像机坐标同时移动形成天空效果。
效果图如下所示:
二、项目创建和Direct3D初始化
首先应当创建一个桌面应用或者空项目但子系统指定为窗口(而不是控制台应用),之后只需要少量的Win32代码就可以创建出一个窗口,在处理消息的循环中,我们的程序会不断更新。更新的具体方法可以在MsgProc
函数中进行定义。
在这个部分,需要关注的变量和函数有如下:
MsgProc
函数和MSG messages
变量,毋庸置疑,这部分决定了我们的操作带来什么样的效果。至于其中的更多细节,属于Win32编程中的内容,可参考《 windows 程序设计 第五版》一书。HWND MainWnd
变量指的是该窗口的句柄,只有拥有该变量的值,我们才能够捕获窗口中的事件以及将场景几何体渲染到该窗口。
LRESULT CALLBACK MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance,
_In_ LPSTR cmdLine, _In_ int showCmd)
{
// 这些参数不使用
UNREFERENCED_PARAMETER(prevInstance);
UNREFERENCED_PARAMETER(cmdLine);
UNREFERENCED_PARAMETER(showCmd);
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MsgProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"D3DWndClassName";
int width = 800, height = 600;
if (!RegisterClass(&wc))
{
MessageBox(0, L"RegisterClass Failed.", 0, 0);
return false;
}
RECT R = {
0, 0, width, height };
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
HWND MainWnd = CreateWindow(L"D3DWndClassName", L"SkyBox",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, hInstance, 0);
if (!MainWnd)
{
MessageBox(0, L"CreateWindow Failed.", 0, 0);
return false;
}
ShowWindow(MainWnd, SW_SHOW);
UpdateWindow(MainWnd);
MSG messages = {
0 };
/* 在这部分就可以创建并初始化我们的Direct3D程序了*/
//这儿会不断接受并处理消息
while (messages.message != WM_QUIT)
{
if (PeekMessage(&messages, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
// 在这里处理消息并更新我们的应用程序
}
return messages.wParam;
}
当窗口创建完毕,根据窗口的句柄即可以初始化Direct3D所需的资源,这部分的代码相对变动较小,可以先抄了跑起来再说 之后再细细研究。
初始化Direct3D(D3D)所需的步骤大致如下:
-
定义想检查的设备类型和特征级别,这部分也就是检查一下可用的设备(硬件设备,WARP设备,软件驱动设备之类),以及所支持的DirectX类型(D3D11.0,D3D10.1,D3D10.0等)。
// 驱动类型数组 D3D_DRIVER_TYPE driverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE, }; // 特性等级数组 D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, };
-
创建D3D设备,上下文和交换链,D3D设备(
ID3D11Device
)和上下文(ID3D11DeviceContext
)是用于和硬件交互的接口,也是后续用到的最多的东西。交换链主要是用双缓冲技术保证画面稳定所用。D3D设备和D3D上下文的主要的功能分别为,D3D设备一般用于分配资源(例如创建缓冲区等),而D3D上下文用于将资源绑定到图形管线并产生渲染命令等。下段代码是对这三个创建的示意程序。D3D_FEATURE_LEVEL featureLevel; ID3D11Device * pd3dDevice; // D3D设备 ID3D11DeviceContext * pd3dImmediateContext; // D3D上下文 IDXGISwapChain * pSwapChain; // D3D交换链 // 在hr中可以得知是否创建成功,这个返回值在之后也会反复出现 HRESULT hr = D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0, createDeviceFlags, 0, 0, D3D11_SDK_VERSION, &d3dDevice, &featureLevel, &d3dImmediateContext); // 先对交换链描述结构体进行填充,再调用函数来创建交换链 DXGI_SWAP_CHAIN_DESC swapChainDesc; // 交换链描述 // 填充swapChainDesc CreateSwapChain(pd3dDevice, &swapChainDesc, &pSwapChain) // 创建交换链
-
创建渲染目标视图,渲染目标视图主要为了在交换链的辅助缓存(在后面绘画的缓冲区)中联合渲染。
ID3D11RenderTargetView * pBackBufferTarget; // 渲染目标视图 ID3D11Texture2D * pBackBufferTexture; // 贴图 HRESULT result; // 获取辅助缓存的指针 result = pSwapChain->GetBuffer(0