我想复习一下D3D11的一些基础API,于是翻了翻Microsoft DirectX SDK (June 2010)找到其中一个最简单的D3D11的实例(\Samples\C++\Direct3D11\Tutorials\Tutorial02
)。可惜因为SDK版本太旧,安装的教学工程是VS2010的,我尝试升级成VS2017的工程但失败了。于是我决定手动新创建一个工程来跑起来这个教学范例,过程中也抛弃掉了一些比较边缘,不影响效果的内容(其中较为重要的被舍弃的内容是:1.清理掉对象;2.输出shader编译的错误)。
实践
首先,需要安装Microsoft DirectX SDK (June 2010),因为用到了其中的函数。
0.创建一个空工程
之后创建一个cpp文件。
1.目标是创建一个窗口
1.1 入口函数
我们的入口函数是
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
要确保配置中 链接器 =》系统 =》子系统 设置为窗口,这将表示我们的入口函数是WinMain
而不是main
1.2 生成窗口
我们窗口的Handle将存在一个全局变量中:
HWND g_hWnd = NULL;
同时,需要一个“窗口过程”来接收Windows发来的消息:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
然后,就可以初始化窗口了
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
// Register class
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)"");
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "TestD3D11WindowClass";
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)"");
if (!RegisterClassEx(&wcex))
return E_FAIL;
// Create window
RECT rc = { 0, 0, WindowWidth, WindowHeight };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
g_hWnd = CreateWindowA("TestD3D11WindowClass", "TestD3D11Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance,
NULL);
if (!g_hWnd)
return E_FAIL;
ShowWindow(g_hWnd, nCmdShow);
return S_OK;
}
将入口函数改为:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
InitWindow(hInstance, nCmdShow);
MSG msg = { 0 };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
运行后可看见窗口:
2.目标是初始化D3D11并且以一颜色清理BackBuffer
首先将D3D11相关的头文件配置上:
然后,需要添加lib,
先把lib的路径添加进来
然后依赖d3d11.lib
和d3dx11.lib
下面,写一个新函数InitD3D
,他将初始化D3D相关的内容
HRESULT InitD3D()
{
HRESULT hr = S_OK;
return hr;
}
2.1 尝试创建Device和SwapChain,得到ImmediateContext
首先声明他们的全局变量
ID3D11Device* g_pd3dDevice = NULL;
ID3D11DeviceContext* g_pImmediateContext = NULL;
IDXGISwapChain* g_pSwapChain = NULL;
然后开始写InitD3D中的代码:
列出所有考虑的FeatureLevel:
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
列出所有考虑的驱动类型(越靠前越优先考虑)
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
创建SwapChain的描述结构体
DXGI_SWAP_CHAIN_DESC swapchainDescription;
ZeroMemory(&swapchainDescription, sizeof(swapchainDescription));
swapchainDescription.BufferCount = 1;
swapchainDescription.BufferDesc.Width = WindowWidth;
swapchainDescription.BufferDesc.Height = WindowHeight;
swapchainDescription.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapchainDescription.BufferDesc.RefreshRate.Numerator = 60;
swapchainDescription.BufferDesc.RefreshRate.Denominator = 1;
swapchainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapchainDescription.OutputWindow = g_hWnd;
swapchainDescription.SampleDesc.Count = 1;
swapchainDescription.SampleDesc.Quality = 0;
swapchainDescription.Windowed = TRUE;
调用D3D11CreateDeviceAndSwapChain
来创建Device和SwapChain,得到ImmediateContext
D3D_DRIVER_TYPE outDriverType; //最终决定的DriverType
D3D_FEATURE_LEVEL outFeatureLevel;//最终决定的FeatureLevel
//按照驱动类型依次尝试创建Device和SwapChain
for (UINT driverTypeIndex = 0; driverTypeIndex < ARRAYSIZE(driverTypes); driverTypeIndex++)
{
outDriverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain(NULL, outDriverType, NULL, 0, featureLevels, ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION, &swapchainDescription, &g_pSwapChain, &g_pd3dDevice, &outFeatureLevel, &g_pImmediateContext);
if (SUCCEEDED(hr))
break;
}
if (FAILED(hr))
return hr;
2.2创建一个 render target view
先声明全局变量
ID3D11RenderTargetView* g_pRenderTargetView = NULL;
继续写 InitD3D:
从SwapChain那里得到BackBuffer
ID3D11Texture2D* pBackBuffer = NULL;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr))
return hr;
调用CreateRenderTargetView
创建一个 render target view
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
pBackBuffer->Release();
if (FAILED(hr))
return hr;
输出合并阶段(Output-Merger Stage)设置RenderTarget
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);
2.3
Render函数:
只是以一个浅绿色来清理 back buffer
void Render()
{
// 清理 back buffer
float ClearColor[4] = { 0.5f, 1.0f, 0.6f, 1.0f };
g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);
// Present the information rendered to the back buffer to the front buffer (the screen)
g_pSwapChain->Present(0, 0);
}
然后改变我们的入口函数:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
InitWindow(hInstance, nCmdShow);
InitD3D();
MSG msg = { 0 };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
return msg.wParam;
}
运行会看到:
3.目标是画出一个三角形
3.1着色器文件
创建一个着色器文件TestShader.fx
,内容很简单:
float4 VS( float4 Pos : POSITION ) : SV_POSITION
{
return Pos;
}
float4 PS( float4 Pos : SV_POSITION ) : SV_Target
{
return float4( 0.8f, 0.7f, 0.9f, 1.0f );
}
顶点着色器只是返回位置,不做任何变换
像素着色器则只输出一个浅紫色
3.2
所需要的的全局变量:
ID3D11VertexShader* g_pVertexShader = NULL; //顶点着色器
ID3D11PixelShader* g_pPixelShader = NULL; //像素着色器
ID3D11InputLayout* g_pVertexLayout = NULL; //顶点输入布局
ID3D11Buffer* g_pVertexBuffer = NULL; //顶点缓冲
顶点输入结构:(只有一个“位置”)
struct SimpleVertex
{
XMFLOAT3 Pos;
};
设置视窗
D3D11_VIEWPORT vp;
vp.Width = (FLOAT)WindowWidth;
vp.Height = (FLOAT)WindowHeight;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
//光栅化阶段(Rasterize Stage)设置视窗
g_pImmediateContext->RSSetViewports(1, &vp);
创建顶点着色器
//编译顶点着色器
ID3DBlob* pVSBlob = NULL;
hr = D3DX11CompileFromFileA("TestShader.fx", NULL, NULL, "VS", "vs_4_0",0, 0, NULL, &pVSBlob, &pErrorBlob, NULL);
if (FAILED(hr))
return hr;
//创建顶点着色器
hr = g_pd3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &g_pVertexShader);
if (FAILED(hr))
{
pVSBlob->Release();
return hr;
}
创建输入布局
//定义顶点输入布局(与SimpleVertex对应)
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
//创建输入布局
hr = g_pd3dDevice->CreateInputLayout(layout, ARRAYSIZE(layout), pVSBlob->GetBufferPointer(),
pVSBlob->GetBufferSize(), &g_pVertexLayout);
pVSBlob->Release();
if (FAILED(hr))
return hr;
//输入装配阶段(Input-Assembler Stage)设置输入布局
g_pImmediateContext->IASetInputLayout(g_pVertexLayout);
创建像素着色器
//编译像素着色器
ID3DBlob* pPSBlob = NULL;
hr = D3DX11CompileFromFileA("TestShader.fx", NULL, NULL, "PS", "ps_4_0", 0, 0, NULL, &pPSBlob, &pErrorBlob, NULL);
if (FAILED(hr))
return hr;
//创建像素着色器
hr = g_pd3dDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &g_pPixelShader);
pPSBlob->Release();
if (FAILED(hr))
return hr;
创建顶点缓冲,只是个简单的三角形
//输入的顶点数据
SimpleVertex vertices[] =
{
XMFLOAT3(0.0f, 0.5f, 0.5f),
XMFLOAT3(0.5f, -0.5f, 0.5f),
XMFLOAT3(-0.5f, -0.5f, 0.5f),
};
//缓冲描述
D3D11_BUFFER_DESC bufferDescription;
ZeroMemory(&bufferDescription, sizeof(bufferDescription));
bufferDescription.Usage = D3D11_USAGE_DEFAULT;
bufferDescription.ByteWidth = sizeof(SimpleVertex) * 3;
bufferDescription.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bufferDescription.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
//创建顶点缓冲
hr = g_pd3dDevice->CreateBuffer(&bufferDescription, &InitData, &g_pVertexBuffer);
if (FAILED(hr))
return hr;
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
//输入装配阶段(Input-Assembler Stage)设置顶点缓冲
g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
//输入装配阶段(Input-Assembler Stage)设置拓扑结构
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
3.3
改变Render函数,中间加上三段:
void Render()
{
// 清理 back buffer
float ClearColor[4] = { 0.5f, 1.0f, 0.6f, 1.0f }; //浅绿色
g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);
// 渲染
g_pImmediateContext->VSSetShader(g_pVertexShader, NULL, 0);
g_pImmediateContext->PSSetShader(g_pPixelShader, NULL, 0);
g_pImmediateContext->Draw(3, 0);
// Present the information rendered to the back buffer to the front buffer (the screen)
g_pSwapChain->Present(0, 0);
}
运行后:
完整代码
#include <windows.h>
#include <d3d11.h>
#include <D3DX11.h>
#include <d3dcompiler.h>
#include <xnamath.h>
HWND g_hWnd = NULL;
ID3D11Device* g_pd3dDevice = NULL;
ID3D11DeviceContext* g_pImmediateContext = NULL;
IDXGISwapChain* g_pSwapChain = NULL;
ID3D11RenderTargetView* g_pRenderTargetView = NULL;
ID3D11VertexShader* g_pVertexShader = NULL; //顶点着色器
ID3D11PixelShader* g_pPixelShader = NULL; //像素着色器
ID3D11InputLayout* g_pVertexLayout = NULL; //顶点输入布局
ID3D11Buffer* g_pVertexBuffer = NULL; //顶点缓冲
const int WindowWidth = 800;
const int WindowHeight = 600;
//顶点输入数据
struct SimpleVertex
{
XMFLOAT3 Pos;
};
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow)
{
// Register class
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)"");
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "TestD3D11WindowClass";
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)"");
if (!RegisterClassEx(&wcex))
return E_FAIL;
// Create window
RECT rc = { 0, 0, WindowWidth, WindowHeight };
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
g_hWnd = CreateWindowA("TestD3D11WindowClass", "TestD3D11Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance,
NULL);
if (!g_hWnd)
return E_FAIL;
ShowWindow(g_hWnd, nCmdShow);
return S_OK;
}
HRESULT InitD3D()
{
HRESULT hr = S_OK;
//所有考虑的FeatureLevel
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};
//所有考虑的驱动类型(越靠前越优先考虑)
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
//创建SwapChain的描述结构体
DXGI_SWAP_CHAIN_DESC swapchainDescription;
ZeroMemory(&swapchainDescription, sizeof(swapchainDescription));
swapchainDescription.BufferCount = 1;
swapchainDescription.BufferDesc.Width = WindowWidth;
swapchainDescription.BufferDesc.Height = WindowHeight;
swapchainDescription.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapchainDescription.BufferDesc.RefreshRate.Numerator = 60;
swapchainDescription.BufferDesc.RefreshRate.Denominator = 1;
swapchainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapchainDescription.OutputWindow = g_hWnd;
swapchainDescription.SampleDesc.Count = 1;
swapchainDescription.SampleDesc.Quality = 0;
swapchainDescription.Windowed = TRUE;
D3D_DRIVER_TYPE outDriverType; //最终决定的DriverType
D3D_FEATURE_LEVEL outFeatureLevel;//最终决定的FeatureLevel
//按照驱动类型依次尝试创建Device和SwapChain
for (UINT driverTypeIndex = 0; driverTypeIndex < ARRAYSIZE(driverTypes); driverTypeIndex++)
{
outDriverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDeviceAndSwapChain(NULL, outDriverType, NULL, 0, featureLevels, ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION, &swapchainDescription, &g_pSwapChain, &g_pd3dDevice, &outFeatureLevel, &g_pImmediateContext);
if (SUCCEEDED(hr))
break;
}
if (FAILED(hr))
return hr;
//创建一个 render target view
ID3D11Texture2D* pBackBuffer = NULL;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (FAILED(hr))
return hr;
hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
pBackBuffer->Release();
if (FAILED(hr))
return hr;
//输出合并阶段(Output-Merger Stage)设置RenderTarget
g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);
// 设置视窗
D3D11_VIEWPORT vp;
vp.Width = (FLOAT)WindowWidth;
vp.Height = (FLOAT)WindowHeight;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
//光栅化阶段(Rasterize Stage)设置视窗
g_pImmediateContext->RSSetViewports(1, &vp);
ID3DBlob* pErrorBlob;
//编译顶点着色器
ID3DBlob* pVSBlob = NULL;
hr = D3DX11CompileFromFileA("TestShader.fx", NULL, NULL, "VS", "vs_4_0",0, 0, NULL, &pVSBlob, &pErrorBlob, NULL);
if (FAILED(hr))
return hr;
//创建顶点着色器
hr = g_pd3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &g_pVertexShader);
if (FAILED(hr))
{
pVSBlob->Release();
return hr;
}
//定义顶点输入布局(与SimpleVertex对应)
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
//创建输入布局
hr = g_pd3dDevice->CreateInputLayout(layout, ARRAYSIZE(layout), pVSBlob->GetBufferPointer(),
pVSBlob->GetBufferSize(), &g_pVertexLayout);
pVSBlob->Release();
if (FAILED(hr))
return hr;
//输入装配阶段(Input-Assembler Stage)设置输入布局
g_pImmediateContext->IASetInputLayout(g_pVertexLayout);
//编译像素着色器
ID3DBlob* pPSBlob = NULL;
hr = D3DX11CompileFromFileA("TestShader.fx", NULL, NULL, "PS", "ps_4_0", 0, 0, NULL, &pPSBlob, &pErrorBlob, NULL);
if (FAILED(hr))
return hr;
//创建像素着色器
hr = g_pd3dDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &g_pPixelShader);
pPSBlob->Release();
if (FAILED(hr))
return hr;
//输入的顶点数据
SimpleVertex vertices[] =
{
XMFLOAT3(0.0f, 0.5f, 0.5f),
XMFLOAT3(0.5f, -0.5f, 0.5f),
XMFLOAT3(-0.5f, -0.5f, 0.5f),
};
//缓冲描述
D3D11_BUFFER_DESC bufferDescription;
ZeroMemory(&bufferDescription, sizeof(bufferDescription));
bufferDescription.Usage = D3D11_USAGE_DEFAULT;
bufferDescription.ByteWidth = sizeof(SimpleVertex) * 3;
bufferDescription.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bufferDescription.CPUAccessFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
//创建顶点缓冲
hr = g_pd3dDevice->CreateBuffer(&bufferDescription, &InitData, &g_pVertexBuffer);
if (FAILED(hr))
return hr;
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
//输入装配阶段(Input-Assembler Stage)设置顶点缓冲
g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
//输入装配阶段(Input-Assembler Stage)设置拓扑结构
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return S_OK;
}
void Render()
{
// 清理 back buffer
float ClearColor[4] = { 0.5f, 1.0f, 0.6f, 1.0f }; //浅绿色
g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);
// 渲染
g_pImmediateContext->VSSetShader(g_pVertexShader, NULL, 0);
g_pImmediateContext->PSSetShader(g_pPixelShader, NULL, 0);
g_pImmediateContext->Draw(3, 0);
// Present the information rendered to the back buffer to the front buffer (the screen)
g_pSwapChain->Present(0, 0);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
InitWindow(hInstance, nCmdShow);
InitD3D();
MSG msg = { 0 };
while (WM_QUIT != msg.message)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
}
}
return msg.wParam;
}